Commit dfd1ffa7 authored by Sebastiano Poggi's avatar Sebastiano Poggi Committed by Yegor

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
parent 91bd9bc4
......@@ -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)));
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment