mirror of
https://github.com/flutter/flutter
synced 2024-10-13 19:52:53 +00:00
Enable physical model shadows (with animation) and change elevation to a double (#9756)
Fixes https://github.com/flutter/flutter/issues/9342
This commit is contained in:
parent
2051669ac7
commit
b586a97ad2
|
@ -66,7 +66,7 @@ class TileScrollLayout extends StatelessWidget {
|
|||
return new Padding(
|
||||
padding:const EdgeInsets.all(5.0),
|
||||
child: new Material(
|
||||
elevation: index % 5 + 1,
|
||||
elevation: (index % 5 + 1).toDouble(),
|
||||
color: Colors.white,
|
||||
child: new IconBar(),
|
||||
),
|
||||
|
|
|
@ -116,7 +116,7 @@ class _CalculatorState extends State<Calculator> {
|
|||
return new Scaffold(
|
||||
appBar: new AppBar(
|
||||
backgroundColor: Theme.of(context).canvasColor,
|
||||
elevation: 0
|
||||
elevation: 0.0
|
||||
),
|
||||
body: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
|
|
|
@ -127,7 +127,7 @@ class ColorsDemo extends StatelessWidget {
|
|||
length: allPalettes.length,
|
||||
child: new Scaffold(
|
||||
appBar: new AppBar(
|
||||
elevation: 0,
|
||||
elevation: 0.0,
|
||||
title: const Text('Colors'),
|
||||
bottom: new TabBar(
|
||||
isScrollable: true,
|
||||
|
|
|
@ -162,7 +162,7 @@ class _Heading extends StatelessWidget {
|
|||
height: (screenSize.height - kToolbarHeight) * 1.35,
|
||||
child: new Material(
|
||||
type: MaterialType.card,
|
||||
elevation: 0,
|
||||
elevation: 0.0,
|
||||
child: new Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0, top: 18.0, right: 16.0, bottom: 24.0),
|
||||
child: new CustomMultiChildLayout(
|
||||
|
@ -302,7 +302,7 @@ class _OrderPageState extends State<OrderPage> {
|
|||
.where((Product product) => product != widget.order.product)
|
||||
.map((Product product) {
|
||||
return new Card(
|
||||
elevation: 1,
|
||||
elevation: 1.0,
|
||||
child: new Image.asset(
|
||||
product.imageAsset,
|
||||
fit: BoxFit.contain,
|
||||
|
|
|
@ -39,10 +39,10 @@ class ShrinePage extends StatefulWidget {
|
|||
|
||||
/// Defines the Scaffold, AppBar, etc that the demo pages have in common.
|
||||
class ShrinePageState extends State<ShrinePage> {
|
||||
int _appBarElevation = 0;
|
||||
double _appBarElevation = 0.0;
|
||||
|
||||
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) {
|
||||
setState(() {
|
||||
_appBarElevation = elevation;
|
||||
|
|
|
@ -193,7 +193,7 @@ class StockHomeState extends State<StockHome> {
|
|||
|
||||
Widget buildAppBar() {
|
||||
return new AppBar(
|
||||
elevation: 0,
|
||||
elevation: 0.0,
|
||||
title: new Text(StockStrings.of(context).title()),
|
||||
actions: <Widget>[
|
||||
new IconButton(
|
||||
|
|
|
@ -157,7 +157,7 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
|
|||
this.actions,
|
||||
this.flexibleSpace,
|
||||
this.bottom,
|
||||
this.elevation: 4,
|
||||
this.elevation: 4.0,
|
||||
this.backgroundColor,
|
||||
this.brightness,
|
||||
this.iconTheme,
|
||||
|
@ -236,10 +236,8 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
|
|||
|
||||
/// 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.
|
||||
final int elevation;
|
||||
final double elevation;
|
||||
|
||||
/// The color to use for the app bar's material. Typically this should be set
|
||||
/// along with [brightness], [iconTheme], [textTheme].
|
||||
|
@ -566,7 +564,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
|
|||
final List<Widget> actions;
|
||||
final Widget flexibleSpace;
|
||||
final PreferredSizeWidget bottom;
|
||||
final int elevation;
|
||||
final double elevation;
|
||||
final bool forceElevated;
|
||||
final Color backgroundColor;
|
||||
final Brightness brightness;
|
||||
|
@ -607,7 +605,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
|
|||
actions: actions,
|
||||
flexibleSpace: flexibleSpace,
|
||||
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,
|
||||
brightness: brightness,
|
||||
iconTheme: iconTheme,
|
||||
|
@ -774,15 +772,13 @@ class SliverAppBar extends StatefulWidget {
|
|||
/// The z-coordinate at which to place this app bar when it is above other
|
||||
/// 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.
|
||||
///
|
||||
/// 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
|
||||
/// content is scrolled under it, or if it scrolls with the content, then no
|
||||
/// 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
|
||||
/// content is not scrolled under the [AppBar].
|
||||
|
|
|
@ -448,7 +448,7 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
|
|||
children: <Widget>[
|
||||
new Positioned.fill(
|
||||
child: new Material( // Casts shadow.
|
||||
elevation: 8,
|
||||
elevation: 8.0,
|
||||
color: widget.type == BottomNavigationBarType.shifting ? _backgroundColor : null
|
||||
)
|
||||
),
|
||||
|
|
|
@ -192,17 +192,13 @@ class MaterialButton extends StatefulWidget {
|
|||
|
||||
/// 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.
|
||||
final int elevation;
|
||||
final double elevation;
|
||||
|
||||
/// 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.
|
||||
final int highlightElevation;
|
||||
final double highlightElevation;
|
||||
|
||||
/// The smallest horizontal extent that the button will occupy.
|
||||
///
|
||||
|
@ -290,7 +286,7 @@ class _MaterialButtonState extends State<MaterialButton> {
|
|||
final TextStyle style = theme.textTheme.button.copyWith(color: textColor);
|
||||
final ButtonTheme buttonTheme = ButtonTheme.of(context);
|
||||
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);
|
||||
Widget contents = IconTheme.merge(
|
||||
data: new IconThemeData(
|
||||
|
|
|
@ -23,7 +23,7 @@ class Card extends StatelessWidget {
|
|||
const Card({
|
||||
Key key,
|
||||
this.color,
|
||||
this.elevation: 2,
|
||||
this.elevation: 2.0,
|
||||
this.child
|
||||
}) : super(key: key);
|
||||
|
||||
|
@ -35,10 +35,8 @@ class Card extends StatelessWidget {
|
|||
|
||||
/// 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.
|
||||
final int elevation;
|
||||
final double elevation;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
|
@ -51,7 +51,7 @@ class Dialog extends StatelessWidget {
|
|||
child: new ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 280.0),
|
||||
child: new Material(
|
||||
elevation: 24,
|
||||
elevation: 24.0,
|
||||
color: _getColor(context),
|
||||
type: MaterialType.card,
|
||||
child: child
|
||||
|
|
|
@ -68,16 +68,14 @@ class Drawer extends StatelessWidget {
|
|||
/// Typically used in the [Scaffold.drawer] property.
|
||||
const Drawer({
|
||||
Key key,
|
||||
this.elevation: 16,
|
||||
this.elevation: 16.0,
|
||||
this.child
|
||||
}) : super(key: key);
|
||||
|
||||
/// 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.
|
||||
final int elevation;
|
||||
final double elevation;
|
||||
|
||||
/// The widget below this widget in the tree.
|
||||
///
|
||||
|
|
|
@ -48,8 +48,8 @@ class FloatingActionButton extends StatefulWidget {
|
|||
this.tooltip,
|
||||
this.backgroundColor,
|
||||
this.heroTag,
|
||||
this.elevation: 6,
|
||||
this.highlightElevation: 12,
|
||||
this.elevation: 6.0,
|
||||
this.highlightElevation: 12.0,
|
||||
@required this.onPressed,
|
||||
this.mini: false
|
||||
}) : super(key: key);
|
||||
|
@ -80,18 +80,14 @@ class FloatingActionButton extends StatefulWidget {
|
|||
|
||||
/// 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.
|
||||
final int elevation;
|
||||
final double elevation;
|
||||
|
||||
/// 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
|
||||
/// while they are being touched.
|
||||
final int highlightElevation;
|
||||
final double highlightElevation;
|
||||
|
||||
/// Controls the size of this button.
|
||||
///
|
||||
|
|
|
@ -7,7 +7,6 @@ import 'package:flutter/rendering.dart';
|
|||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'constants.dart';
|
||||
import 'shadows.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
/// Signature for the callback used by ink effects to obtain the rectangle for the effect.
|
||||
|
@ -113,7 +112,7 @@ class Material extends StatefulWidget {
|
|||
const Material({
|
||||
Key key,
|
||||
this.type: MaterialType.canvas,
|
||||
this.elevation: 0,
|
||||
this.elevation: 0.0,
|
||||
this.color,
|
||||
this.textStyle,
|
||||
this.borderRadius,
|
||||
|
@ -133,10 +132,8 @@ class Material extends StatefulWidget {
|
|||
|
||||
/// 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.
|
||||
final int elevation;
|
||||
final double elevation;
|
||||
|
||||
/// The color to paint the material.
|
||||
///
|
||||
|
@ -189,9 +186,6 @@ class Material extends StatefulWidget {
|
|||
|
||||
/// The default radius of an ink splash in logical pixels.
|
||||
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 {
|
||||
|
@ -237,63 +231,38 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
|
|||
)
|
||||
);
|
||||
|
||||
if (Material.debugEnablePhysicalModel) {
|
||||
if (widget.type == MaterialType.circle) {
|
||||
contents = new PhysicalModel(
|
||||
shape: BoxShape.circle,
|
||||
elevation: widget.elevation,
|
||||
color: backgroundColor,
|
||||
child: contents,
|
||||
);
|
||||
} else if (widget.type == MaterialType.transparency) {
|
||||
if (radius == null) {
|
||||
contents = new ClipRect(child: contents);
|
||||
} else {
|
||||
contents = new ClipRRect(
|
||||
borderRadius: radius,
|
||||
child: contents
|
||||
);
|
||||
}
|
||||
if (widget.type == MaterialType.circle) {
|
||||
contents = new AnimatedPhysicalModel(
|
||||
curve: Curves.fastOutSlowIn,
|
||||
duration: kThemeChangeDuration,
|
||||
shape: BoxShape.circle,
|
||||
elevation: widget.elevation,
|
||||
color: backgroundColor,
|
||||
animateColor: false,
|
||||
child: contents,
|
||||
);
|
||||
} else if (widget.type == MaterialType.transparency) {
|
||||
if (radius == null) {
|
||||
contents = new ClipRect(child: contents);
|
||||
} else {
|
||||
contents = new PhysicalModel(
|
||||
shape: BoxShape.rectangle,
|
||||
borderRadius: radius ?? BorderRadius.zero,
|
||||
elevation: widget.elevation,
|
||||
color: backgroundColor,
|
||||
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(
|
||||
} else {
|
||||
contents = new AnimatedPhysicalModel(
|
||||
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
|
||||
)
|
||||
shape: BoxShape.rectangle,
|
||||
borderRadius: radius ?? BorderRadius.zero,
|
||||
elevation: widget.elevation,
|
||||
color: backgroundColor,
|
||||
animateColor: false,
|
||||
child: contents,
|
||||
);
|
||||
}
|
||||
|
||||
return contents;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -379,7 +379,7 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
|||
final RelativeRect position;
|
||||
final List<PopupMenuEntry<T>> items;
|
||||
final dynamic initialValue;
|
||||
final int elevation;
|
||||
final double elevation;
|
||||
final ThemeData theme;
|
||||
|
||||
@override
|
||||
|
@ -433,15 +433,14 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
|||
/// a [Theme] to use for the menu.
|
||||
///
|
||||
/// 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,
|
||||
/// 12, 16, 24. The elevation defaults to 8, the appropriate elevation for popup
|
||||
/// menu. The elevation defaults to 8, the appropriate elevation for popup
|
||||
/// menus.
|
||||
Future<T> showMenu<T>({
|
||||
@required BuildContext context,
|
||||
RelativeRect position,
|
||||
@required List<PopupMenuEntry<T>> items,
|
||||
T initialValue,
|
||||
int elevation: 8
|
||||
double elevation: 8.0
|
||||
}) {
|
||||
assert(context != null);
|
||||
assert(items != null && items.isNotEmpty);
|
||||
|
@ -481,7 +480,7 @@ class PopupMenuButton<T> extends StatefulWidget {
|
|||
this.initialValue,
|
||||
this.onSelected,
|
||||
this.tooltip: 'Show menu',
|
||||
this.elevation: 8,
|
||||
this.elevation: 8.0,
|
||||
this.padding: const EdgeInsets.all(8.0),
|
||||
this.child
|
||||
}) : assert(itemBuilder != null),
|
||||
|
@ -503,9 +502,7 @@ class PopupMenuButton<T> extends StatefulWidget {
|
|||
final String tooltip;
|
||||
|
||||
/// The z-coordinate at which to place the menu when open.
|
||||
///
|
||||
/// The following elevations have defined shadows: 1, 2, 3, 4, 6, 8, 9, 12, 16, 24
|
||||
final int elevation;
|
||||
final double elevation;
|
||||
|
||||
/// 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
|
||||
|
|
|
@ -480,7 +480,7 @@ class _RefreshProgressIndicatorState extends _CircularProgressIndicatorState {
|
|||
child: new Material(
|
||||
type: MaterialType.circle,
|
||||
color: widget.backgroundColor ?? Theme.of(context).canvasColor,
|
||||
elevation: 2,
|
||||
elevation: 2.0,
|
||||
child: new Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: new CustomPaint(
|
||||
|
|
|
@ -43,9 +43,9 @@ class RaisedButton extends StatelessWidget {
|
|||
this.highlightColor,
|
||||
this.splashColor,
|
||||
this.disabledColor,
|
||||
this.elevation: 2,
|
||||
this.highlightElevation: 8,
|
||||
this.disabledElevation: 0,
|
||||
this.elevation: 2.0,
|
||||
this.highlightElevation: 8.0,
|
||||
this.disabledElevation: 0.0,
|
||||
this.colorBrightness,
|
||||
this.child
|
||||
}) : super(key: key);
|
||||
|
@ -97,25 +97,19 @@ class RaisedButton extends StatelessWidget {
|
|||
|
||||
/// 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.
|
||||
final int elevation;
|
||||
final double elevation;
|
||||
|
||||
/// 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
|
||||
/// being touched.
|
||||
final int highlightElevation;
|
||||
final double highlightElevation;
|
||||
|
||||
/// 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.
|
||||
final int disabledElevation;
|
||||
final double disabledElevation;
|
||||
|
||||
/// The theme brightness to use for this button.
|
||||
///
|
||||
|
|
|
@ -237,7 +237,7 @@ class SnackBar extends StatelessWidget {
|
|||
Scaffold.of(context).removeCurrentSnackBar(reason: SnackBarClosedReason.swipe);
|
||||
},
|
||||
child: new Material(
|
||||
elevation: 6,
|
||||
elevation: 6.0,
|
||||
color: backgroundColor ?? _kSnackBackground,
|
||||
child: new Theme(
|
||||
data: darkTheme,
|
||||
|
|
|
@ -591,7 +591,7 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
|
|||
return new Column(
|
||||
children: <Widget>[
|
||||
new Material(
|
||||
elevation: 2,
|
||||
elevation: 2.0,
|
||||
child: new Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: new Row(
|
||||
|
|
|
@ -41,7 +41,7 @@ class _TextSelectionToolbar extends StatelessWidget {
|
|||
}
|
||||
|
||||
return new Material(
|
||||
elevation: 1,
|
||||
elevation: 1.0,
|
||||
child: new Container(
|
||||
height: 44.0,
|
||||
child: new Row(mainAxisSize: MainAxisSize.min, children: items)
|
||||
|
|
|
@ -645,7 +645,7 @@ class PhysicalModelLayer extends ContainerLayer {
|
|||
///
|
||||
/// The scene must be explicitly recomposited after this property is changed
|
||||
/// (as described at [Layer]).
|
||||
int elevation;
|
||||
double elevation;
|
||||
|
||||
/// The background color.
|
||||
///
|
||||
|
@ -657,7 +657,7 @@ class PhysicalModelLayer extends ContainerLayer {
|
|||
void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
|
||||
builder.pushPhysicalModel(
|
||||
rrect: clipRRect.shift(layerOffset),
|
||||
elevation: elevation.toDouble(),
|
||||
elevation: elevation,
|
||||
color: color,
|
||||
);
|
||||
addChildrenToScene(builder, layerOffset);
|
||||
|
|
|
@ -456,7 +456,7 @@ class PaintingContext {
|
|||
/// * `color` is the background color.
|
||||
/// * `painter` is a callback that will paint with the `clipRRect` applied. This
|
||||
/// 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 RRect offsetClipRRect = clipRRect.shift(offset);
|
||||
if (needsCompositing) {
|
||||
|
|
|
@ -1198,7 +1198,7 @@ class RenderPhysicalModel extends _RenderCustomClip<RRect> {
|
|||
RenderBox child,
|
||||
BoxShape shape,
|
||||
BorderRadius borderRadius: BorderRadius.zero,
|
||||
int elevation,
|
||||
double elevation,
|
||||
Color color,
|
||||
}) : _shape = shape,
|
||||
_borderRadius = borderRadius,
|
||||
|
@ -1235,9 +1235,9 @@ class RenderPhysicalModel extends _RenderCustomClip<RRect> {
|
|||
}
|
||||
|
||||
/// The z-coordinate at which to place this material.
|
||||
int get elevation => _elevation;
|
||||
int _elevation;
|
||||
set elevation(int value) {
|
||||
double get elevation => _elevation;
|
||||
double _elevation;
|
||||
set elevation(double value) {
|
||||
assert(value != null);
|
||||
if (_elevation == value)
|
||||
return;
|
||||
|
|
|
@ -472,7 +472,7 @@ class PhysicalModel extends SingleChildRenderObjectWidget {
|
|||
final BorderRadius borderRadius;
|
||||
|
||||
/// The z-coordinate at which to place this physical object.
|
||||
final int elevation;
|
||||
final double elevation;
|
||||
|
||||
/// The background color.
|
||||
final Color color;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/animation.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
|
||||
|
@ -44,6 +45,17 @@ class EdgeInsetsTween extends Tween<EdgeInsets> {
|
|||
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.
|
||||
///
|
||||
/// Currently this class works only for translations.
|
||||
|
@ -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 {
|
|||
}
|
||||
}
|
||||
|
||||
Widget buildMaterial(int elevation) {
|
||||
Widget buildMaterial(double elevation) {
|
||||
return new Center(
|
||||
child: new SizedBox(
|
||||
height: 100.0,
|
||||
|
@ -27,10 +27,8 @@ Widget buildMaterial(int elevation) {
|
|||
);
|
||||
}
|
||||
|
||||
List<BoxShadow> getShadow(WidgetTester tester) {
|
||||
final RenderDecoratedBox box = tester.renderObject(find.byType(DecoratedBox).first);
|
||||
final BoxDecoration decoration = box.decoration;
|
||||
return decoration.boxShadow;
|
||||
RenderPhysicalModel getShadow(WidgetTester tester) {
|
||||
return tester.renderObject(find.byType(PhysicalModel));
|
||||
}
|
||||
|
||||
class PaintRecorder extends CustomPainter {
|
||||
|
@ -115,67 +113,27 @@ void main() {
|
|||
});
|
||||
|
||||
testWidgets('Shadows animate smoothly', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(buildMaterial(0));
|
||||
final List<BoxShadow> shadowA = getShadow(tester);
|
||||
// This code verifies that the PhysicalModel's elevation animates over
|
||||
// a kThemeChangeDuration time interval.
|
||||
|
||||
await tester.pumpWidget(buildMaterial(9));
|
||||
final List<BoxShadow> shadowB = getShadow(tester);
|
||||
await tester.pumpWidget(buildMaterial(0.0));
|
||||
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));
|
||||
final List<BoxShadow> shadowC = getShadow(tester);
|
||||
final RenderPhysicalModel modelC = getShadow(tester);
|
||||
expect(modelC.elevation, closeTo(0.0, 0.001));
|
||||
|
||||
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);
|
||||
final List<BoxShadow> shadowE = getShadow(tester);
|
||||
|
||||
// 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]);
|
||||
final RenderPhysicalModel modelE = getShadow(tester);
|
||||
expect(modelE.elevation, equals(9.0));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
final List<Element> containers = tester.elementList(find.byType(Container))
|
||||
.toList();
|
||||
|
||||
final Container container = containers[index + 2].widget;
|
||||
final Container container = containers[index].widget;
|
||||
final BoxDecoration boxDecoration = container.decoration;
|
||||
|
||||
return boxDecoration.borderRadius;
|
||||
|
@ -1095,7 +1098,7 @@ void main() {
|
|||
);
|
||||
|
||||
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 + 1], true, true), isTrue);
|
||||
|
@ -1151,7 +1154,7 @@ void main() {
|
|||
await tester.pump(const Duration(milliseconds: 200));
|
||||
|
||||
boxes = tester.widgetList(find.byType(DecoratedBox)).toList();
|
||||
offset = 3;
|
||||
offset = 1;
|
||||
|
||||
expect(isDivider(boxes[offset], false, true), isTrue);
|
||||
expect(isDivider(boxes[offset + 1], true, false), isTrue);
|
||||
|
|
Loading…
Reference in a new issue