Commit b586a97a authored by Jason Simmons's avatar Jason Simmons Committed by GitHub

Enable physical model shadows (with animation) and change elevation to a double (#9756)

Fixes https://github.com/flutter/flutter/issues/9342
parent 2051669a
...@@ -66,7 +66,7 @@ class TileScrollLayout extends StatelessWidget { ...@@ -66,7 +66,7 @@ class TileScrollLayout extends StatelessWidget {
return new Padding( return new Padding(
padding:const EdgeInsets.all(5.0), padding:const EdgeInsets.all(5.0),
child: new Material( child: new Material(
elevation: index % 5 + 1, elevation: (index % 5 + 1).toDouble(),
color: Colors.white, color: Colors.white,
child: new IconBar(), child: new IconBar(),
), ),
......
...@@ -116,7 +116,7 @@ class _CalculatorState extends State<Calculator> { ...@@ -116,7 +116,7 @@ class _CalculatorState extends State<Calculator> {
return new Scaffold( return new Scaffold(
appBar: new AppBar( appBar: new AppBar(
backgroundColor: Theme.of(context).canvasColor, backgroundColor: Theme.of(context).canvasColor,
elevation: 0 elevation: 0.0
), ),
body: new Column( body: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
......
...@@ -127,7 +127,7 @@ class ColorsDemo extends StatelessWidget { ...@@ -127,7 +127,7 @@ class ColorsDemo extends StatelessWidget {
length: allPalettes.length, length: allPalettes.length,
child: new Scaffold( child: new Scaffold(
appBar: new AppBar( appBar: new AppBar(
elevation: 0, elevation: 0.0,
title: const Text('Colors'), title: const Text('Colors'),
bottom: new TabBar( bottom: new TabBar(
isScrollable: true, isScrollable: true,
......
...@@ -162,7 +162,7 @@ class _Heading extends StatelessWidget { ...@@ -162,7 +162,7 @@ class _Heading extends StatelessWidget {
height: (screenSize.height - kToolbarHeight) * 1.35, height: (screenSize.height - kToolbarHeight) * 1.35,
child: new Material( child: new Material(
type: MaterialType.card, type: MaterialType.card,
elevation: 0, elevation: 0.0,
child: new Padding( child: new Padding(
padding: const EdgeInsets.only(left: 16.0, top: 18.0, right: 16.0, bottom: 24.0), padding: const EdgeInsets.only(left: 16.0, top: 18.0, right: 16.0, bottom: 24.0),
child: new CustomMultiChildLayout( child: new CustomMultiChildLayout(
...@@ -302,7 +302,7 @@ class _OrderPageState extends State<OrderPage> { ...@@ -302,7 +302,7 @@ class _OrderPageState extends State<OrderPage> {
.where((Product product) => product != widget.order.product) .where((Product product) => product != widget.order.product)
.map((Product product) { .map((Product product) {
return new Card( return new Card(
elevation: 1, elevation: 1.0,
child: new Image.asset( child: new Image.asset(
product.imageAsset, product.imageAsset,
fit: BoxFit.contain, fit: BoxFit.contain,
......
...@@ -39,10 +39,10 @@ class ShrinePage extends StatefulWidget { ...@@ -39,10 +39,10 @@ class ShrinePage extends StatefulWidget {
/// Defines the Scaffold, AppBar, etc that the demo pages have in common. /// Defines the Scaffold, AppBar, etc that the demo pages have in common.
class ShrinePageState extends State<ShrinePage> { class ShrinePageState extends State<ShrinePage> {
int _appBarElevation = 0; double _appBarElevation = 0.0;
bool _handleScrollNotification(ScrollNotification notification) { bool _handleScrollNotification(ScrollNotification notification) {
final int elevation = notification.metrics.extentBefore <= 0.0 ? 0 : 1; final double elevation = notification.metrics.extentBefore <= 0.0 ? 0.0 : 1.0;
if (elevation != _appBarElevation) { if (elevation != _appBarElevation) {
setState(() { setState(() {
_appBarElevation = elevation; _appBarElevation = elevation;
......
...@@ -193,7 +193,7 @@ class StockHomeState extends State<StockHome> { ...@@ -193,7 +193,7 @@ class StockHomeState extends State<StockHome> {
Widget buildAppBar() { Widget buildAppBar() {
return new AppBar( return new AppBar(
elevation: 0, elevation: 0.0,
title: new Text(StockStrings.of(context).title()), title: new Text(StockStrings.of(context).title()),
actions: <Widget>[ actions: <Widget>[
new IconButton( new IconButton(
......
...@@ -157,7 +157,7 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget { ...@@ -157,7 +157,7 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
this.actions, this.actions,
this.flexibleSpace, this.flexibleSpace,
this.bottom, this.bottom,
this.elevation: 4, this.elevation: 4.0,
this.backgroundColor, this.backgroundColor,
this.brightness, this.brightness,
this.iconTheme, this.iconTheme,
...@@ -236,10 +236,8 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget { ...@@ -236,10 +236,8 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
/// The z-coordinate at which to place this app bar. /// The z-coordinate at which to place this app bar.
/// ///
/// The following elevations have defined shadows: 1, 2, 3, 4, 6, 8, 9, 12, 16, 24
///
/// Defaults to 4, the appropriate elevation for app bars. /// Defaults to 4, the appropriate elevation for app bars.
final int elevation; final double elevation;
/// The color to use for the app bar's material. Typically this should be set /// The color to use for the app bar's material. Typically this should be set
/// along with [brightness], [iconTheme], [textTheme]. /// along with [brightness], [iconTheme], [textTheme].
...@@ -566,7 +564,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { ...@@ -566,7 +564,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
final List<Widget> actions; final List<Widget> actions;
final Widget flexibleSpace; final Widget flexibleSpace;
final PreferredSizeWidget bottom; final PreferredSizeWidget bottom;
final int elevation; final double elevation;
final bool forceElevated; final bool forceElevated;
final Color backgroundColor; final Color backgroundColor;
final Brightness brightness; final Brightness brightness;
...@@ -607,7 +605,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { ...@@ -607,7 +605,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
actions: actions, actions: actions,
flexibleSpace: flexibleSpace, flexibleSpace: flexibleSpace,
bottom: bottom, bottom: bottom,
elevation: forceElevated || overlapsContent || (pinned && shrinkOffset > maxExtent - minExtent) ? elevation ?? 4 : 0, elevation: forceElevated || overlapsContent || (pinned && shrinkOffset > maxExtent - minExtent) ? elevation ?? 4.0 : 0.0,
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
brightness: brightness, brightness: brightness,
iconTheme: iconTheme, iconTheme: iconTheme,
...@@ -774,15 +772,13 @@ class SliverAppBar extends StatefulWidget { ...@@ -774,15 +772,13 @@ class SliverAppBar extends StatefulWidget {
/// The z-coordinate at which to place this app bar when it is above other /// The z-coordinate at which to place this app bar when it is above other
/// content. /// content.
/// ///
/// The following elevations have defined shadows: 1, 2, 3, 4, 6, 8, 9, 12, 16, 24
///
/// Defaults to 4, the appropriate elevation for app bars. /// Defaults to 4, the appropriate elevation for app bars.
/// ///
/// If [forceElevated] is false, the elevation is ignored when the app bar has /// If [forceElevated] is false, the elevation is ignored when the app bar has
/// no content underneath it. For example, if the app bar is [pinned] but no /// no content underneath it. For example, if the app bar is [pinned] but no
/// content is scrolled under it, or if it scrolls with the content, then no /// content is scrolled under it, or if it scrolls with the content, then no
/// shadow is drawn, regardless of the value of [elevation]. /// shadow is drawn, regardless of the value of [elevation].
final int elevation; final double elevation;
/// Whether to show the shadow appropriate for the [elevation] even if the /// Whether to show the shadow appropriate for the [elevation] even if the
/// content is not scrolled under the [AppBar]. /// content is not scrolled under the [AppBar].
......
...@@ -448,7 +448,7 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr ...@@ -448,7 +448,7 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
children: <Widget>[ children: <Widget>[
new Positioned.fill( new Positioned.fill(
child: new Material( // Casts shadow. child: new Material( // Casts shadow.
elevation: 8, elevation: 8.0,
color: widget.type == BottomNavigationBarType.shifting ? _backgroundColor : null color: widget.type == BottomNavigationBarType.shifting ? _backgroundColor : null
) )
), ),
......
...@@ -192,17 +192,13 @@ class MaterialButton extends StatefulWidget { ...@@ -192,17 +192,13 @@ class MaterialButton extends StatefulWidget {
/// The z-coordinate at which to place this button. /// The z-coordinate at which to place this button.
/// ///
/// The following elevations have defined shadows: 1, 2, 3, 4, 6, 8, 9, 12, 16, 24
///
/// Defaults to 0. /// Defaults to 0.
final int elevation; final double elevation;
/// The z-coordinate at which to place this button when highlighted. /// The z-coordinate at which to place this button when highlighted.
/// ///
/// The following elevations have defined shadows: 1, 2, 3, 4, 6, 8, 9, 12, 16, 24
///
/// Defaults to 0. /// Defaults to 0.
final int highlightElevation; final double highlightElevation;
/// The smallest horizontal extent that the button will occupy. /// The smallest horizontal extent that the button will occupy.
/// ///
...@@ -290,7 +286,7 @@ class _MaterialButtonState extends State<MaterialButton> { ...@@ -290,7 +286,7 @@ class _MaterialButtonState extends State<MaterialButton> {
final TextStyle style = theme.textTheme.button.copyWith(color: textColor); final TextStyle style = theme.textTheme.button.copyWith(color: textColor);
final ButtonTheme buttonTheme = ButtonTheme.of(context); final ButtonTheme buttonTheme = ButtonTheme.of(context);
final double height = widget.height ?? buttonTheme.height; final double height = widget.height ?? buttonTheme.height;
final int elevation = (_highlight ? widget.highlightElevation : widget.elevation) ?? 0; final double elevation = (_highlight ? widget.highlightElevation : widget.elevation) ?? 0.0;
final bool hasColorOrElevation = (widget.color != null || elevation > 0); final bool hasColorOrElevation = (widget.color != null || elevation > 0);
Widget contents = IconTheme.merge( Widget contents = IconTheme.merge(
data: new IconThemeData( data: new IconThemeData(
......
...@@ -23,7 +23,7 @@ class Card extends StatelessWidget { ...@@ -23,7 +23,7 @@ class Card extends StatelessWidget {
const Card({ const Card({
Key key, Key key,
this.color, this.color,
this.elevation: 2, this.elevation: 2.0,
this.child this.child
}) : super(key: key); }) : super(key: key);
...@@ -35,10 +35,8 @@ class Card extends StatelessWidget { ...@@ -35,10 +35,8 @@ class Card extends StatelessWidget {
/// The z-coordinate at which to place this card. /// The z-coordinate at which to place this card.
/// ///
/// The following elevations have defined shadows: 1, 2, 3, 4, 6, 8, 9, 12, 16, 24
///
/// Defaults to 2, the appropriate elevation for cards. /// Defaults to 2, the appropriate elevation for cards.
final int elevation; final double elevation;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
......
...@@ -51,7 +51,7 @@ class Dialog extends StatelessWidget { ...@@ -51,7 +51,7 @@ class Dialog extends StatelessWidget {
child: new ConstrainedBox( child: new ConstrainedBox(
constraints: const BoxConstraints(minWidth: 280.0), constraints: const BoxConstraints(minWidth: 280.0),
child: new Material( child: new Material(
elevation: 24, elevation: 24.0,
color: _getColor(context), color: _getColor(context),
type: MaterialType.card, type: MaterialType.card,
child: child child: child
......
...@@ -68,16 +68,14 @@ class Drawer extends StatelessWidget { ...@@ -68,16 +68,14 @@ class Drawer extends StatelessWidget {
/// Typically used in the [Scaffold.drawer] property. /// Typically used in the [Scaffold.drawer] property.
const Drawer({ const Drawer({
Key key, Key key,
this.elevation: 16, this.elevation: 16.0,
this.child this.child
}) : super(key: key); }) : super(key: key);
/// The z-coordinate at which to place this drawer. /// The z-coordinate at which to place this drawer.
/// ///
/// The following elevations have defined shadows: 1, 2, 3, 4, 6, 8, 9, 12, 16, 24
///
/// Defaults to 16, the appropriate elevation for drawers. /// Defaults to 16, the appropriate elevation for drawers.
final int elevation; final double elevation;
/// The widget below this widget in the tree. /// The widget below this widget in the tree.
/// ///
......
...@@ -48,8 +48,8 @@ class FloatingActionButton extends StatefulWidget { ...@@ -48,8 +48,8 @@ class FloatingActionButton extends StatefulWidget {
this.tooltip, this.tooltip,
this.backgroundColor, this.backgroundColor,
this.heroTag, this.heroTag,
this.elevation: 6, this.elevation: 6.0,
this.highlightElevation: 12, this.highlightElevation: 12.0,
@required this.onPressed, @required this.onPressed,
this.mini: false this.mini: false
}) : super(key: key); }) : super(key: key);
...@@ -80,18 +80,14 @@ class FloatingActionButton extends StatefulWidget { ...@@ -80,18 +80,14 @@ class FloatingActionButton extends StatefulWidget {
/// The z-coordinate at which to place this button. /// The z-coordinate at which to place this button.
/// ///
/// The following elevations have defined shadows: 1, 2, 3, 4, 6, 8, 9, 12, 16, 24
///
/// Defaults to 6, the appropriate elevation for floating action buttons. /// Defaults to 6, the appropriate elevation for floating action buttons.
final int elevation; final double elevation;
/// The z-coordinate at which to place this button when the user is touching the button. /// The z-coordinate at which to place this button when the user is touching the button.
/// ///
/// The following elevations have defined shadows: 1, 2, 3, 4, 6, 8, 9, 12, 16, 24
///
/// Defaults to 12, the appropriate elevation for floating action buttons /// Defaults to 12, the appropriate elevation for floating action buttons
/// while they are being touched. /// while they are being touched.
final int highlightElevation; final double highlightElevation;
/// Controls the size of this button. /// Controls the size of this button.
/// ///
......
...@@ -7,7 +7,6 @@ import 'package:flutter/rendering.dart'; ...@@ -7,7 +7,6 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'constants.dart'; import 'constants.dart';
import 'shadows.dart';
import 'theme.dart'; import 'theme.dart';
/// Signature for the callback used by ink effects to obtain the rectangle for the effect. /// Signature for the callback used by ink effects to obtain the rectangle for the effect.
...@@ -113,7 +112,7 @@ class Material extends StatefulWidget { ...@@ -113,7 +112,7 @@ class Material extends StatefulWidget {
const Material({ const Material({
Key key, Key key,
this.type: MaterialType.canvas, this.type: MaterialType.canvas,
this.elevation: 0, this.elevation: 0.0,
this.color, this.color,
this.textStyle, this.textStyle,
this.borderRadius, this.borderRadius,
...@@ -133,10 +132,8 @@ class Material extends StatefulWidget { ...@@ -133,10 +132,8 @@ class Material extends StatefulWidget {
/// The z-coordinate at which to place this material. /// The z-coordinate at which to place this material.
/// ///
/// The following elevations have defined shadows: 1, 2, 3, 4, 6, 8, 9, 12, 16, 24
///
/// Defaults to 0. /// Defaults to 0.
final int elevation; final double elevation;
/// The color to paint the material. /// The color to paint the material.
/// ///
...@@ -189,9 +186,6 @@ class Material extends StatefulWidget { ...@@ -189,9 +186,6 @@ class Material extends StatefulWidget {
/// The default radius of an ink splash in logical pixels. /// The default radius of an ink splash in logical pixels.
static const double defaultSplashRadius = 35.0; static const double defaultSplashRadius = 35.0;
/// Temporary flag used to enable the PhysicalModel shadow implementation.
static bool debugEnablePhysicalModel = false;
} }
class _MaterialState extends State<Material> with TickerProviderStateMixin { class _MaterialState extends State<Material> with TickerProviderStateMixin {
...@@ -237,12 +231,14 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin { ...@@ -237,12 +231,14 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
) )
); );
if (Material.debugEnablePhysicalModel) {
if (widget.type == MaterialType.circle) { if (widget.type == MaterialType.circle) {
contents = new PhysicalModel( contents = new AnimatedPhysicalModel(
curve: Curves.fastOutSlowIn,
duration: kThemeChangeDuration,
shape: BoxShape.circle, shape: BoxShape.circle,
elevation: widget.elevation, elevation: widget.elevation,
color: backgroundColor, color: backgroundColor,
animateColor: false,
child: contents, child: contents,
); );
} else if (widget.type == MaterialType.transparency) { } else if (widget.type == MaterialType.transparency) {
...@@ -255,45 +251,18 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin { ...@@ -255,45 +251,18 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
); );
} }
} else { } else {
contents = new PhysicalModel( contents = new AnimatedPhysicalModel(
curve: Curves.fastOutSlowIn,
duration: kThemeChangeDuration,
shape: BoxShape.rectangle, shape: BoxShape.rectangle,
borderRadius: radius ?? BorderRadius.zero, borderRadius: radius ?? BorderRadius.zero,
elevation: widget.elevation, elevation: widget.elevation,
color: backgroundColor, color: backgroundColor,
animateColor: false,
child: contents, child: contents,
); );
} }
} else {
if (widget.type == MaterialType.circle) {
contents = new ClipOval(child: contents);
} else if (kMaterialEdges[widget.type] != null) {
contents = new ClipRRect(
borderRadius: radius,
child: contents
);
}
}
if (widget.type != MaterialType.transparency) {
contents = new AnimatedContainer(
curve: Curves.fastOutSlowIn,
duration: kThemeChangeDuration,
decoration: new BoxDecoration(
borderRadius: radius,
boxShadow: widget.elevation == 0 || Material.debugEnablePhysicalModel ?
null : kElevationToShadow[widget.elevation],
shape: widget.type == MaterialType.circle ? BoxShape.circle : BoxShape.rectangle
),
child: new Container(
decoration: new BoxDecoration(
borderRadius: radius,
color: backgroundColor,
shape: widget.type == MaterialType.circle ? BoxShape.circle : BoxShape.rectangle
),
child: contents
)
);
}
return contents; return contents;
} }
} }
......
...@@ -379,7 +379,7 @@ class _PopupMenuRoute<T> extends PopupRoute<T> { ...@@ -379,7 +379,7 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
final RelativeRect position; final RelativeRect position;
final List<PopupMenuEntry<T>> items; final List<PopupMenuEntry<T>> items;
final dynamic initialValue; final dynamic initialValue;
final int elevation; final double elevation;
final ThemeData theme; final ThemeData theme;
@override @override
...@@ -433,15 +433,14 @@ class _PopupMenuRoute<T> extends PopupRoute<T> { ...@@ -433,15 +433,14 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
/// a [Theme] to use for the menu. /// a [Theme] to use for the menu.
/// ///
/// The `elevation` argument specifies the z-coordinate at which to place the /// The `elevation` argument specifies the z-coordinate at which to place the
/// menu. The following elevations have defined shadows: 1, 2, 3, 4, 6, 8, 9, /// menu. The elevation defaults to 8, the appropriate elevation for popup
/// 12, 16, 24. The elevation defaults to 8, the appropriate elevation for popup
/// menus. /// menus.
Future<T> showMenu<T>({ Future<T> showMenu<T>({
@required BuildContext context, @required BuildContext context,
RelativeRect position, RelativeRect position,
@required List<PopupMenuEntry<T>> items, @required List<PopupMenuEntry<T>> items,
T initialValue, T initialValue,
int elevation: 8 double elevation: 8.0
}) { }) {
assert(context != null); assert(context != null);
assert(items != null && items.isNotEmpty); assert(items != null && items.isNotEmpty);
...@@ -481,7 +480,7 @@ class PopupMenuButton<T> extends StatefulWidget { ...@@ -481,7 +480,7 @@ class PopupMenuButton<T> extends StatefulWidget {
this.initialValue, this.initialValue,
this.onSelected, this.onSelected,
this.tooltip: 'Show menu', this.tooltip: 'Show menu',
this.elevation: 8, this.elevation: 8.0,
this.padding: const EdgeInsets.all(8.0), this.padding: const EdgeInsets.all(8.0),
this.child this.child
}) : assert(itemBuilder != null), }) : assert(itemBuilder != null),
...@@ -503,9 +502,7 @@ class PopupMenuButton<T> extends StatefulWidget { ...@@ -503,9 +502,7 @@ class PopupMenuButton<T> extends StatefulWidget {
final String tooltip; final String tooltip;
/// The z-coordinate at which to place the menu when open. /// The z-coordinate at which to place the menu when open.
/// final double elevation;
/// The following elevations have defined shadows: 1, 2, 3, 4, 6, 8, 9, 12, 16, 24
final int elevation;
/// Matches IconButton's 8 dps padding by default. In some cases, notably where /// Matches IconButton's 8 dps padding by default. In some cases, notably where
/// this button appears as the trailing element of a list item, it's useful to be able /// this button appears as the trailing element of a list item, it's useful to be able
......
...@@ -480,7 +480,7 @@ class _RefreshProgressIndicatorState extends _CircularProgressIndicatorState { ...@@ -480,7 +480,7 @@ class _RefreshProgressIndicatorState extends _CircularProgressIndicatorState {
child: new Material( child: new Material(
type: MaterialType.circle, type: MaterialType.circle,
color: widget.backgroundColor ?? Theme.of(context).canvasColor, color: widget.backgroundColor ?? Theme.of(context).canvasColor,
elevation: 2, elevation: 2.0,
child: new Padding( child: new Padding(
padding: const EdgeInsets.all(12.0), padding: const EdgeInsets.all(12.0),
child: new CustomPaint( child: new CustomPaint(
......
...@@ -43,9 +43,9 @@ class RaisedButton extends StatelessWidget { ...@@ -43,9 +43,9 @@ class RaisedButton extends StatelessWidget {
this.highlightColor, this.highlightColor,
this.splashColor, this.splashColor,
this.disabledColor, this.disabledColor,
this.elevation: 2, this.elevation: 2.0,
this.highlightElevation: 8, this.highlightElevation: 8.0,
this.disabledElevation: 0, this.disabledElevation: 0.0,
this.colorBrightness, this.colorBrightness,
this.child this.child
}) : super(key: key); }) : super(key: key);
...@@ -97,25 +97,19 @@ class RaisedButton extends StatelessWidget { ...@@ -97,25 +97,19 @@ class RaisedButton extends StatelessWidget {
/// The z-coordinate at which to place this button. /// The z-coordinate at which to place this button.
/// ///
/// The following elevations have defined shadows: 1, 2, 3, 4, 6, 8, 9, 12, 16, 24
///
/// Defaults to 2, the appropriate elevation for raised buttons. /// Defaults to 2, the appropriate elevation for raised buttons.
final int elevation; final double elevation;
/// The z-coordinate at which to place this button when highlighted. /// The z-coordinate at which to place this button when highlighted.
/// ///
/// The following elevations have defined shadows: 1, 2, 3, 4, 6, 8, 9, 12, 16, 24
///
/// Defaults to 8, the appropriate elevation for raised buttons while they are /// Defaults to 8, the appropriate elevation for raised buttons while they are
/// being touched. /// being touched.
final int highlightElevation; final double highlightElevation;
/// The z-coordinate at which to place this button when disabled. /// The z-coordinate at which to place this button when disabled.
/// ///
/// The following elevations have defined shadows: 1, 2, 3, 4, 6, 8, 9, 12, 16, 24
///
/// Defaults to 0, the appropriate elevation for disabled raised buttons. /// Defaults to 0, the appropriate elevation for disabled raised buttons.
final int disabledElevation; final double disabledElevation;
/// The theme brightness to use for this button. /// The theme brightness to use for this button.
/// ///
......
...@@ -237,7 +237,7 @@ class SnackBar extends StatelessWidget { ...@@ -237,7 +237,7 @@ class SnackBar extends StatelessWidget {
Scaffold.of(context).removeCurrentSnackBar(reason: SnackBarClosedReason.swipe); Scaffold.of(context).removeCurrentSnackBar(reason: SnackBarClosedReason.swipe);
}, },
child: new Material( child: new Material(
elevation: 6, elevation: 6.0,
color: backgroundColor ?? _kSnackBackground, color: backgroundColor ?? _kSnackBackground,
child: new Theme( child: new Theme(
data: darkTheme, data: darkTheme,
......
...@@ -591,7 +591,7 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin { ...@@ -591,7 +591,7 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
return new Column( return new Column(
children: <Widget>[ children: <Widget>[
new Material( new Material(
elevation: 2, elevation: 2.0,
child: new Container( child: new Container(
margin: const EdgeInsets.symmetric(horizontal: 24.0), margin: const EdgeInsets.symmetric(horizontal: 24.0),
child: new Row( child: new Row(
......
...@@ -41,7 +41,7 @@ class _TextSelectionToolbar extends StatelessWidget { ...@@ -41,7 +41,7 @@ class _TextSelectionToolbar extends StatelessWidget {
} }
return new Material( return new Material(
elevation: 1, elevation: 1.0,
child: new Container( child: new Container(
height: 44.0, height: 44.0,
child: new Row(mainAxisSize: MainAxisSize.min, children: items) child: new Row(mainAxisSize: MainAxisSize.min, children: items)
......
...@@ -645,7 +645,7 @@ class PhysicalModelLayer extends ContainerLayer { ...@@ -645,7 +645,7 @@ class PhysicalModelLayer extends ContainerLayer {
/// ///
/// The scene must be explicitly recomposited after this property is changed /// The scene must be explicitly recomposited after this property is changed
/// (as described at [Layer]). /// (as described at [Layer]).
int elevation; double elevation;
/// The background color. /// The background color.
/// ///
...@@ -657,7 +657,7 @@ class PhysicalModelLayer extends ContainerLayer { ...@@ -657,7 +657,7 @@ class PhysicalModelLayer extends ContainerLayer {
void addToScene(ui.SceneBuilder builder, Offset layerOffset) { void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
builder.pushPhysicalModel( builder.pushPhysicalModel(
rrect: clipRRect.shift(layerOffset), rrect: clipRRect.shift(layerOffset),
elevation: elevation.toDouble(), elevation: elevation,
color: color, color: color,
); );
addChildrenToScene(builder, layerOffset); addChildrenToScene(builder, layerOffset);
......
...@@ -456,7 +456,7 @@ class PaintingContext { ...@@ -456,7 +456,7 @@ class PaintingContext {
/// * `color` is the background color. /// * `color` is the background color.
/// * `painter` is a callback that will paint with the `clipRRect` applied. This /// * `painter` is a callback that will paint with the `clipRRect` applied. This
/// function calls the `painter` synchronously. /// function calls the `painter` synchronously.
void pushPhysicalModel(bool needsCompositing, Offset offset, Rect bounds, RRect clipRRect, int elevation, Color color, PaintingContextCallback painter) { void pushPhysicalModel(bool needsCompositing, Offset offset, Rect bounds, RRect clipRRect, double elevation, Color color, PaintingContextCallback painter) {
final Rect offsetBounds = bounds.shift(offset); final Rect offsetBounds = bounds.shift(offset);
final RRect offsetClipRRect = clipRRect.shift(offset); final RRect offsetClipRRect = clipRRect.shift(offset);
if (needsCompositing) { if (needsCompositing) {
......
...@@ -1198,7 +1198,7 @@ class RenderPhysicalModel extends _RenderCustomClip<RRect> { ...@@ -1198,7 +1198,7 @@ class RenderPhysicalModel extends _RenderCustomClip<RRect> {
RenderBox child, RenderBox child,
BoxShape shape, BoxShape shape,
BorderRadius borderRadius: BorderRadius.zero, BorderRadius borderRadius: BorderRadius.zero,
int elevation, double elevation,
Color color, Color color,
}) : _shape = shape, }) : _shape = shape,
_borderRadius = borderRadius, _borderRadius = borderRadius,
...@@ -1235,9 +1235,9 @@ class RenderPhysicalModel extends _RenderCustomClip<RRect> { ...@@ -1235,9 +1235,9 @@ class RenderPhysicalModel extends _RenderCustomClip<RRect> {
} }
/// The z-coordinate at which to place this material. /// The z-coordinate at which to place this material.
int get elevation => _elevation; double get elevation => _elevation;
int _elevation; double _elevation;
set elevation(int value) { set elevation(double value) {
assert(value != null); assert(value != null);
if (_elevation == value) if (_elevation == value)
return; return;
......
...@@ -472,7 +472,7 @@ class PhysicalModel extends SingleChildRenderObjectWidget { ...@@ -472,7 +472,7 @@ class PhysicalModel extends SingleChildRenderObjectWidget {
final BorderRadius borderRadius; final BorderRadius borderRadius;
/// The z-coordinate at which to place this physical object. /// The z-coordinate at which to place this physical object.
final int elevation; final double elevation;
/// The background color. /// The background color.
final Color color; final Color color;
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/animation.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:vector_math/vector_math_64.dart'; import 'package:vector_math/vector_math_64.dart';
...@@ -44,6 +45,17 @@ class EdgeInsetsTween extends Tween<EdgeInsets> { ...@@ -44,6 +45,17 @@ class EdgeInsetsTween extends Tween<EdgeInsets> {
EdgeInsets lerp(double t) => EdgeInsets.lerp(begin, end, t); EdgeInsets lerp(double t) => EdgeInsets.lerp(begin, end, t);
} }
/// An interpolation between two [BorderRadius]s.
class BorderRadiusTween extends Tween<BorderRadius> {
/// Creates a border radius tween.
///
/// The [begin] and [end] arguments must not be null.
BorderRadiusTween({ BorderRadius begin, BorderRadius end }) : super(begin: begin, end: end);
@override
BorderRadius lerp(double t) => BorderRadius.lerp(begin, end, t);
}
/// An interpolation between two [Matrix4]s. /// An interpolation between two [Matrix4]s.
/// ///
/// Currently this class works only for translations. /// Currently this class works only for translations.
...@@ -643,3 +655,86 @@ class _AnimatedDefaultTextStyleState extends AnimatedWidgetBaseState<AnimatedDef ...@@ -643,3 +655,86 @@ class _AnimatedDefaultTextStyleState extends AnimatedWidgetBaseState<AnimatedDef
); );
} }
} }
/// Animated version of [PhysicalModel].
class AnimatedPhysicalModel extends ImplicitlyAnimatedWidget {
/// Creates a widget that animates the properties of a [PhysicalModel].
///
/// The [child], [shape], [borderRadius], [elevation], [color], [curve], and
/// [duration] arguments must not be null.
///
/// Animating [color] is optional and is controlled by the [animateColor] flag.
const AnimatedPhysicalModel({
Key key,
@required this.child,
@required this.shape,
this.borderRadius: BorderRadius.zero,
@required this.elevation,
@required this.color,
this.animateColor: true,
Curve curve: Curves.linear,
@required Duration duration,
}) : assert(child != null),
assert(shape != null),
assert(borderRadius != null),
assert(elevation != null),
assert(color != null),
super(key: key, curve: curve, duration: duration);
/// The widget below this widget in the tree.
final Widget child;
/// The type of shape.
///
/// This property is not animated.
final BoxShape shape;
/// The target border radius of the rounded corners for a rectangle shape.
final BorderRadius borderRadius;
/// The target z-coordinate at which to place this physical object.
final double elevation;
/// The target background color.
final Color color;
/// Whether the color should be animated.
final bool animateColor;
@override
_AnimatedPhysicalModelState createState() => new _AnimatedPhysicalModelState();
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('shape: $shape');
description.add('borderRadius: $borderRadius');
description.add('elevation: $elevation');
description.add('color: $color');
description.add('animateColor: $animateColor');
}
}
class _AnimatedPhysicalModelState extends AnimatedWidgetBaseState<AnimatedPhysicalModel> {
BorderRadiusTween _borderRadius;
Tween<double> _elevation;
ColorTween _color;
@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));
}
@override
Widget build(BuildContext context) {
return new PhysicalModel(
child: widget.child,
shape: widget.shape,
borderRadius: _borderRadius.evaluate(animation),
elevation: _elevation.evaluate(animation),
color: widget.animateColor ? _color.evaluate(animation) : widget.color,
);
}
}
...@@ -14,7 +14,7 @@ class NotifyMaterial extends StatelessWidget { ...@@ -14,7 +14,7 @@ class NotifyMaterial extends StatelessWidget {
} }
} }
Widget buildMaterial(int elevation) { Widget buildMaterial(double elevation) {
return new Center( return new Center(
child: new SizedBox( child: new SizedBox(
height: 100.0, height: 100.0,
...@@ -27,10 +27,8 @@ Widget buildMaterial(int elevation) { ...@@ -27,10 +27,8 @@ Widget buildMaterial(int elevation) {
); );
} }
List<BoxShadow> getShadow(WidgetTester tester) { RenderPhysicalModel getShadow(WidgetTester tester) {
final RenderDecoratedBox box = tester.renderObject(find.byType(DecoratedBox).first); return tester.renderObject(find.byType(PhysicalModel));
final BoxDecoration decoration = box.decoration;
return decoration.boxShadow;
} }
class PaintRecorder extends CustomPainter { class PaintRecorder extends CustomPainter {
...@@ -115,67 +113,27 @@ void main() { ...@@ -115,67 +113,27 @@ void main() {
}); });
testWidgets('Shadows animate smoothly', (WidgetTester tester) async { testWidgets('Shadows animate smoothly', (WidgetTester tester) async {
await tester.pumpWidget(buildMaterial(0)); // This code verifies that the PhysicalModel's elevation animates over
final List<BoxShadow> shadowA = getShadow(tester); // a kThemeChangeDuration time interval.
await tester.pumpWidget(buildMaterial(9)); await tester.pumpWidget(buildMaterial(0.0));
final List<BoxShadow> shadowB = getShadow(tester); final RenderPhysicalModel modelA = getShadow(tester);
expect(modelA.elevation, equals(0.0));
await tester.pumpWidget(buildMaterial(9.0));
final RenderPhysicalModel modelB = getShadow(tester);
expect(modelB.elevation, equals(0.0));
await tester.pump(const Duration(milliseconds: 1)); await tester.pump(const Duration(milliseconds: 1));
final List<BoxShadow> shadowC = getShadow(tester); final RenderPhysicalModel modelC = getShadow(tester);
expect(modelC.elevation, closeTo(0.0, 0.001));
await tester.pump(kThemeChangeDuration ~/ 2); await tester.pump(kThemeChangeDuration ~/ 2);
final List<BoxShadow> shadowD = getShadow(tester); final RenderPhysicalModel modelD = getShadow(tester);
expect(modelD.elevation, isNot(closeTo(0.0, 0.001)));
await tester.pump(kThemeChangeDuration); await tester.pump(kThemeChangeDuration);
final List<BoxShadow> shadowE = getShadow(tester); final RenderPhysicalModel modelE = getShadow(tester);
expect(modelE.elevation, equals(9.0));
// This code verifies the following:
// 1. When the elevation is zero, there's no shadow.
// 2. When the elevation isn't zero, there's three shadows.
// 3. When the elevation changes from 0 to 9, one millisecond later, the
// shadows are still more or less indistinguishable from zero.
// 4. Have a kThemeChangeDuration later, they are distinguishable form
// zero.
// 5. ...but still distinguishable from the actual 9 elevation.
// 6. After kThemeChangeDuration, the shadows are exactly the elevation 9
// shadows.
// The point being to verify that the shadows animate, and do so
// continually, not in discrete increments (e.g. not jumping from elevation
// 0 to 1 to 2 to 3 and so forth).
// TODO(ianh): Port this test when we turn the physical model back on.
// 1
expect(shadowA, isNull);
expect(shadowB, isNull);
// 2
expect(shadowC, hasLength(3));
// 3
expect(shadowC[0].offset.dy, closeTo(0.0, 0.001));
expect(shadowC[1].offset.dy, closeTo(0.0, 0.001));
expect(shadowC[2].offset.dy, closeTo(0.0, 0.001));
expect(shadowC[0].blurRadius, closeTo(0.0, 0.001));
expect(shadowC[1].blurRadius, closeTo(0.0, 0.001));
expect(shadowC[2].blurRadius, closeTo(0.0, 0.001));
expect(shadowC[0].spreadRadius, closeTo(0.0, 0.001));
expect(shadowC[1].spreadRadius, closeTo(0.0, 0.001));
expect(shadowC[2].spreadRadius, closeTo(0.0, 0.001));
// 4
expect(shadowD[0].offset.dy, isNot(closeTo(0.0, 0.001)));
expect(shadowD[1].offset.dy, isNot(closeTo(0.0, 0.001)));
expect(shadowD[2].offset.dy, isNot(closeTo(0.0, 0.001)));
expect(shadowD[0].blurRadius, isNot(closeTo(0.0, 0.001)));
expect(shadowD[1].blurRadius, isNot(closeTo(0.0, 0.001)));
expect(shadowD[2].blurRadius, isNot(closeTo(0.0, 0.001)));
expect(shadowD[0].spreadRadius, isNot(closeTo(0.0, 0.001)));
expect(shadowD[1].spreadRadius, isNot(closeTo(0.0, 0.001)));
expect(shadowD[2].spreadRadius, isNot(closeTo(0.0, 0.001)));
// 5
expect(shadowD[0], isNot(shadowE[0]));
expect(shadowD[1], isNot(shadowE[1]));
expect(shadowD[2], isNot(shadowE[2]));
// 6
expect(shadowE, kElevationToShadow[9]);
}); });
} }
...@@ -51,11 +51,14 @@ void matches(BorderRadius borderRadius, RadiusType top, RadiusType bottom) { ...@@ -51,11 +51,14 @@ void matches(BorderRadius borderRadius, RadiusType top, RadiusType bottom) {
} }
} }
// Returns the border radius decoration of an item within a MergeableMaterial.
// This depends on the exact structure of objects built by the Material and
// MergeableMaterial widgets.
BorderRadius getBorderRadius(WidgetTester tester, int index) { BorderRadius getBorderRadius(WidgetTester tester, int index) {
final List<Element> containers = tester.elementList(find.byType(Container)) final List<Element> containers = tester.elementList(find.byType(Container))
.toList(); .toList();
final Container container = containers[index + 2].widget; final Container container = containers[index].widget;
final BoxDecoration boxDecoration = container.decoration; final BoxDecoration boxDecoration = container.decoration;
return boxDecoration.borderRadius; return boxDecoration.borderRadius;
...@@ -1095,7 +1098,7 @@ void main() { ...@@ -1095,7 +1098,7 @@ void main() {
); );
List<Widget> boxes = tester.widgetList(find.byType(DecoratedBox)).toList(); List<Widget> boxes = tester.widgetList(find.byType(DecoratedBox)).toList();
int offset = 3; int offset = 1;
expect(isDivider(boxes[offset], false, true), isTrue); expect(isDivider(boxes[offset], false, true), isTrue);
expect(isDivider(boxes[offset + 1], true, true), isTrue); expect(isDivider(boxes[offset + 1], true, true), isTrue);
...@@ -1151,7 +1154,7 @@ void main() { ...@@ -1151,7 +1154,7 @@ void main() {
await tester.pump(const Duration(milliseconds: 200)); await tester.pump(const Duration(milliseconds: 200));
boxes = tester.widgetList(find.byType(DecoratedBox)).toList(); boxes = tester.widgetList(find.byType(DecoratedBox)).toList();
offset = 3; offset = 1;
expect(isDivider(boxes[offset], false, true), isTrue); expect(isDivider(boxes[offset], false, true), isTrue);
expect(isDivider(boxes[offset + 1], true, false), isTrue); expect(isDivider(boxes[offset + 1], true, false), isTrue);
......
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