GlassSurface
The primitive: a box of the screen that is glass. It refracts, blurs and tints the backdrop, then paints its child.
GlassSurface · kGlassCapsule · GlassFade · Source
GlassSurface says "this box of the screen is glass". It refracts, blurs and tints what is behind it, draws a thin rim
along its edge, then paints its child on top. Its shape is a smooth rounded rectangle, Apple's continuous-corner
squircle, drawn as the engine's own RSuperellipse.
Every component in the package is built from it. Reach for it when you need a shape no component offers.
When to use
- Custom glass: lenses, blobs, a now-playing pill, a panel of your own design.
- Members of a GlassGroup or
GlassUnion, which fuse surfaces into one silhouette. - Not when a component exists. GlassBar, GlassCard and GlassButton also choose a legible label colour, which a raw surface does not.
Usage
const SizedBox(
width: 220,
height: 120,
child: GlassSurface(
borderRadius: BorderRadius.all(Radius.circular(28)),
child: Center(child: Text('Glass', style: TextStyle(color: Color(0xFFFFFFFF)))),
),
)
A surface is a plain box: give it a size with a SizedBox, a Positioned with a width and height, or a child that
has one. kGlassCapsule makes a pill or a circle at any size.
Appearing and leaving
Two values, from 0 to 1, and they do different things:
materializeis how far the material has arrived: the bend and the blur first, the tint last, over the whole shape. That is Apple's materialize transition, and the one to animate when a panel appears or leaves. At 0 nothing is drawn or captured.presenceis how much of the shape exists. Inside a GlassGroup a member growing from 0 buds out of its neighbours. On a lone panel it narrows the shape to a line, which is rarely what you want.
Labels
labelled (true by default) says text sits on this glass. When the host declares minLabelContrast, labelled glass
may be dimmed to keep that text readable. Set labelled: false on glass that carries no text, such as lenses, drops
and blobs, so it keeps its finish exactly as named. Inside a raw surface, set the text colour yourself; see
Legibility & theme.
Performance
- Each surface is one draw. Keep the count low and prefer one bigger surface to many small ones.
- Animating
presencecosts no capture. Animatingmaterializechanges the blur, which means a capture every frame while it runs. - Moving glass belongs in a GlassTravel region so the motion costs no capture.
Tip
Set debugPaintGlassSurfaces = true in debug builds to outline every surface in cyan.
Gotchas
- Hit-testing goes to the child only: an empty surface is not tappable unless it has a ripple.
- Glass sitting beside other glass does not show it. To float a surface above other glass, wrap it in GlassAbove.
Code
import 'package:flutter/material.dart';
import 'package:g1455/g1455.dart';
/// A now-playing pill that materializes in, next to a magnifying lens.
/// Assumes a GlassHost above, e.g. in MaterialApp.builder.
class NowPlaying extends StatefulWidget {
const NowPlaying({super.key});
@override
State<NowPlaying> createState() => _NowPlayingState();
}
class _NowPlayingState extends State<NowPlaying> with SingleTickerProviderStateMixin {
late final AnimationController _in = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 400),
)..forward();
@override
void dispose() {
_in.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
AnimatedBuilder(
animation: _in,
builder: (BuildContext context, Widget? child) => GlassSurface(
borderRadius: kGlassCapsule,
// Blur and bend arrive first, the tint last.
materialize: Curves.easeOut.transform(_in.value),
child: child,
),
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(Icons.music_note, color: Colors.white),
SizedBox(width: 8),
Text('Now playing', style: TextStyle(color: Colors.white)),
],
),
),
),
const SizedBox(width: 16),
// A text-free lens: `labelled: false` keeps the clear finish undimmed.
SizedBox(
width: 72,
height: 72,
child: GlassSurface(
borderRadius: kGlassCapsule,
finish: GlassFinish.clear.copyWith(optics: const GlassOptics(zoom: 1.4)),
labelled: false,
),
),
],
);
}API
| Parameter | Type | Default | Description |
|---|---|---|---|
borderRadius |
BorderRadius |
BorderRadius.all(Radius.circular(24)) |
Corner radii. kGlassCapsule gives a pill or a circle at any size. |
finish |
GlassFinish? |
null |
The material for this surface. Null takes the theme's. |
materialize |
double |
1 |
0 to 1: how far the material has arrived, blur and bend first, tint last. At 0 nothing is drawn or captured. |
presence |
double |
1 |
0 to 1: how much of the shape exists. In a group a member buds from its neighbours; alone it narrows to a line. |
labelled |
bool |
true |
Text sits on this glass, so it may be dimmed to meet minLabelContrast. false for lenses and drops. |
fade |
GlassFade? |
null |
Fades the glass out across the surface, scroll-edge style. Ignored for members of a fusing group. |
ripple |
GlassRipple? |
null |
A touch wave for this surface. Null takes the theme's, which is none by default. |
child |
Widget? |
null |
Drawn on top of the glass. It receives the hits. |
GlassFade
| Constructor | Description |
|---|---|
GlassFade({required Offset begin, required Offset end}) |
Whole at begin, gone at end, in local logical pixels, with a smoothstep between. |
GlassFade.vertical({required double from, required double extent}) |
The vertical form. A negative extent fades upward. |
Constants
| Name | Value | Description |
|---|---|---|
kGlassCapsule |
BorderRadius.all(Radius.circular(1e9)) |
An over-large radius the engine clamps to half the short side: a stadium at any size. |
debugPaintGlassSurfaces |
false |
A top-level variable. In debug builds, outlines every surface in cyan. |