Surface elevation shadow colour on Material (#12881)
* Surface shadowColor from RenderPhysicalModel to Material * Fix typo in material_test * Add nullability info to documentation * Add support for animating elevation shadow color * Add shadowColor to Material's debugFillProperties() * Add missing default value for elevation in Material debugFillProperties() * Add missing non-null asserts for animate flags in AnimatedPhysicalModel * Add test for shadow color animating smoothly
This commit is contained in:
parent
91bd9bc4f8
commit
dfd1ffa7c5
@ -97,8 +97,8 @@ abstract class MaterialInkController {
|
||||
/// splashes and ink highlights) won't move to account for the new layout.
|
||||
///
|
||||
/// In general, the features of a [Material] should not change over time (e.g. a
|
||||
/// [Material] should not change its [color] or [type]). The one exception is
|
||||
/// the [elevation], changes to which will be animated.
|
||||
/// [Material] should not change its [color], [shadowColor] or [type]). The one
|
||||
/// exception is the [elevation], changes to which will be animated.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
@ -108,17 +108,19 @@ abstract class MaterialInkController {
|
||||
class Material extends StatefulWidget {
|
||||
/// Creates a piece of material.
|
||||
///
|
||||
/// The [type] and the [elevation] arguments must not be null.
|
||||
/// The [type], [elevation] and [shadowColor] arguments must not be null.
|
||||
const Material({
|
||||
Key key,
|
||||
this.type: MaterialType.canvas,
|
||||
this.elevation: 0.0,
|
||||
this.color,
|
||||
this.shadowColor: const Color(0xFF000000),
|
||||
this.textStyle,
|
||||
this.borderRadius,
|
||||
this.child,
|
||||
}) : assert(type != null),
|
||||
assert(elevation != null),
|
||||
assert(shadowColor != null),
|
||||
assert(!(identical(type, MaterialType.circle) && borderRadius != null)),
|
||||
super(key: key);
|
||||
|
||||
@ -148,6 +150,11 @@ class Material extends StatefulWidget {
|
||||
/// By default, the color is derived from the [type] of material.
|
||||
final Color color;
|
||||
|
||||
/// The color to paint the shadow below the material.
|
||||
///
|
||||
/// Defaults to fully opaque black.
|
||||
final Color shadowColor;
|
||||
|
||||
/// The typographical style to use for text within this material.
|
||||
final TextStyle textStyle;
|
||||
|
||||
@ -178,8 +185,9 @@ class Material extends StatefulWidget {
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder description) {
|
||||
super.debugFillProperties(description);
|
||||
description.add(new EnumProperty<MaterialType>('type', type));
|
||||
description.add(new DoubleProperty('elevation', elevation));
|
||||
description.add(new DoubleProperty('elevation', elevation, defaultValue: 0.0));
|
||||
description.add(new DiagnosticsProperty<Color>('color', color, defaultValue: null));
|
||||
description.add(new DiagnosticsProperty<Color>('shadowColor', shadowColor, defaultValue: const Color(0xFF000000)));
|
||||
textStyle?.debugFillProperties(description, prefix: 'textStyle.');
|
||||
description.add(new EnumProperty<BorderRadius>('borderRadius', borderRadius, defaultValue: null));
|
||||
}
|
||||
@ -238,6 +246,7 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
|
||||
shape: BoxShape.circle,
|
||||
elevation: widget.elevation,
|
||||
color: backgroundColor,
|
||||
shadowColor: widget.shadowColor,
|
||||
animateColor: false,
|
||||
child: contents,
|
||||
);
|
||||
@ -258,6 +267,7 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
|
||||
borderRadius: radius ?? BorderRadius.zero,
|
||||
elevation: widget.elevation,
|
||||
color: backgroundColor,
|
||||
shadowColor: widget.shadowColor,
|
||||
animateColor: false,
|
||||
child: contents,
|
||||
);
|
||||
|
@ -1298,20 +1298,23 @@ class RenderPhysicalModel extends _RenderCustomClip<RRect> {
|
||||
///
|
||||
/// The [color] is required.
|
||||
///
|
||||
/// The [shape], [elevation], and [color] must not be null.
|
||||
/// The [shape], [elevation], [color], and [shadowColor] must not be null.
|
||||
RenderPhysicalModel({
|
||||
RenderBox child,
|
||||
BoxShape shape: BoxShape.rectangle,
|
||||
BorderRadius borderRadius,
|
||||
double elevation: 0.0,
|
||||
@required Color color,
|
||||
Color shadowColor: const Color(0xFF000000),
|
||||
}) : assert(shape != null),
|
||||
assert(elevation != null),
|
||||
assert(color != null),
|
||||
assert(shadowColor != null),
|
||||
_shape = shape,
|
||||
_borderRadius = borderRadius,
|
||||
_elevation = elevation,
|
||||
_color = color,
|
||||
_shadowColor = shadowColor,
|
||||
super(child: child);
|
||||
|
||||
/// The shape of the layer.
|
||||
@ -1357,6 +1360,17 @@ class RenderPhysicalModel extends _RenderCustomClip<RRect> {
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
/// The shadow color.
|
||||
Color get shadowColor => _shadowColor;
|
||||
Color _shadowColor;
|
||||
set shadowColor(Color value) {
|
||||
assert(value != null);
|
||||
if (shadowColor == value)
|
||||
return;
|
||||
_shadowColor = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
/// The background color.
|
||||
Color get color => _color;
|
||||
Color _color;
|
||||
@ -1427,7 +1441,7 @@ class RenderPhysicalModel extends _RenderCustomClip<RRect> {
|
||||
);
|
||||
canvas.drawShadow(
|
||||
new Path()..addRRect(offsetClipRRect),
|
||||
const Color(0xFF000000),
|
||||
shadowColor,
|
||||
elevation,
|
||||
color.alpha != 0xFF,
|
||||
);
|
||||
|
@ -648,17 +648,19 @@ class PhysicalModel extends SingleChildRenderObjectWidget {
|
||||
///
|
||||
/// The [color] is required; physical things have a color.
|
||||
///
|
||||
/// The [shape], [elevation], and [color] must not be null.
|
||||
/// The [shape], [elevation], [color], and [shadowColor] must not be null.
|
||||
const PhysicalModel({
|
||||
Key key,
|
||||
this.shape: BoxShape.rectangle,
|
||||
this.borderRadius,
|
||||
this.elevation: 0.0,
|
||||
@required this.color,
|
||||
this.shadowColor: const Color(0xFF000000),
|
||||
Widget child,
|
||||
}) : assert(shape != null),
|
||||
assert(elevation != null),
|
||||
assert(color != null),
|
||||
assert(shadowColor != null),
|
||||
super(key: key, child: child);
|
||||
|
||||
/// The type of shape.
|
||||
@ -678,8 +680,11 @@ class PhysicalModel extends SingleChildRenderObjectWidget {
|
||||
/// The background color.
|
||||
final Color color;
|
||||
|
||||
/// The shadow color.
|
||||
final Color shadowColor;
|
||||
|
||||
@override
|
||||
RenderPhysicalModel createRenderObject(BuildContext context) => new RenderPhysicalModel(shape: shape, borderRadius: borderRadius, elevation: elevation, color: color);
|
||||
RenderPhysicalModel createRenderObject(BuildContext context) => new RenderPhysicalModel(shape: shape, borderRadius: borderRadius, elevation: elevation, color: color, shadowColor: shadowColor);
|
||||
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, RenderPhysicalModel renderObject) {
|
||||
@ -687,7 +692,8 @@ class PhysicalModel extends SingleChildRenderObjectWidget {
|
||||
..shape = shape
|
||||
..borderRadius = borderRadius
|
||||
..elevation = elevation
|
||||
..color = color;
|
||||
..color = color
|
||||
..shadowColor = shadowColor;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -697,6 +703,7 @@ class PhysicalModel extends SingleChildRenderObjectWidget {
|
||||
description.add(new DiagnosticsProperty<BorderRadius>('borderRadius', borderRadius));
|
||||
description.add(new DoubleProperty('elevation', elevation));
|
||||
description.add(new DiagnosticsProperty<Color>('color', color));
|
||||
description.add(new DiagnosticsProperty<Color>('shadowColor', shadowColor));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -931,10 +931,12 @@ class _AnimatedDefaultTextStyleState extends AnimatedWidgetBaseState<AnimatedDef
|
||||
class AnimatedPhysicalModel extends ImplicitlyAnimatedWidget {
|
||||
/// Creates a widget that animates the properties of a [PhysicalModel].
|
||||
///
|
||||
/// The [child], [shape], [borderRadius], [elevation], [color], [curve], and
|
||||
/// The [child], [shape], [borderRadius], [elevation], [color], [shadowColor], [curve], and
|
||||
/// [duration] arguments must not be null.
|
||||
///
|
||||
/// Animating [color] is optional and is controlled by the [animateColor] flag.
|
||||
///
|
||||
/// Animating [shadowColor] is optional and is controlled by the [animateShadowColor] flag.
|
||||
const AnimatedPhysicalModel({
|
||||
Key key,
|
||||
@required this.child,
|
||||
@ -943,6 +945,8 @@ class AnimatedPhysicalModel extends ImplicitlyAnimatedWidget {
|
||||
@required this.elevation,
|
||||
@required this.color,
|
||||
this.animateColor: true,
|
||||
@required this.shadowColor,
|
||||
this.animateShadowColor: true,
|
||||
Curve curve: Curves.linear,
|
||||
@required Duration duration,
|
||||
}) : assert(child != null),
|
||||
@ -950,6 +954,9 @@ class AnimatedPhysicalModel extends ImplicitlyAnimatedWidget {
|
||||
assert(borderRadius != null),
|
||||
assert(elevation != null),
|
||||
assert(color != null),
|
||||
assert(shadowColor != null),
|
||||
assert(animateColor != null),
|
||||
assert(animateShadowColor != null),
|
||||
super(key: key, curve: curve, duration: duration);
|
||||
|
||||
/// The widget below this widget in the tree.
|
||||
@ -972,6 +979,12 @@ class AnimatedPhysicalModel extends ImplicitlyAnimatedWidget {
|
||||
/// Whether the color should be animated.
|
||||
final bool animateColor;
|
||||
|
||||
/// The target shadow color.
|
||||
final Color shadowColor;
|
||||
|
||||
/// Whether the shadow color should be animated.
|
||||
final bool animateShadowColor;
|
||||
|
||||
@override
|
||||
_AnimatedPhysicalModelState createState() => new _AnimatedPhysicalModelState();
|
||||
|
||||
@ -983,6 +996,8 @@ class AnimatedPhysicalModel extends ImplicitlyAnimatedWidget {
|
||||
description.add(new DoubleProperty('elevation', elevation));
|
||||
description.add(new DiagnosticsProperty<Color>('color', color));
|
||||
description.add(new DiagnosticsProperty<bool>('animateColor', animateColor));
|
||||
description.add(new DiagnosticsProperty<Color>('shadowColor', shadowColor));
|
||||
description.add(new DiagnosticsProperty<bool>('animateShadowColor', animateShadowColor));
|
||||
}
|
||||
}
|
||||
|
||||
@ -990,12 +1005,14 @@ class _AnimatedPhysicalModelState extends AnimatedWidgetBaseState<AnimatedPhysic
|
||||
BorderRadiusTween _borderRadius;
|
||||
Tween<double> _elevation;
|
||||
ColorTween _color;
|
||||
ColorTween _shadowColor;
|
||||
|
||||
@override
|
||||
void forEachTween(TweenVisitor<dynamic> visitor) {
|
||||
_borderRadius = visitor(_borderRadius, widget.borderRadius, (dynamic value) => new BorderRadiusTween(begin: value));
|
||||
_elevation = visitor(_elevation, widget.elevation, (dynamic value) => new Tween<double>(begin: value));
|
||||
_color = visitor(_color, widget.color, (dynamic value) => new ColorTween(begin: value));
|
||||
_shadowColor = visitor(_shadowColor, widget.shadowColor, (dynamic value) => new ColorTween(begin: value));
|
||||
}
|
||||
|
||||
@override
|
||||
@ -1006,6 +1023,9 @@ class _AnimatedPhysicalModelState extends AnimatedWidgetBaseState<AnimatedPhysic
|
||||
borderRadius: _borderRadius.evaluate(animation),
|
||||
elevation: _elevation.evaluate(animation),
|
||||
color: widget.animateColor ? _color.evaluate(animation) : widget.color,
|
||||
shadowColor: widget.animateShadowColor
|
||||
? _shadowColor.evaluate(animation)
|
||||
: widget.shadowColor,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -14,13 +14,14 @@ class NotifyMaterial extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildMaterial(double elevation) {
|
||||
Widget buildMaterial(
|
||||
{double elevation: 0.0, Color shadowColor: const Color(0xFF00FF00)}) {
|
||||
return new Center(
|
||||
child: new SizedBox(
|
||||
height: 100.0,
|
||||
width: 100.0,
|
||||
child: new Material(
|
||||
color: const Color(0xFF00FF00),
|
||||
shadowColor: shadowColor,
|
||||
elevation: elevation,
|
||||
),
|
||||
),
|
||||
@ -48,7 +49,7 @@ class PaintRecorder extends CustomPainter {
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('LayoutChangedNotificaion test', (WidgetTester tester) async {
|
||||
testWidgets('LayoutChangedNotification test', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new Material(
|
||||
child: new NotifyMaterial(),
|
||||
@ -119,11 +120,11 @@ void main() {
|
||||
// This code verifies that the PhysicalModel's elevation animates over
|
||||
// a kThemeChangeDuration time interval.
|
||||
|
||||
await tester.pumpWidget(buildMaterial(0.0));
|
||||
await tester.pumpWidget(buildMaterial(elevation: 0.0));
|
||||
final RenderPhysicalModel modelA = getShadow(tester);
|
||||
expect(modelA.elevation, equals(0.0));
|
||||
|
||||
await tester.pumpWidget(buildMaterial(9.0));
|
||||
await tester.pumpWidget(buildMaterial(elevation: 9.0));
|
||||
final RenderPhysicalModel modelB = getShadow(tester);
|
||||
expect(modelB.elevation, equals(0.0));
|
||||
|
||||
@ -139,4 +140,35 @@ void main() {
|
||||
final RenderPhysicalModel modelE = getShadow(tester);
|
||||
expect(modelE.elevation, equals(9.0));
|
||||
});
|
||||
|
||||
testWidgets('Shadow colors animate smoothly', (WidgetTester tester) async {
|
||||
// This code verifies that the PhysicalModel's elevation animates over
|
||||
// a kThemeChangeDuration time interval.
|
||||
|
||||
await tester.pumpWidget(buildMaterial(shadowColor: const Color(0xFF00FF00)));
|
||||
final RenderPhysicalModel modelA = getShadow(tester);
|
||||
expect(modelA.shadowColor, equals(const Color(0xFF00FF00)));
|
||||
|
||||
await tester.pumpWidget(buildMaterial(shadowColor: const Color(0xFFFF0000)));
|
||||
final RenderPhysicalModel modelB = getShadow(tester);
|
||||
expect(modelB.shadowColor, equals(const Color(0xFF00FF00)));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 1));
|
||||
final RenderPhysicalModel modelC = getShadow(tester);
|
||||
expect(modelC.shadowColor.alpha, equals(0xFF));
|
||||
expect(modelC.shadowColor.red, closeTo(0x00, 1));
|
||||
expect(modelC.shadowColor.green, closeTo(0xFF, 1));
|
||||
expect(modelC.shadowColor.blue, equals(0x00));
|
||||
|
||||
await tester.pump(kThemeChangeDuration ~/ 2);
|
||||
final RenderPhysicalModel modelD = getShadow(tester);
|
||||
expect(modelD.shadowColor.alpha, equals(0xFF));
|
||||
expect(modelD.shadowColor.red, isNot(closeTo(0x00, 1)));
|
||||
expect(modelD.shadowColor.green, isNot(closeTo(0xFF, 1)));
|
||||
expect(modelD.shadowColor.blue, equals(0x00));
|
||||
|
||||
await tester.pump(kThemeChangeDuration);
|
||||
final RenderPhysicalModel modelE = getShadow(tester);
|
||||
expect(modelE.shadowColor, equals(const Color(0xFFFF0000)));
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user