Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Material build method #147430

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
129 changes: 37 additions & 92 deletions packages/flutter/lib/src/material/material.dart
Expand Up @@ -431,38 +431,24 @@ class Material extends StatefulWidget {
class _MaterialState extends State<Material> with TickerProviderStateMixin {
final GlobalKey _inkFeatureRenderer = GlobalKey(debugLabel: 'ink renderer');

Color? _getBackgroundColor(BuildContext context) {
final ThemeData theme = Theme.of(context);
Color? color = widget.color;
if (color == null) {
switch (widget.type) {
case MaterialType.canvas:
color = theme.canvasColor;
case MaterialType.card:
color = theme.cardColor;
case MaterialType.button:
case MaterialType.circle:
case MaterialType.transparency:
break;
}
}
return color;
}

@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final Color? backgroundColor = _getBackgroundColor(context);
final Color modelShadowColor = widget.shadowColor ?? (theme.useMaterial3 ? theme.colorScheme.shadow : theme.shadowColor);
// If no shadow color is specified, use 0 for elevation in the model so a drop shadow won't be painted.
final double modelElevation = widget.elevation;
final Color? backgroundColor = widget.color ?? switch (widget.type) {
MaterialType.canvas => theme.canvasColor,
MaterialType.card => theme.cardColor,
MaterialType.button || MaterialType.circle || MaterialType.transparency => null,
};
final Color modelShadowColor = widget.shadowColor
?? (theme.useMaterial3 ? theme.colorScheme.shadow : theme.shadowColor);
assert(
backgroundColor != null || widget.type == MaterialType.transparency,
'If Material type is not MaterialType.transparency, a color must '
'either be passed in through the `color` property, or be defined '
'in the theme (ex. canvasColor != null if type is set to '
'MaterialType.canvas)',
);

Widget? contents = widget.child;
if (contents != null) {
contents = AnimatedDefaultTextStyle(
Expand All @@ -486,6 +472,10 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
),
);

ShapeBorder? shape = widget.borderRadius != null
? RoundedRectangleBorder(borderRadius: widget.borderRadius!)
: widget.shape;

// PhysicalModel has a temporary workaround for a performance issue that
// speeds up rectangular non transparent material (the workaround is to
// skip the call to ui.Canvas.saveLayer if the border radius is 0).
Expand All @@ -495,31 +485,39 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
// specified rectangles (e.g shape RoundedRectangleBorder with radius 0, but
// we choose not to as we want the change from the fast-path to the
// slow-path to be noticeable in the construction site of Material.
if (widget.type == MaterialType.canvas && widget.shape == null && widget.borderRadius == null) {
final Color color = Theme.of(context).useMaterial3
if (widget.type == MaterialType.canvas && shape == null) {
final Color color = theme.useMaterial3
? ElevationOverlay.applySurfaceTint(backgroundColor!, widget.surfaceTintColor, widget.elevation)
: ElevationOverlay.applyOverlay(context, backgroundColor!, widget.elevation);

return AnimatedPhysicalModel(
curve: Curves.fastOutSlowIn,
duration: widget.animationDuration,
clipBehavior: widget.clipBehavior,
elevation: modelElevation,
elevation: widget.elevation,
color: color,
shadowColor: modelShadowColor,
animateColor: false,
child: contents,
);
}

final ShapeBorder shape = _getShape();
shape ??= switch (widget.type) {
MaterialType.circle => const CircleBorder(),
MaterialType.canvas || MaterialType.transparency => const RoundedRectangleBorder(),
MaterialType.card || MaterialType.button => const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(2.0)),
),
};

if (widget.type == MaterialType.transparency) {
return _transparentInterior(
context: context,
shape: shape,
return ClipPath(
clipper: ShapeBorderClipper(
shape: shape,
textDirection: Directionality.maybeOf(context),
),
clipBehavior: widget.clipBehavior,
contents: contents,
child: _ShapeBorderPaint(shape: shape, child: contents),
);
}

Expand All @@ -536,56 +534,6 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
child: contents,
);
}

static Widget _transparentInterior({
required BuildContext context,
required ShapeBorder shape,
required Clip clipBehavior,
required Widget contents,
}) {
final _ShapeBorderPaint child = _ShapeBorderPaint(
shape: shape,
child: contents,
);
return ClipPath(
clipper: ShapeBorderClipper(
shape: shape,
textDirection: Directionality.maybeOf(context),
),
clipBehavior: clipBehavior,
child: child,
);
}

// Determines the shape for this Material.
//
// If a shape was specified, it will determine the shape.
// If a borderRadius was specified, the shape is a rounded
// rectangle.
// Otherwise, the shape is determined by the widget type as described in the
// Material class documentation.
ShapeBorder _getShape() {
if (widget.shape != null) {
return widget.shape!;
}
if (widget.borderRadius != null) {
return RoundedRectangleBorder(borderRadius: widget.borderRadius!);
}
switch (widget.type) {
case MaterialType.canvas:
case MaterialType.transparency:
return const RoundedRectangleBorder();

case MaterialType.card:
case MaterialType.button:
return RoundedRectangleBorder(
borderRadius: widget.borderRadius ?? kMaterialEdges[widget.type]!,
);

case MaterialType.circle:
return const CircleBorder();
}
}
}

class _RenderInkFeatures extends RenderProxyBox implements MaterialInkController {
Expand Down Expand Up @@ -899,7 +847,7 @@ class _MaterialInterior extends ImplicitlyAnimatedWidget {
final Color color;

/// The target shadow color.
final Color? shadowColor;
final Color shadowColor;

/// The target surface tint color.
final Color? surfaceTintColor;
Expand Down Expand Up @@ -930,13 +878,11 @@ class _MaterialInteriorState extends AnimatedWidgetBaseState<_MaterialInterior>
widget.elevation,
(dynamic value) => Tween<double>(begin: value as double),
) as Tween<double>?;
_shadowColor = widget.shadowColor != null
? visitor(
_shadowColor,
widget.shadowColor,
(dynamic value) => ColorTween(begin: value as Color),
) as ColorTween?
: null;
_shadowColor = visitor(
_shadowColor,
widget.shadowColor,
(dynamic value) => ColorTween(begin: value as Color),
) as ColorTween?;
_surfaceTintColor = widget.surfaceTintColor != null
? visitor(
_surfaceTintColor,
Expand All @@ -958,16 +904,15 @@ class _MaterialInteriorState extends AnimatedWidgetBaseState<_MaterialInterior>
final Color color = Theme.of(context).useMaterial3
? ElevationOverlay.applySurfaceTint(widget.color, _surfaceTintColor?.evaluate(animation), elevation)
: ElevationOverlay.applyOverlay(context, widget.color, elevation);
// If no shadow color is specified, use 0 for elevation in the model so a drop shadow won't be painted.
final double modelElevation = widget.shadowColor != null ? elevation : 0;
final Color shadowColor = _shadowColor?.evaluate(animation) ?? const Color(0x00000000);
final Color shadowColor = _shadowColor!.evaluate(animation)!;

return PhysicalShape(
clipper: ShapeBorderClipper(
shape: shape,
textDirection: Directionality.maybeOf(context),
),
clipBehavior: widget.clipBehavior,
elevation: modelElevation,
elevation: elevation,
color: color,
shadowColor: shadowColor,
child: _ShapeBorderPaint(
Expand Down