Unverified Commit 6d134e0c authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Animation API improvements (#21540)

parent adcf226e
...@@ -267,7 +267,7 @@ linter: ...@@ -267,7 +267,7 @@ linter:
throw 'failed to parse error message (read line number as $lineNumber; total number of lines is ${lines.length}): $error'; throw 'failed to parse error message (read line number as $lineNumber; total number of lines is ${lines.length}): $error';
} }
final Line actualLine = lines[lineNumber - 1]; final Line actualLine = lines[lineNumber - 1];
if (errorCode == 'unused_element') { if (errorCode == 'unused_element' || errorCode == 'unused_local_variable') {
// We don't really care if sample code isn't used! // We don't really care if sample code isn't used!
} else if (actualLine == null) { } else if (actualLine == null) {
if (errorCode == 'missing_identifier' && lineNumber > 1 && buffer[lineNumber - 2].endsWith(',')) { if (errorCode == 'missing_identifier' && lineNumber > 1 && buffer[lineNumber - 2].endsWith(',')) {
...@@ -330,6 +330,9 @@ void processBlock(Line line, List<String> block, List<Section> sections) { ...@@ -330,6 +330,9 @@ void processBlock(Line line, List<String> block, List<Section> sections) {
sections.add(Section(line, 'Future<Null> expression$_expressionId() async { ', block.toList(), ' }')); sections.add(Section(line, 'Future<Null> expression$_expressionId() async { ', block.toList(), ' }'));
} else if (block.first.startsWith('class ') || block.first.startsWith('enum ')) { } else if (block.first.startsWith('class ') || block.first.startsWith('enum ')) {
sections.add(Section(line, null, block.toList(), null)); sections.add(Section(line, null, block.toList(), null));
} else if ((block.first.startsWith('_') || block.first.startsWith('final ')) && block.first.contains(' = ')) {
_expressionId += 1;
sections.add(Section(line, 'void expression$_expressionId() { ', block.toList(), ' }'));
} else { } else {
final List<String> buffer = <String>[]; final List<String> buffer = <String>[];
int subblocks = 0; int subblocks = 0;
......
...@@ -318,7 +318,8 @@ class _BackdropDemoState extends State<BackdropDemo> with SingleTickerProviderSt ...@@ -318,7 +318,8 @@ class _BackdropDemoState extends State<BackdropDemo> with SingleTickerProviderSt
final Size panelSize = constraints.biggest; final Size panelSize = constraints.biggest;
final double panelTop = panelSize.height - panelTitleHeight; final double panelTop = panelSize.height - panelTitleHeight;
final Animation<RelativeRect> panelAnimation = RelativeRectTween( final Animation<RelativeRect> panelAnimation = _controller.drive(
RelativeRectTween(
begin: RelativeRect.fromLTRB( begin: RelativeRect.fromLTRB(
0.0, 0.0,
panelTop - MediaQuery.of(context).padding.bottom, panelTop - MediaQuery.of(context).padding.bottom,
...@@ -326,10 +327,6 @@ class _BackdropDemoState extends State<BackdropDemo> with SingleTickerProviderSt ...@@ -326,10 +327,6 @@ class _BackdropDemoState extends State<BackdropDemo> with SingleTickerProviderSt
panelTop - panelSize.height, panelTop - panelSize.height,
), ),
end: const RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0), end: const RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0),
).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.linear,
), ),
); );
......
...@@ -24,10 +24,9 @@ class NavigationIconView { ...@@ -24,10 +24,9 @@ class NavigationIconView {
duration: kThemeAnimationDuration, duration: kThemeAnimationDuration,
vsync: vsync, vsync: vsync,
) { ) {
_animation = CurvedAnimation( _animation = controller.drive(CurveTween(
parent: controller,
curve: const Interval(0.5, 1.0, curve: Curves.fastOutSlowIn), curve: const Interval(0.5, 1.0, curve: Curves.fastOutSlowIn),
); ));
} }
final Widget _icon; final Widget _icon;
...@@ -35,7 +34,7 @@ class NavigationIconView { ...@@ -35,7 +34,7 @@ class NavigationIconView {
final String _title; final String _title;
final BottomNavigationBarItem item; final BottomNavigationBarItem item;
final AnimationController controller; final AnimationController controller;
CurvedAnimation _animation; Animation<double> _animation;
FadeTransition transition(BottomNavigationBarType type, BuildContext context) { FadeTransition transition(BottomNavigationBarType type, BuildContext context) {
Color iconColor; Color iconColor;
...@@ -51,10 +50,12 @@ class NavigationIconView { ...@@ -51,10 +50,12 @@ class NavigationIconView {
return FadeTransition( return FadeTransition(
opacity: _animation, opacity: _animation,
child: SlideTransition( child: SlideTransition(
position: Tween<Offset>( position: _animation.drive(
Tween<Offset>(
begin: const Offset(0.0, 0.02), // Slightly down. begin: const Offset(0.0, 0.02), // Slightly down.
end: Offset.zero, end: Offset.zero,
).animate(_animation), ),
),
child: IconTheme( child: IconTheme(
data: IconThemeData( data: IconThemeData(
color: iconColor, color: iconColor,
......
...@@ -23,6 +23,13 @@ class _DrawerDemoState extends State<DrawerDemo> with TickerProviderStateMixin { ...@@ -23,6 +23,13 @@ class _DrawerDemoState extends State<DrawerDemo> with TickerProviderStateMixin {
'A', 'B', 'C', 'D', 'E', 'A', 'B', 'C', 'D', 'E',
]; ];
static final Animatable<Offset> _drawerDetailsTween = Tween<Offset>(
begin: const Offset(0.0, -1.0),
end: Offset.zero,
).chain(CurveTween(
curve: Curves.fastOutSlowIn,
));
AnimationController _controller; AnimationController _controller;
Animation<double> _drawerContentsOpacity; Animation<double> _drawerContentsOpacity;
Animation<Offset> _drawerDetailsPosition; Animation<Offset> _drawerDetailsPosition;
...@@ -39,13 +46,7 @@ class _DrawerDemoState extends State<DrawerDemo> with TickerProviderStateMixin { ...@@ -39,13 +46,7 @@ class _DrawerDemoState extends State<DrawerDemo> with TickerProviderStateMixin {
parent: ReverseAnimation(_controller), parent: ReverseAnimation(_controller),
curve: Curves.fastOutSlowIn, curve: Curves.fastOutSlowIn,
); );
_drawerDetailsPosition = Tween<Offset>( _drawerDetailsPosition = _controller.drive(_drawerDetailsTween);
begin: const Offset(0.0, -1.0),
end: Offset.zero,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.fastOutSlowIn,
));
} }
@override @override
......
...@@ -117,10 +117,10 @@ class _GridPhotoViewerState extends State<GridPhotoViewer> with SingleTickerProv ...@@ -117,10 +117,10 @@ class _GridPhotoViewerState extends State<GridPhotoViewer> with SingleTickerProv
return; return;
final Offset direction = details.velocity.pixelsPerSecond / magnitude; final Offset direction = details.velocity.pixelsPerSecond / magnitude;
final double distance = (Offset.zero & context.size).shortestSide; final double distance = (Offset.zero & context.size).shortestSide;
_flingAnimation = Tween<Offset>( _flingAnimation = _controller.drive(Tween<Offset>(
begin: _offset, begin: _offset,
end: _clampOffset(_offset + direction * distance) end: _clampOffset(_offset + direction * distance)
).animate(_controller); ));
_controller _controller
..value = 0.0 ..value = 0.0
..fling(velocity: magnitude / 1000.0); ..fling(velocity: magnitude / 1000.0);
......
...@@ -35,7 +35,7 @@ class _CustomThumbShape extends SliderComponentShape { ...@@ -35,7 +35,7 @@ class _CustomThumbShape extends SliderComponentShape {
return isEnabled ? const Size.fromRadius(_thumbSize) : const Size.fromRadius(_disabledThumbSize); return isEnabled ? const Size.fromRadius(_thumbSize) : const Size.fromRadius(_disabledThumbSize);
} }
static final Tween<double> sizeTween = Tween<double>( static final Animatable<double> sizeTween = Tween<double>(
begin: _disabledThumbSize, begin: _disabledThumbSize,
end: _thumbSize, end: _thumbSize,
); );
...@@ -74,7 +74,7 @@ class _CustomValueIndicatorShape extends SliderComponentShape { ...@@ -74,7 +74,7 @@ class _CustomValueIndicatorShape extends SliderComponentShape {
return Size.fromRadius(isEnabled ? _indicatorSize : _disabledIndicatorSize); return Size.fromRadius(isEnabled ? _indicatorSize : _disabledIndicatorSize);
} }
static final Tween<double> sizeTween = Tween<double>( static final Animatable<double> sizeTween = Tween<double>(
begin: _disabledIndicatorSize, begin: _disabledIndicatorSize,
end: _indicatorSize, end: _indicatorSize,
); );
......
...@@ -120,7 +120,7 @@ class _RecipeGridPageState extends State<RecipeGridPage> { ...@@ -120,7 +120,7 @@ class _RecipeGridPageState extends State<RecipeGridPage> {
final Size size = constraints.biggest; final Size size = constraints.biggest;
final double appBarHeight = size.height - statusBarHeight; final double appBarHeight = size.height - statusBarHeight;
final double t = (appBarHeight - kToolbarHeight) / (_kAppBarHeight - kToolbarHeight); final double t = (appBarHeight - kToolbarHeight) / (_kAppBarHeight - kToolbarHeight);
final double extraPadding = Tween<double>(begin: 10.0, end: 24.0).lerp(t); final double extraPadding = Tween<double>(begin: 10.0, end: 24.0).transform(t);
final double logoHeight = appBarHeight - 1.5 * extraPadding; final double logoHeight = appBarHeight - 1.5 * extraPadding;
return Padding( return Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
......
...@@ -12,7 +12,7 @@ const double _kFrontClosedHeight = 92.0; // front layer height when closed ...@@ -12,7 +12,7 @@ const double _kFrontClosedHeight = 92.0; // front layer height when closed
const double _kBackAppBarHeight = 56.0; // back layer (options) appbar height const double _kBackAppBarHeight = 56.0; // back layer (options) appbar height
// The size of the front layer heading's left and right beveled corners. // The size of the front layer heading's left and right beveled corners.
final Tween<BorderRadius> _kFrontHeadingBevelRadius = BorderRadiusTween( final Animatable<BorderRadius> _kFrontHeadingBevelRadius = BorderRadiusTween(
begin: const BorderRadius.only( begin: const BorderRadius.only(
topLeft: Radius.circular(12.0), topLeft: Radius.circular(12.0),
topRight: Radius.circular(12.0), topRight: Radius.circular(12.0),
...@@ -199,6 +199,9 @@ class _BackdropState extends State<Backdrop> with SingleTickerProviderStateMixin ...@@ -199,6 +199,9 @@ class _BackdropState extends State<Backdrop> with SingleTickerProviderStateMixin
AnimationController _controller; AnimationController _controller;
Animation<double> _frontOpacity; Animation<double> _frontOpacity;
static final Animatable<double> _frontOpacityTween = Tween<double>(begin: 0.2, end: 1.0)
.chain(CurveTween(curve: const Interval(0.0, 0.4, curve: Curves.easeInOut)));
@override @override
void initState() { void initState() {
super.initState(); super.initState();
...@@ -207,14 +210,7 @@ class _BackdropState extends State<Backdrop> with SingleTickerProviderStateMixin ...@@ -207,14 +210,7 @@ class _BackdropState extends State<Backdrop> with SingleTickerProviderStateMixin
value: 1.0, value: 1.0,
vsync: this, vsync: this,
); );
_frontOpacity = _controller.drive(_frontOpacityTween);
_frontOpacity =
Tween<double>(begin: 0.2, end: 1.0).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.0, 0.4, curve: Curves.easeInOut),
),
);
} }
@override @override
...@@ -254,10 +250,10 @@ class _BackdropState extends State<Backdrop> with SingleTickerProviderStateMixin ...@@ -254,10 +250,10 @@ class _BackdropState extends State<Backdrop> with SingleTickerProviderStateMixin
} }
Widget _buildStack(BuildContext context, BoxConstraints constraints) { Widget _buildStack(BuildContext context, BoxConstraints constraints) {
final Animation<RelativeRect> frontRelativeRect = RelativeRectTween( final Animation<RelativeRect> frontRelativeRect = _controller.drive(RelativeRectTween(
begin: RelativeRect.fromLTRB(0.0, constraints.biggest.height - _kFrontClosedHeight, 0.0, 0.0), begin: RelativeRect.fromLTRB(0.0, constraints.biggest.height - _kFrontClosedHeight, 0.0, 0.0),
end: const RelativeRect.fromLTRB(0.0, _kBackAppBarHeight, 0.0, 0.0), end: const RelativeRect.fromLTRB(0.0, _kBackAppBarHeight, 0.0, 0.0),
).animate(_controller); ));
final List<Widget> layers = <Widget>[ final List<Widget> layers = <Widget>[
// Back layer // Back layer
...@@ -301,7 +297,7 @@ class _BackdropState extends State<Backdrop> with SingleTickerProviderStateMixin ...@@ -301,7 +297,7 @@ class _BackdropState extends State<Backdrop> with SingleTickerProviderStateMixin
color: Theme.of(context).canvasColor, color: Theme.of(context).canvasColor,
clipper: ShapeBorderClipper( clipper: ShapeBorderClipper(
shape: BeveledRectangleBorder( shape: BeveledRectangleBorder(
borderRadius: _kFrontHeadingBevelRadius.lerp(_controller.value), borderRadius: _kFrontHeadingBevelRadius.transform(_controller.value),
), ),
), ),
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
......
...@@ -6,6 +6,8 @@ import 'dart:ui' show VoidCallback; ...@@ -6,6 +6,8 @@ import 'dart:ui' show VoidCallback;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'tween.dart';
/// The status of an animation /// The status of an animation
enum AnimationStatus { enum AnimationStatus {
/// The animation is stopped at the beginning /// The animation is stopped at the beginning
...@@ -38,6 +40,11 @@ typedef AnimationStatusListener = void Function(AnimationStatus status); ...@@ -38,6 +40,11 @@ typedef AnimationStatusListener = void Function(AnimationStatus status);
/// ///
/// To create a new animation that you can run forward and backward, consider /// To create a new animation that you can run forward and backward, consider
/// using [AnimationController]. /// using [AnimationController].
///
/// See also:
///
/// * [Tween], which can be used to create [Animation] subclasses that
/// convert `Animation<double>`s into other kinds of `Animation`s.
abstract class Animation<T> extends Listenable implements ValueListenable<T> { abstract class Animation<T> extends Listenable implements ValueListenable<T> {
/// Abstract const constructor. This constructor enables subclasses to provide /// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions. /// const constructors so that they can be used in const expressions.
...@@ -80,6 +87,76 @@ abstract class Animation<T> extends Listenable implements ValueListenable<T> { ...@@ -80,6 +87,76 @@ abstract class Animation<T> extends Listenable implements ValueListenable<T> {
/// Whether this animation is stopped at the end. /// Whether this animation is stopped at the end.
bool get isCompleted => status == AnimationStatus.completed; bool get isCompleted => status == AnimationStatus.completed;
/// Chains a [Tween] (or [CurveTween]) to this [Animation].
///
/// This method is only valid for `Animation<double>` instances (i.e. when `T`
/// is `double`). This means, for instance, that it can be called on
/// [AnimationController] objects, as well as [CurvedAnimation]s,
/// [ProxyAnimation]s, [ReverseAnimation]s, [TrainHoppingAnimation]s, etc.
///
/// It returns an [Animation] specialized to the same type, `U`, as the
/// argument to the method (`child`), whose value is derived by applying the
/// given [Tween] to the value of this [Animation].
///
/// ## Sample code
///
/// Given an [AnimationController] `_controller`, the following code creates
/// an `Animation<Alignment>` that swings from top left to top right as the
/// controller goes from 0.0 to 1.0:
///
/// ```dart
/// Animation<Alignment> _alignment1 = _controller.drive(
/// AlignmentTween(
/// begin: Alignment.topLeft,
/// end: Alignment.topRight,
/// ),
/// );
/// ```
///
/// The `_alignment.value` could then be used in a widget's build method, for
/// instance, to position a child using an [Align] widget such that the
/// position of the child shifts over time from the top left to the top right.
///
/// It is common to ease this kind of curve, e.g. making the transition slower
/// at the start and faster at the end. The following snippet shows one way to
/// chain the alignment tween in the previous example to an easing curve (in
/// this case, [Curves.easeIn]). In this example, the tween is created
/// elsewhere as a variable that can be reused, since none of its arguments
/// vary.
///
/// ```dart
/// final Animatable<Alignment> _tween = AlignmentTween(begin: Alignment.topLeft, end: Alignment.topRight)
/// .chain(CurveTween(curve: Curves.easeIn));
/// // ...
/// Animation<Alignment> _alignment2 = _controller.drive(_tween);
/// ```
///
/// The following code is exactly equivalent, and is typically clearer when
/// the tweens are created inline, as might be preferred when the tweens have
/// values that depend on other variables:
///
/// ```dart
/// Animation<Alignment> _alignment3 = _controller
/// .drive(CurveTween(curve: Curves.easeIn))
/// .drive(AlignmentTween(
/// begin: Alignment.topLeft,
/// end: Alignment.topRight,
/// ));
/// ```
///
/// See also:
///
/// * [Animatable.animate], which does the same thing.
/// * [AnimationController], which is usually used to drive animations.
/// * [CurvedAnimation], an alternative to [CurveTween] for applying easing
/// curves, which supports distinct curves in the forward direction and the
/// reverse direction.
@optionalTypeArgs
Animation<U> drive<U>(Animatable<U> child) {
assert(this is Animation<double>);
return child.animate(this as dynamic); // TODO(ianh): Clean this once https://github.com/dart-lang/sdk/issues/32120 is fixed.
}
@override @override
String toString() { String toString() {
return '${describeIdentity(this)}(${toStringDetails()})'; return '${describeIdentity(this)}(${toStringDetails()})';
......
...@@ -17,7 +17,8 @@ import 'listener_helpers.dart'; ...@@ -17,7 +17,8 @@ import 'listener_helpers.dart';
export 'package:flutter/scheduler.dart' show TickerFuture, TickerCanceled; export 'package:flutter/scheduler.dart' show TickerFuture, TickerCanceled;
// Examples can assume: // Examples can assume:
// AnimationController _controller; // AnimationController _controller, fadeAnimationController, sizeAnimationController;
// bool dismissed;
/// The direction in which an animation is running. /// The direction in which an animation is running.
enum _AnimationDirection { enum _AnimationDirection {
...@@ -77,15 +78,39 @@ enum AnimationBehavior { ...@@ -77,15 +78,39 @@ enum AnimationBehavior {
/// a new value whenever the device running your app is ready to display a new /// a new value whenever the device running your app is ready to display a new
/// frame (typically, this rate is around 60 values per second). /// frame (typically, this rate is around 60 values per second).
/// ///
/// An AnimationController needs a [TickerProvider], which is configured using /// ## Ticker providers
/// the `vsync` argument on the constructor. If you are creating an ///
/// AnimationController from a [State], then you can use the /// An [AnimationController] needs a [TickerProvider], which is configured using
/// [TickerProviderStateMixin] and [SingleTickerProviderStateMixin] classes to /// the `vsync` argument on the constructor.
/// obtain a suitable [TickerProvider]. The widget test framework [WidgetTester] ///
/// object can be used as a ticker provider in the context of tests. In other /// The [TickerProvider] interface describes a factory for [Ticker] objects. A
/// contexts, you will have to either pass a [TickerProvider] from a higher /// [Ticker] is an object that knows how to register itself with the
/// level (e.g. indirectly from a [State] that mixes in /// [SchedulerBinding] and fires a callback every frame. The
/// [TickerProviderStateMixin]), or create a custom [TickerProvider] subclass. /// [AnimationController] class uses a [Ticker] to step through the animation
/// that it controls.
///
/// If an [AnimationController] is being created from a [State], then the State
/// can use the [TickerProviderStateMixin] and [SingleTickerProviderStateMixin]
/// classes to implement the [TickerProvider] interface. The
/// [TickerProviderStateMixin] class always works for this purpose; the
/// [SingleTickerProviderStateMixin] is slightly more efficient in the case of
/// the class only ever needing one [Ticker] (e.g. if the class creates only a
/// single [AnimationController] during its entire lifetime).
///
/// The widget test framework [WidgetTester] object can be used as a ticker
/// provider in the context of tests. In other contexts, you will have to either
/// pass a [TickerProvider] from a higher level (e.g. indirectly from a [State]
/// that mixes in [TickerProviderStateMixin]), or create a custom
/// [TickerProvider] subclass.
///
/// ## Life cycle
///
/// An [AnimationController] should be [dispose]d when it is no longer needed.
/// This reduces the likelihood of leaks. When used with a [StatefulWidget], it
/// is common for an [AnimationController] to be created in the
/// [State.initState] method and then disposed in the [State.dispose] method.
///
/// ## Using [Future]s with [AnimationController]
/// ///
/// The methods that start animations return a [TickerFuture] object which /// The methods that start animations return a [TickerFuture] object which
/// completes when the animation completes successfully, and never throws an /// completes when the animation completes successfully, and never throws an
...@@ -94,7 +119,61 @@ enum AnimationBehavior { ...@@ -94,7 +119,61 @@ enum AnimationBehavior {
/// completes when the animation completes successfully, and completes with an /// completes when the animation completes successfully, and completes with an
/// error when the animation is aborted. /// error when the animation is aborted.
/// ///
/// This can be used to write code such as: /// This can be used to write code such as the `fadeOutAndUpdateState` method
/// below.
///
/// ## Sample code
///
/// Here is a stateful [Foo] widget. Its [State] uses the
/// [SingleTickerProviderStateMixin] to implement the necessary
/// [TickerProvider], creating its controller in the [initState] method and
/// disposing of it in the [dispose] method. The duration of the controller is
/// configured from a property in the [Foo] widget; as that changes, the
/// [didUpdateWidget] method is used to update the controller.
///
/// ```dart
/// class Foo extends StatefulWidget {
/// Foo({ Key key, this.duration }) : super(key: key);
///
/// final Duration duration;
///
/// @override
/// _FooState createState() => _FooState();
/// }
///
/// class _FooState extends State<Foo> with SingleTickerProviderStateMixin {
/// AnimationController _controller;
///
/// @override
/// void initState() {
/// super.initState();
/// _controller = AnimationController(
/// vsync: this, // the SingleTickerProviderStateMixin
/// duration: widget.duration,
/// );
/// }
///
/// @override
/// void didUpdateWidget(Foo oldWidget) {
/// super.didUpdateWidget(oldWidget);
/// _controller.duration = widget.duration;
/// }
///
/// @override
/// void dispose() {
/// _controller.dispose();
/// super.dispose();
/// }
///
/// @override
/// Widget build(BuildContext context) {
/// return Container(); // ...
/// }
/// }
/// ```
///
/// The following method (for a [State] subclass) drives two animation
/// controllers using Dart's asynchronous syntax for awaiting [Future] objects:
/// ///
/// ```dart /// ```dart
/// Future<Null> fadeOutAndUpdateState() async { /// Future<Null> fadeOutAndUpdateState() async {
...@@ -110,14 +189,20 @@ enum AnimationBehavior { ...@@ -110,14 +189,20 @@ enum AnimationBehavior {
/// } /// }
/// ``` /// ```
/// ///
/// ...which asynchronously runs one animation, then runs another, then changes /// The assumption in the code above is that the animation controllers are being
/// the state of the widget, without having to verify [State.mounted] is still /// disposed in the [State] subclass' override of the [State.dispose] method.
/// true at each step, and without having to chain futures together explicitly. /// Since disposing the controller cancels the animation (raising a
/// (This assumes that the controllers are created in [State.initState] and /// [TickerCanceled] exception), the code here can skip verifying whether
/// disposed in [State.dispose].) /// [State.mounted] is still true at each step. (Again, this assumes that the
/// controllers are created in [State.initState] and disposed in
/// [State.dispose], as described in the previous section.)
///
/// See also:
///
/// * [Tween], the base class for converting an [AnimationController] to a
/// range of values of other types.
class AnimationController extends Animation<double> class AnimationController extends Animation<double>
with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin { with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
/// Creates an animation controller. /// Creates an animation controller.
/// ///
/// * [value] is the initial value of the animation. If defaults to the lower /// * [value] is the initial value of the animation. If defaults to the lower
......
...@@ -248,6 +248,13 @@ class ProxyAnimation extends Animation<double> ...@@ -248,6 +248,13 @@ class ProxyAnimation extends Animation<double>
/// Using a [ReverseAnimation] is different from simply using a [Tween] with a /// Using a [ReverseAnimation] is different from simply using a [Tween] with a
/// begin of 1.0 and an end of 0.0 because the tween does not change the status /// begin of 1.0 and an end of 0.0 because the tween does not change the status
/// or direction of the animation. /// or direction of the animation.
///
/// See also:
///
/// * [Curve.flipped] and [FlippedCurve], which provide a similar effect but on
/// [Curve]s.
/// * [CurvedAnimation], which can take separate curves for when the animation
/// is going forward than for when it is going in reverse.
class ReverseAnimation extends Animation<double> class ReverseAnimation extends Animation<double>
with AnimationLazyListenerMixin, AnimationLocalStatusListenersMixin { with AnimationLazyListenerMixin, AnimationLocalStatusListenersMixin {
...@@ -312,15 +319,8 @@ class ReverseAnimation extends Animation<double> ...@@ -312,15 +319,8 @@ class ReverseAnimation extends Animation<double>
/// An animation that applies a curve to another animation. /// An animation that applies a curve to another animation.
/// ///
/// [CurvedAnimation] is useful when you want to apply a non-linear [Curve] to /// [CurvedAnimation] is useful when you want to apply a non-linear [Curve] to
/// an animation object wrapped in the [CurvedAnimation]. /// an animation object, especially if you want different curves when the
/// /// animation is going forward vs when it is going backward.
/// For example, the following code snippet shows how you can apply a curve to a
/// linear animation produced by an [AnimationController]:
///
/// ``` dart
/// final AnimationController controller = AnimationController(duration: const Duration(milliseconds: 500));
/// final CurvedAnimation animation = CurvedAnimation(parent: controller, curve: Curves.ease);
///```
/// ///
/// Depending on the given curve, the output of the [CurvedAnimation] could have /// Depending on the given curve, the output of the [CurvedAnimation] could have
/// a wider range than its input. For example, elastic curves such as /// a wider range than its input. For example, elastic curves such as
...@@ -328,6 +328,42 @@ class ReverseAnimation extends Animation<double> ...@@ -328,6 +328,42 @@ class ReverseAnimation extends Animation<double>
/// range of 0.0 to 1.0. /// range of 0.0 to 1.0.
/// ///
/// If you want to apply a [Curve] to a [Tween], consider using [CurveTween]. /// If you want to apply a [Curve] to a [Tween], consider using [CurveTween].
///
/// ## Sample code
///
/// The following code snippet shows how you can apply a curve to a linear
/// animation produced by an [AnimationController] `controller`.
///
/// ```dart
/// final Animation<double> animation = CurvedAnimation(
/// parent: controller,
/// curve: Curves.ease,
/// );
/// ```
///
/// This second code snippet shows how to apply a different curve in the forward
/// direction than in the reverse direction. This can't be done using a
/// [CurveTween] (since [Tween]s are not aware of the animation direction when
/// they are applied).
///
/// ```dart
/// final Animation<double> animation = CurvedAnimation(
/// parent: controller,
/// curve: Curves.easeIn,
/// reverseCurve: Curves.easeOut,
/// );
/// ```
///
/// By default, the [reverseCurve] matches the forward [curve].
///
/// See also:
///
/// * [CurveTween], for an alternative way of expressing the first sample
/// above.
/// * [AnimationController], for examples of creating and disposing of an
/// [AnimationController].
/// * [Curve.flipped] and [FlippedCurve], which provide the reverse of a
/// [Curve].
class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<double> { class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<double> {
/// Creates a curved animation. /// Creates a curved animation.
/// ///
...@@ -428,11 +464,19 @@ class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<do ...@@ -428,11 +464,19 @@ class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<do
enum _TrainHoppingMode { minimize, maximize } enum _TrainHoppingMode { minimize, maximize }
/// This animation starts by proxying one animation, but can be given a /// This animation starts by proxying one animation, but when the value of that
/// second animation. When their times cross (either because the second is /// animation crosses the value of the second (either because the second is
/// going in the opposite direction, or because the one overtakes the other), /// going in the opposite direction, or because the one overtakes the other),
/// the animation hops over to proxying the second animation, and the second /// the animation hops over to proxying the second animation.
/// animation becomes the new "first" performance. ///
/// When the [TrainHoppingAnimation] starts proxying the second animation
/// instead of the first, the [onSwitchedTrain] callback is called.
///
/// If the two animations start at the same value, then the
/// [TrainHoppingAnimation] immediately hops to the second animation, and the
/// [onSwitchedTrain] callback is not called. If only one animation is provided
/// (i.e. if the second is null), then the [TrainHoppingAnimation] just proxies
/// the first animation.
/// ///
/// Since this object must track the two animations even when it has no /// Since this object must track the two animations even when it has no
/// listeners of its own, instead of shutting down when all its listeners are /// listeners of its own, instead of shutting down when all its listeners are
...@@ -444,33 +488,41 @@ class TrainHoppingAnimation extends Animation<double> ...@@ -444,33 +488,41 @@ class TrainHoppingAnimation extends Animation<double>
/// Creates a train-hopping animation. /// Creates a train-hopping animation.
/// ///
/// The current train argument must not be null but the next train argument /// The current train argument must not be null but the next train argument
/// can be null. /// can be null. If the next train is null, then this object will just proxy
/// the first animation and never hop.
TrainHoppingAnimation(this._currentTrain, this._nextTrain, { this.onSwitchedTrain }) TrainHoppingAnimation(this._currentTrain, this._nextTrain, { this.onSwitchedTrain })
: assert(_currentTrain != null) { : assert(_currentTrain != null) {
if (_nextTrain != null) { if (_nextTrain != null) {
if (_currentTrain.value > _nextTrain.value) {
_mode = _TrainHoppingMode.maximize;
} else {
_mode = _TrainHoppingMode.minimize;
if (_currentTrain.value == _nextTrain.value) { if (_currentTrain.value == _nextTrain.value) {
_currentTrain = _nextTrain; _currentTrain = _nextTrain;
_nextTrain = null; _nextTrain = null;
} } else if (_currentTrain.value > _nextTrain.value) {
_mode = _TrainHoppingMode.maximize;
} else {
assert(_currentTrain.value < _nextTrain.value);
_mode = _TrainHoppingMode.minimize;
} }
} }
_currentTrain.addStatusListener(_statusChangeHandler); _currentTrain.addStatusListener(_statusChangeHandler);
_currentTrain.addListener(_valueChangeHandler); _currentTrain.addListener(_valueChangeHandler);
_nextTrain?.addListener(_valueChangeHandler); _nextTrain?.addListener(_valueChangeHandler);
assert(_mode != null); assert(_mode != null || _nextTrain == null);
} }
/// The animation that is current driving this animation. /// The animation that is currently driving this animation.
///
/// The identity of this object will change from the first animation to the
/// second animation when [onSwitchedTrain] is called.
Animation<double> get currentTrain => _currentTrain; Animation<double> get currentTrain => _currentTrain;
Animation<double> _currentTrain; Animation<double> _currentTrain;
Animation<double> _nextTrain; Animation<double> _nextTrain;
_TrainHoppingMode _mode; _TrainHoppingMode _mode;
/// Called when this animation switches to be driven by a different animation. /// Called when this animation switches to be driven by the second animation.
///
/// This is not called if the two animations provided to the constructor have
/// the same value at the time of the call to the constructor. In that case,
/// the second animation is used from the start, and the first is ignored.
VoidCallback onSwitchedTrain; VoidCallback onSwitchedTrain;
AnimationStatus _lastStatus; AnimationStatus _lastStatus;
...@@ -491,6 +543,7 @@ class TrainHoppingAnimation extends Animation<double> ...@@ -491,6 +543,7 @@ class TrainHoppingAnimation extends Animation<double>
assert(_currentTrain != null); assert(_currentTrain != null);
bool hop = false; bool hop = false;
if (_nextTrain != null) { if (_nextTrain != null) {
assert(_mode != null);
switch (_mode) { switch (_mode) {
case _TrainHoppingMode.minimize: case _TrainHoppingMode.minimize:
hop = _nextTrain.value <= _currentTrain.value; hop = _nextTrain.value <= _currentTrain.value;
......
...@@ -6,11 +6,22 @@ import 'dart:math' as math; ...@@ -6,11 +6,22 @@ import 'dart:math' as math;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
/// A mapping of the unit interval to the unit interval. /// An easing curve, i.e. a mapping of the unit interval to the unit interval.
///
/// Easing curves are used to adjust the rate of change of an animation over
/// time, allowing them to speed up and slow down, rather than moving at a
/// constant rate.
/// ///
/// A curve must map t=0.0 to 0.0 and t=1.0 to 1.0. /// A curve must map t=0.0 to 0.0 and t=1.0 to 1.0.
/// ///
/// See [Curves] for a collection of common animation curves. /// See also:
///
/// * [Curves], a collection of common animation easing curves.
/// * [CurveTween], which can be used to apply a [Curve] to an [Animation].
/// * [Canvas.drawArc], which draws an arc, and has nothing to do with easing
/// curves.
/// * [Animatable], for a more flexible interface that maps fractions to
/// arbitrary values.
@immutable @immutable
abstract class Curve { abstract class Curve {
/// Abstract const constructor. This constructor enables subclasses to provide /// Abstract const constructor. This constructor enables subclasses to provide
...@@ -26,7 +37,8 @@ abstract class Curve { ...@@ -26,7 +37,8 @@ abstract class Curve {
double transform(double t); double transform(double t);
/// Returns a new curve that is the reversed inversion of this one. /// Returns a new curve that is the reversed inversion of this one.
/// This is often useful as the reverseCurve of an [Animation]. ///
/// This is often useful with [CurvedAnimation.reverseCurve].
/// ///
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4}
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_flipped.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_flipped.mp4}
...@@ -34,6 +46,8 @@ abstract class Curve { ...@@ -34,6 +46,8 @@ abstract class Curve {
/// See also: /// See also:
/// ///
/// * [FlippedCurve], the class that is used to implement this getter. /// * [FlippedCurve], the class that is used to implement this getter.
/// * [ReverseAnimation], which reverses an [Animation] rather than a [Curve].
/// * [CurvedAnimation], which can take a separate curve and reverse curve.
Curve get flipped => FlippedCurve(this); Curve get flipped => FlippedCurve(this);
@override @override
...@@ -248,13 +262,21 @@ class Cubic extends Curve { ...@@ -248,13 +262,21 @@ class Cubic extends Curve {
/// A curve that is the reversed inversion of its given curve. /// A curve that is the reversed inversion of its given curve.
/// ///
/// This curve evaluates the given curve in reverse (i.e., from 1.0 to 0.0 as t /// This curve evaluates the given curve in reverse (i.e., from 1.0 to 0.0 as t
/// increases from 0.0 to 1.0) and returns the inverse of the given curve's value /// increases from 0.0 to 1.0) and returns the inverse of the given curve's
/// (i.e., 1.0 minus the given curve's value). /// value (i.e., 1.0 minus the given curve's value).
/// ///
/// This is the class used to implement the [flipped] getter on curves. /// This is the class used to implement the [flipped] getter on curves.
/// ///
/// This is often useful with [CurvedAnimation.reverseCurve].
///
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4}
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_flipped_curve.mp4} /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_flipped.mp4}
///
/// See also:
///
/// * [Curve.flipped], which provides the [FlippedCurve] of a [Curve].
/// * [ReverseAnimation], which reverses an [Animation] rather than a [Curve].
/// * [CurvedAnimation], which can take a separate curve and reverse curve.
class FlippedCurve extends Curve { class FlippedCurve extends Curve {
/// Creates a flipped curve. /// Creates a flipped curve.
/// ///
......
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'animation.dart'; import 'animation.dart';
import 'animations.dart';
import 'tween.dart'; import 'tween.dart';
/// Enables creating an [Animation] whose value is defined by a /// Enables creating an [Animation] whose value is defined by a
...@@ -67,12 +66,11 @@ class TweenSequence<T> extends Animatable<T> { ...@@ -67,12 +66,11 @@ class TweenSequence<T> extends Animatable<T> {
T _evaluateAt(double t, int index) { T _evaluateAt(double t, int index) {
final TweenSequenceItem<T> element = _items[index]; final TweenSequenceItem<T> element = _items[index];
final double tInterval = _intervals[index].value(t); final double tInterval = _intervals[index].value(t);
return element.tween.evaluate(AlwaysStoppedAnimation<double>(tInterval)); return element.tween.transform(tInterval);
} }
@override @override
T evaluate(Animation<double> animation) { T transform(double t) {
final double t = animation.value;
assert(t >= 0.0 && t <= 1.0); assert(t >= 0.0 && t <= 1.0);
if (t == 1.0) if (t == 1.0)
return _evaluateAt(t, _items.length - 1); return _evaluateAt(t, _items.length - 1);
...@@ -100,6 +98,8 @@ class TweenSequenceItem<T> { ...@@ -100,6 +98,8 @@ class TweenSequenceItem<T> {
/// animation's duration indicated by [weight] and this item's position /// animation's duration indicated by [weight] and this item's position
/// in the list of items. /// in the list of items.
/// ///
/// ## Sample code
///
/// The value of this item can be "curved" by chaining it to a [CurveTween]. /// The value of this item can be "curved" by chaining it to a [CurveTween].
/// For example to create a tween that eases from 0.0 to 10.0: /// For example to create a tween that eases from 0.0 to 10.0:
/// ///
......
...@@ -125,16 +125,10 @@ class _CupertinoButtonState extends State<CupertinoButton> with SingleTickerProv ...@@ -125,16 +125,10 @@ class _CupertinoButtonState extends State<CupertinoButton> with SingleTickerProv
// Eyeballed values. Feel free to tweak. // Eyeballed values. Feel free to tweak.
static const Duration kFadeOutDuration = Duration(milliseconds: 10); static const Duration kFadeOutDuration = Duration(milliseconds: 10);
static const Duration kFadeInDuration = Duration(milliseconds: 100); static const Duration kFadeInDuration = Duration(milliseconds: 100);
Tween<double> _opacityTween; final Tween<double> _opacityTween = Tween<double>(begin: 1.0);
AnimationController _animationController; AnimationController _animationController;
Animation<double> _opacityAnimation;
void _setTween() {
_opacityTween = Tween<double>(
begin: 1.0,
end: widget.pressedOpacity ?? 1.0,
);
}
@override @override
void initState() { void initState() {
...@@ -144,9 +138,22 @@ class _CupertinoButtonState extends State<CupertinoButton> with SingleTickerProv ...@@ -144,9 +138,22 @@ class _CupertinoButtonState extends State<CupertinoButton> with SingleTickerProv
value: 0.0, value: 0.0,
vsync: this, vsync: this,
); );
_opacityAnimation = _animationController
.drive(CurveTween(curve: Curves.decelerate))
.drive(_opacityTween);
_setTween(); _setTween();
} }
@override
void didUpdateWidget(CupertinoButton old) {
super.didUpdateWidget(old);
_setTween();
}
void _setTween() {
_opacityTween.end = widget.pressedOpacity ?? 1.0;
}
@override @override
void dispose() { void dispose() {
_animationController.dispose(); _animationController.dispose();
...@@ -154,12 +161,6 @@ class _CupertinoButtonState extends State<CupertinoButton> with SingleTickerProv ...@@ -154,12 +161,6 @@ class _CupertinoButtonState extends State<CupertinoButton> with SingleTickerProv
super.dispose(); super.dispose();
} }
@override
void didUpdateWidget(CupertinoButton old) {
super.didUpdateWidget(old);
_setTween();
}
bool _buttonHeldDown = false; bool _buttonHeldDown = false;
void _handleTapDown(TapDownDetails event) { void _handleTapDown(TapDownDetails event) {
...@@ -217,10 +218,7 @@ class _CupertinoButtonState extends State<CupertinoButton> with SingleTickerProv ...@@ -217,10 +218,7 @@ class _CupertinoButtonState extends State<CupertinoButton> with SingleTickerProv
minHeight: widget.minSize, minHeight: widget.minSize,
), ),
child: FadeTransition( child: FadeTransition(
opacity: _opacityTween.animate(CurvedAnimation( opacity: _opacityAnimation,
parent: _animationController,
curve: Curves.decelerate,
)),
child: DecoratedBox( child: DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: widget.borderRadius, borderRadius: widget.borderRadius,
......
...@@ -1520,11 +1520,11 @@ class _NavigationBarComponentsTransition { ...@@ -1520,11 +1520,11 @@ class _NavigationBarComponentsTransition {
// paintBounds are based on offset zero so it's ok to expand the Rects. // paintBounds are based on offset zero so it's ok to expand the Rects.
bottomNavBar.renderBox.paintBounds.expandToInclude(topNavBar.renderBox.paintBounds); bottomNavBar.renderBox.paintBounds.expandToInclude(topNavBar.renderBox.paintBounds);
static final Tween<double> fadeOut = Tween<double>( static final Animatable<double> fadeOut = Tween<double>(
begin: 1.0, begin: 1.0,
end: 0.0, end: 0.0,
); );
static final Tween<double> fadeIn = Tween<double>( static final Animatable<double> fadeIn = Tween<double>(
begin: 0.0, begin: 0.0,
end: 1.0, end: 1.0,
); );
...@@ -1601,15 +1601,15 @@ class _NavigationBarComponentsTransition { ...@@ -1601,15 +1601,15 @@ class _NavigationBarComponentsTransition {
} }
Animation<double> fadeInFrom(double t, { Curve curve = Curves.easeIn }) { Animation<double> fadeInFrom(double t, { Curve curve = Curves.easeIn }) {
return fadeIn.animate( return animation.drive(fadeIn.chain(
CurvedAnimation(curve: Interval(t, 1.0, curve: curve), parent: animation), CurveTween(curve: Interval(t, 1.0, curve: curve)),
); ));
} }
Animation<double> fadeOutBy(double t, { Curve curve = Curves.easeOut }) { Animation<double> fadeOutBy(double t, { Curve curve = Curves.easeOut }) {
return fadeOut.animate( return animation.drive(fadeOut.chain(
CurvedAnimation(curve: Interval(0.0, t, curve: curve), parent: animation), CurveTween(curve: Interval(0.0, t, curve: curve)),
); ));
} }
Widget get bottomLeading { Widget get bottomLeading {
...@@ -1663,7 +1663,7 @@ class _NavigationBarComponentsTransition { ...@@ -1663,7 +1663,7 @@ class _NavigationBarComponentsTransition {
); );
return PositionedTransition( return PositionedTransition(
rect: positionTween.animate(animation), rect: animation.drive(positionTween),
child: FadeTransition( child: FadeTransition(
opacity: fadeOutBy(0.2), opacity: fadeOutBy(0.2),
child: DefaultTextStyle( child: DefaultTextStyle(
...@@ -1687,12 +1687,12 @@ class _NavigationBarComponentsTransition { ...@@ -1687,12 +1687,12 @@ class _NavigationBarComponentsTransition {
if (bottomMiddle != null && topBackLabel != null) { if (bottomMiddle != null && topBackLabel != null) {
return PositionedTransition( return PositionedTransition(
rect: slideFromLeadingEdge( rect: animation.drive(slideFromLeadingEdge(
fromKey: bottomComponents.middleKey, fromKey: bottomComponents.middleKey,
fromNavBarBox: bottomNavBarBox, fromNavBarBox: bottomNavBarBox,
toKey: topComponents.backLabelKey, toKey: topComponents.backLabelKey,
toNavBarBox: topNavBarBox, toNavBarBox: topNavBarBox,
).animate(animation), )),
child: FadeTransition( child: FadeTransition(
// A custom middle widget like a segmented control fades away faster. // A custom middle widget like a segmented control fades away faster.
opacity: fadeOutBy(bottomHasUserMiddle ? 0.4 : 0.7), opacity: fadeOutBy(bottomHasUserMiddle ? 0.4 : 0.7),
...@@ -1701,10 +1701,10 @@ class _NavigationBarComponentsTransition { ...@@ -1701,10 +1701,10 @@ class _NavigationBarComponentsTransition {
// edge of a constantly sized outer box. // edge of a constantly sized outer box.
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
child: DefaultTextStyleTransition( child: DefaultTextStyleTransition(
style: TextStyleTween( style: animation.drive(TextStyleTween(
begin: _kMiddleTitleTextStyle, begin: _kMiddleTitleTextStyle,
end: topActionsStyle, end: topActionsStyle,
).animate(animation), )),
child: bottomMiddle.child, child: bottomMiddle.child,
), ),
), ),
...@@ -1742,12 +1742,12 @@ class _NavigationBarComponentsTransition { ...@@ -1742,12 +1742,12 @@ class _NavigationBarComponentsTransition {
if (bottomLargeTitle != null && topBackLabel != null) { if (bottomLargeTitle != null && topBackLabel != null) {
return PositionedTransition( return PositionedTransition(
rect: slideFromLeadingEdge( rect: animation.drive(slideFromLeadingEdge(
fromKey: bottomComponents.largeTitleKey, fromKey: bottomComponents.largeTitleKey,
fromNavBarBox: bottomNavBarBox, fromNavBarBox: bottomNavBarBox,
toKey: topComponents.backLabelKey, toKey: topComponents.backLabelKey,
toNavBarBox: topNavBarBox, toNavBarBox: topNavBarBox,
).animate(animation), )),
child: FadeTransition( child: FadeTransition(
opacity: fadeOutBy(0.6), opacity: fadeOutBy(0.6),
child: Align( child: Align(
...@@ -1755,10 +1755,10 @@ class _NavigationBarComponentsTransition { ...@@ -1755,10 +1755,10 @@ class _NavigationBarComponentsTransition {
// edge of a constantly sized outer box. // edge of a constantly sized outer box.
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
child: DefaultTextStyleTransition( child: DefaultTextStyleTransition(
style: TextStyleTween( style: animation.drive(TextStyleTween(
begin: _kLargeTitleTextStyle, begin: _kLargeTitleTextStyle,
end: topActionsStyle, end: topActionsStyle,
).animate(animation), )),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
child: bottomLargeTitle.child, child: bottomLargeTitle.child,
...@@ -1779,7 +1779,7 @@ class _NavigationBarComponentsTransition { ...@@ -1779,7 +1779,7 @@ class _NavigationBarComponentsTransition {
// Just shift slightly towards the right instead of moving to the back // Just shift slightly towards the right instead of moving to the back
// label position. // label position.
return PositionedTransition( return PositionedTransition(
rect: positionTween.animate(animation), rect: animation.drive(positionTween),
child: FadeTransition( child: FadeTransition(
opacity: fadeOutBy(0.4), opacity: fadeOutBy(0.4),
// Keep the font when transitioning into a non-back-label leading. // Keep the font when transitioning into a non-back-label leading.
...@@ -1850,7 +1850,7 @@ class _NavigationBarComponentsTransition { ...@@ -1850,7 +1850,7 @@ class _NavigationBarComponentsTransition {
); );
return PositionedTransition( return PositionedTransition(
rect: positionTween.animate(animation), rect: animation.drive(positionTween),
child: FadeTransition( child: FadeTransition(
opacity: fadeInFrom(bottomBackChevron == null ? 0.7 : 0.4), opacity: fadeInFrom(bottomBackChevron == null ? 0.7 : 0.4),
child: DefaultTextStyle( child: DefaultTextStyle(
...@@ -1877,10 +1877,10 @@ class _NavigationBarComponentsTransition { ...@@ -1877,10 +1877,10 @@ class _NavigationBarComponentsTransition {
Animation<double> midClickOpacity; Animation<double> midClickOpacity;
if (topBackLabelOpacity != null && topBackLabelOpacity.opacity.value < 1.0) { if (topBackLabelOpacity != null && topBackLabelOpacity.opacity.value < 1.0) {
midClickOpacity = Tween<double>( midClickOpacity = animation.drive(Tween<double>(
begin: 0.0, begin: 0.0,
end: topBackLabelOpacity.opacity.value, end: topBackLabelOpacity.opacity.value,
).animate(animation); ));
} }
// Pick up from an incoming transition from the large title. This is // Pick up from an incoming transition from the large title. This is
...@@ -1893,19 +1893,19 @@ class _NavigationBarComponentsTransition { ...@@ -1893,19 +1893,19 @@ class _NavigationBarComponentsTransition {
bottomLargeExpanded bottomLargeExpanded
) { ) {
return PositionedTransition( return PositionedTransition(
rect: slideFromLeadingEdge( rect: animation.drive(slideFromLeadingEdge(
fromKey: bottomComponents.largeTitleKey, fromKey: bottomComponents.largeTitleKey,
fromNavBarBox: bottomNavBarBox, fromNavBarBox: bottomNavBarBox,
toKey: topComponents.backLabelKey, toKey: topComponents.backLabelKey,
toNavBarBox: topNavBarBox, toNavBarBox: topNavBarBox,
).animate(animation), )),
child: FadeTransition( child: FadeTransition(
opacity: midClickOpacity ?? fadeInFrom(0.4), opacity: midClickOpacity ?? fadeInFrom(0.4),
child: DefaultTextStyleTransition( child: DefaultTextStyleTransition(
style: TextStyleTween( style: animation.drive(TextStyleTween(
begin: _kLargeTitleTextStyle, begin: _kLargeTitleTextStyle,
end: topActionsStyle, end: topActionsStyle,
).animate(animation), )),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
child: topBackLabel.child, child: topBackLabel.child,
...@@ -1918,19 +1918,19 @@ class _NavigationBarComponentsTransition { ...@@ -1918,19 +1918,19 @@ class _NavigationBarComponentsTransition {
// and expanded instead of middle. // and expanded instead of middle.
if (bottomMiddle != null && topBackLabel != null) { if (bottomMiddle != null && topBackLabel != null) {
return PositionedTransition( return PositionedTransition(
rect: slideFromLeadingEdge( rect: animation.drive(slideFromLeadingEdge(
fromKey: bottomComponents.middleKey, fromKey: bottomComponents.middleKey,
fromNavBarBox: bottomNavBarBox, fromNavBarBox: bottomNavBarBox,
toKey: topComponents.backLabelKey, toKey: topComponents.backLabelKey,
toNavBarBox: topNavBarBox, toNavBarBox: topNavBarBox,
).animate(animation), )),
child: FadeTransition( child: FadeTransition(
opacity: midClickOpacity ?? fadeInFrom(0.3), opacity: midClickOpacity ?? fadeInFrom(0.3),
child: DefaultTextStyleTransition( child: DefaultTextStyleTransition(
style: TextStyleTween( style: animation.drive(TextStyleTween(
begin: _kMiddleTitleTextStyle, begin: _kMiddleTitleTextStyle,
end: topActionsStyle, end: topActionsStyle,
).animate(animation), )),
child: topBackLabel.child, child: topBackLabel.child,
), ),
), ),
...@@ -1962,7 +1962,7 @@ class _NavigationBarComponentsTransition { ...@@ -1962,7 +1962,7 @@ class _NavigationBarComponentsTransition {
); );
return PositionedTransition( return PositionedTransition(
rect: positionTween.animate(animation), rect: animation.drive(positionTween),
child: FadeTransition( child: FadeTransition(
opacity: fadeInFrom(0.25), opacity: fadeInFrom(0.25),
child: DefaultTextStyle( child: DefaultTextStyle(
...@@ -2005,7 +2005,7 @@ class _NavigationBarComponentsTransition { ...@@ -2005,7 +2005,7 @@ class _NavigationBarComponentsTransition {
); );
return PositionedTransition( return PositionedTransition(
rect: positionTween.animate(animation), rect: animation.drive(positionTween),
child: FadeTransition( child: FadeTransition(
opacity: fadeInFrom(0.3), opacity: fadeInFrom(0.3),
child: DefaultTextStyle( child: DefaultTextStyle(
......
...@@ -19,19 +19,19 @@ const Color _kModalBarrierColor = Color(0x6604040F); ...@@ -19,19 +19,19 @@ const Color _kModalBarrierColor = Color(0x6604040F);
const Duration _kModalPopupTransitionDuration = Duration(milliseconds: 335); const Duration _kModalPopupTransitionDuration = Duration(milliseconds: 335);
// Offset from offscreen to the right to fully on screen. // Offset from offscreen to the right to fully on screen.
final Tween<Offset> _kRightMiddleTween = Tween<Offset>( final Animatable<Offset> _kRightMiddleTween = Tween<Offset>(
begin: const Offset(1.0, 0.0), begin: const Offset(1.0, 0.0),
end: Offset.zero, end: Offset.zero,
); );
// Offset from fully on screen to 1/3 offscreen to the left. // Offset from fully on screen to 1/3 offscreen to the left.
final Tween<Offset> _kMiddleLeftTween = Tween<Offset>( final Animatable<Offset> _kMiddleLeftTween = Tween<Offset>(
begin: Offset.zero, begin: Offset.zero,
end: const Offset(-1.0/3.0, 0.0), end: const Offset(-1.0/3.0, 0.0),
); );
// Offset from offscreen below to fully on screen. // Offset from offscreen below to fully on screen.
final Tween<Offset> _kBottomUpTween = Tween<Offset>( final Animatable<Offset> _kBottomUpTween = Tween<Offset>(
begin: const Offset(0.0, 1.0), begin: const Offset(0.0, 1.0),
end: Offset.zero, end: Offset.zero,
); );
...@@ -354,28 +354,22 @@ class CupertinoPageTransition extends StatelessWidget { ...@@ -354,28 +354,22 @@ class CupertinoPageTransition extends StatelessWidget {
@required this.child, @required this.child,
@required bool linearTransition, @required bool linearTransition,
}) : assert(linearTransition != null), }) : assert(linearTransition != null),
_primaryPositionAnimation = linearTransition _primaryPositionAnimation = (linearTransition ? primaryRouteAnimation :
? _kRightMiddleTween.animate(primaryRouteAnimation)
: _kRightMiddleTween.animate(
CurvedAnimation( CurvedAnimation(
parent: primaryRouteAnimation, parent: primaryRouteAnimation,
curve: Curves.easeOut, curve: Curves.easeOut,
reverseCurve: Curves.easeIn, reverseCurve: Curves.easeIn,
) )
), ).drive(_kRightMiddleTween),
_secondaryPositionAnimation = _kMiddleLeftTween.animate( _secondaryPositionAnimation = CurvedAnimation(
CurvedAnimation(
parent: secondaryRouteAnimation, parent: secondaryRouteAnimation,
curve: Curves.easeOut, curve: Curves.easeOut,
reverseCurve: Curves.easeIn, reverseCurve: Curves.easeIn,
) ).drive(_kMiddleLeftTween),
), _primaryShadowAnimation = CurvedAnimation(
_primaryShadowAnimation = _kGradientShadowTween.animate(
CurvedAnimation(
parent: primaryRouteAnimation, parent: primaryRouteAnimation,
curve: Curves.easeOut, curve: Curves.easeOut,
) ).drive(_kGradientShadowTween),
),
super(key: key); super(key: key);
// When this page is coming in to cover another page. // When this page is coming in to cover another page.
...@@ -418,12 +412,9 @@ class CupertinoFullscreenDialogTransition extends StatelessWidget { ...@@ -418,12 +412,9 @@ class CupertinoFullscreenDialogTransition extends StatelessWidget {
Key key, Key key,
@required Animation<double> animation, @required Animation<double> animation,
@required this.child, @required this.child,
}) : _positionAnimation = _kBottomUpTween.animate( }) : _positionAnimation = animation
CurvedAnimation( .drive(CurveTween(curve: Curves.easeInOut))
parent: animation, .drive(_kBottomUpTween),
curve: Curves.easeInOut,
)
),
super(key: key); super(key: key);
final Animation<Offset> _positionAnimation; final Animation<Offset> _positionAnimation;
...@@ -859,6 +850,9 @@ Future<T> showCupertinoModalPopup<T>({ ...@@ -859,6 +850,9 @@ Future<T> showCupertinoModalPopup<T>({
); );
} }
final Animatable<double> _dialogTween = Tween<double>(begin: 1.2, end: 1.0)
.chain(CurveTween(curve: Curves.fastOutSlowIn));
Widget _buildCupertinoDialogTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) { Widget _buildCupertinoDialogTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
final CurvedAnimation fadeAnimation = CurvedAnimation( final CurvedAnimation fadeAnimation = CurvedAnimation(
parent: animation, parent: animation,
...@@ -874,15 +868,7 @@ Widget _buildCupertinoDialogTransitions(BuildContext context, Animation<double> ...@@ -874,15 +868,7 @@ Widget _buildCupertinoDialogTransitions(BuildContext context, Animation<double>
opacity: fadeAnimation, opacity: fadeAnimation,
child: ScaleTransition( child: ScaleTransition(
child: child, child: child,
scale: Tween<double>( scale: animation.drive(_dialogTween),
begin: 1.2,
end: 1.0,
).animate(
CurvedAnimation(
parent: animation,
curve: Curves.fastOutSlowIn,
),
),
), ),
); );
} }
......
...@@ -316,7 +316,7 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr ...@@ -316,7 +316,7 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
// animation is complete. // animation is complete.
Color _backgroundColor; Color _backgroundColor;
static final Tween<double> _flexTween = Tween<double>(begin: 1.0, end: 1.5); static final Animatable<double> _flexTween = Tween<double>(begin: 1.0, end: 1.5);
void _resetState() { void _resetState() {
for (AnimationController controller in _controllers) for (AnimationController controller in _controllers)
...@@ -655,7 +655,7 @@ class _RadialPainter extends CustomPainter { ...@@ -655,7 +655,7 @@ class _RadialPainter extends CustomPainter {
); );
canvas.drawCircle( canvas.drawCircle(
center, center,
radiusTween.lerp(circle.animation.value), radiusTween.transform(circle.animation.value),
paint, paint,
); );
} }
......
...@@ -1220,7 +1220,7 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip ...@@ -1220,7 +1220,7 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip
Animation<double> enableAnimation; Animation<double> enableAnimation;
Animation<double> selectionFade; Animation<double> selectionFade;
static final Tween<double> pressedShadowTween = Tween<double>( static final Animatable<double> pressedShadowTween = Tween<double>(
begin: 0.0, begin: 0.0,
end: _kPressElevation, end: _kPressElevation,
); );
......
...@@ -694,6 +694,9 @@ class _SortArrowState extends State<_SortArrow> with TickerProviderStateMixin { ...@@ -694,6 +694,9 @@ class _SortArrowState extends State<_SortArrow> with TickerProviderStateMixin {
bool _down; bool _down;
static final Animatable<double> _turnTween = Tween<double>(begin: 0.0, end: math.pi)
.chain(CurveTween(curve: Curves.easeIn));
@override @override
void initState() { void initState() {
super.initState(); super.initState();
...@@ -706,16 +709,11 @@ class _SortArrowState extends State<_SortArrow> with TickerProviderStateMixin { ...@@ -706,16 +709,11 @@ class _SortArrowState extends State<_SortArrow> with TickerProviderStateMixin {
) )
..addListener(_rebuild); ..addListener(_rebuild);
_opacityController.value = widget.visible ? 1.0 : 0.0; _opacityController.value = widget.visible ? 1.0 : 0.0;
_orientationAnimation = Tween<double>( _orientationController = AnimationController(
begin: 0.0,
end: math.pi,
).animate(CurvedAnimation(
parent: _orientationController = AnimationController(
duration: widget.duration, duration: widget.duration,
vsync: this, vsync: this,
), );
curve: Curves.easeIn _orientationAnimation = _orientationController.drive(_turnTween)
))
..addListener(_rebuild) ..addListener(_rebuild)
..addStatusListener(_resetOrientationAnimation); ..addStatusListener(_resetOrientationAnimation);
if (widget.visible) if (widget.visible)
......
...@@ -525,6 +525,9 @@ class MonthPicker extends StatefulWidget { ...@@ -525,6 +525,9 @@ class MonthPicker extends StatefulWidget {
} }
class _MonthPickerState extends State<MonthPicker> with SingleTickerProviderStateMixin { class _MonthPickerState extends State<MonthPicker> with SingleTickerProviderStateMixin {
static final Animatable<double> _chevronOpacityTween = Tween<double>(begin: 1.0, end: 0.0)
.chain(CurveTween(curve: Curves.easeInOut));
@override @override
void initState() { void initState() {
super.initState(); super.initState();
...@@ -538,12 +541,7 @@ class _MonthPickerState extends State<MonthPicker> with SingleTickerProviderStat ...@@ -538,12 +541,7 @@ class _MonthPickerState extends State<MonthPicker> with SingleTickerProviderStat
_chevronOpacityController = AnimationController( _chevronOpacityController = AnimationController(
duration: const Duration(milliseconds: 250), vsync: this duration: const Duration(milliseconds: 250), vsync: this
); );
_chevronOpacityAnimation = Tween<double>(begin: 1.0, end: 0.0).animate( _chevronOpacityAnimation = _chevronOpacityController.drive(_chevronOpacityTween);
CurvedAnimation(
parent: _chevronOpacityController,
curve: Curves.easeInOut,
)
);
} }
@override @override
......
...@@ -67,16 +67,14 @@ class _ExpandIconState extends State<ExpandIcon> with SingleTickerProviderStateM ...@@ -67,16 +67,14 @@ class _ExpandIconState extends State<ExpandIcon> with SingleTickerProviderStateM
AnimationController _controller; AnimationController _controller;
Animation<double> _iconTurns; Animation<double> _iconTurns;
static final Animatable<double> _iconTurnTween = Tween<double>(begin: 0.0, end: 0.5)
.chain(CurveTween(curve: Curves.fastOutSlowIn));
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_controller = AnimationController(duration: kThemeAnimationDuration, vsync: this); _controller = AnimationController(duration: kThemeAnimationDuration, vsync: this);
_iconTurns = Tween<double>(begin: 0.0, end: 0.5).animate( _iconTurns = _controller.drive(_iconTurnTween);
CurvedAnimation(
parent: _controller,
curve: Curves.fastOutSlowIn
)
);
// If the widget is initially expanded, rotate the icon without animating it. // If the widget is initially expanded, rotate the icon without animating it.
if (widget.isExpanded) { if (widget.isExpanded) {
_controller.value = math.pi; _controller.value = math.pi;
......
...@@ -79,14 +79,22 @@ class ExpansionTile extends StatefulWidget { ...@@ -79,14 +79,22 @@ class ExpansionTile extends StatefulWidget {
} }
class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProviderStateMixin { class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProviderStateMixin {
static final Animatable<double> _easeOutTween = CurveTween(curve: Curves.easeOut);
static final Animatable<double> _easeInTween = CurveTween(curve: Curves.easeIn);
static final Animatable<double> _halfTween = Tween<double>(begin: 0.0, end: 0.5);
final ColorTween _borderColorTween = ColorTween();
final ColorTween _headerColorTween = ColorTween();
final ColorTween _iconColorTween = ColorTween();
final ColorTween _backgroundColorTween = ColorTween();
AnimationController _controller; AnimationController _controller;
CurvedAnimation _easeOutAnimation;
CurvedAnimation _easeInAnimation;
ColorTween _borderColor;
ColorTween _headerColor;
ColorTween _iconColor;
ColorTween _backgroundColor;
Animation<double> _iconTurns; Animation<double> _iconTurns;
Animation<double> _heightFactor;
Animation<Color> _borderColor;
Animation<Color> _headerColor;
Animation<Color> _iconColor;
Animation<Color> _backgroundColor;
bool _isExpanded = false; bool _isExpanded = false;
...@@ -94,13 +102,12 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider ...@@ -94,13 +102,12 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
void initState() { void initState() {
super.initState(); super.initState();
_controller = AnimationController(duration: _kExpand, vsync: this); _controller = AnimationController(duration: _kExpand, vsync: this);
_easeOutAnimation = CurvedAnimation(parent: _controller, curve: Curves.easeOut); _heightFactor = _controller.drive(_easeInTween);
_easeInAnimation = CurvedAnimation(parent: _controller, curve: Curves.easeIn); _iconTurns = _controller.drive(_halfTween.chain(_easeInTween));
_borderColor = ColorTween(); _borderColor = _controller.drive(_borderColorTween.chain(_easeOutTween));
_headerColor = ColorTween(); _headerColor = _controller.drive(_headerColorTween.chain(_easeInTween));
_iconColor = ColorTween(); _iconColor = _controller.drive(_iconColorTween.chain(_easeInTween));
_iconTurns = Tween<double>(begin: 0.0, end: 0.5).animate(_easeInAnimation); _backgroundColor = _controller.drive(_backgroundColorTween.chain(_easeOutTween));
_backgroundColor = ColorTween();
_isExpanded = PageStorage.of(context)?.readState(context) ?? widget.initiallyExpanded; _isExpanded = PageStorage.of(context)?.readState(context) ?? widget.initiallyExpanded;
if (_isExpanded) if (_isExpanded)
...@@ -116,14 +123,17 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider ...@@ -116,14 +123,17 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
void _handleTap() { void _handleTap() {
setState(() { setState(() {
_isExpanded = !_isExpanded; _isExpanded = !_isExpanded;
if (_isExpanded) if (_isExpanded) {
_controller.forward(); _controller.forward();
else } else {
_controller.reverse().then<void>((Null value) { _controller.reverse().then<void>((void value) {
if (!mounted)
return;
setState(() { setState(() {
// Rebuild without widget.children. // Rebuild without widget.children.
}); });
}); });
}
PageStorage.of(context)?.writeState(context, _isExpanded); PageStorage.of(context)?.writeState(context, _isExpanded);
}); });
if (widget.onExpansionChanged != null) if (widget.onExpansionChanged != null)
...@@ -131,12 +141,12 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider ...@@ -131,12 +141,12 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
} }
Widget _buildChildren(BuildContext context, Widget child) { Widget _buildChildren(BuildContext context, Widget child) {
final Color borderSideColor = _borderColor.evaluate(_easeOutAnimation) ?? Colors.transparent; final Color borderSideColor = _borderColor.value ?? Colors.transparent;
final Color titleColor = _headerColor.evaluate(_easeInAnimation); final Color titleColor = _headerColor.value;
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: _backgroundColor.evaluate(_easeOutAnimation) ?? Colors.transparent, color: _backgroundColor.value ?? Colors.transparent,
border: Border( border: Border(
top: BorderSide(color: borderSideColor), top: BorderSide(color: borderSideColor),
bottom: BorderSide(color: borderSideColor), bottom: BorderSide(color: borderSideColor),
...@@ -146,7 +156,7 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider ...@@ -146,7 +156,7 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
IconTheme.merge( IconTheme.merge(
data: IconThemeData(color: _iconColor.evaluate(_easeInAnimation)), data: IconThemeData(color: _iconColor.value),
child: ListTile( child: ListTile(
onTap: _handleTap, onTap: _handleTap,
leading: widget.leading, leading: widget.leading,
...@@ -162,7 +172,7 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider ...@@ -162,7 +172,7 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
), ),
ClipRect( ClipRect(
child: Align( child: Align(
heightFactor: _easeInAnimation.value, heightFactor: _heightFactor.value,
child: child, child: child,
), ),
), ),
...@@ -172,17 +182,23 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider ...@@ -172,17 +182,23 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
} }
@override @override
Widget build(BuildContext context) { void didChangeDependencies() {
final ThemeData theme = Theme.of(context); final ThemeData theme = Theme.of(context);
_borderColor.end = theme.dividerColor; _borderColorTween
_headerColor ..end = theme.dividerColor;
_headerColorTween
..begin = theme.textTheme.subhead.color ..begin = theme.textTheme.subhead.color
..end = theme.accentColor; ..end = theme.accentColor;
_iconColor _iconColorTween
..begin = theme.unselectedWidgetColor ..begin = theme.unselectedWidgetColor
..end = theme.accentColor; ..end = theme.accentColor;
_backgroundColor.end = widget.backgroundColor; _backgroundColorTween
..end = widget.backgroundColor;
super.didChangeDependencies();
}
@override
Widget build(BuildContext context) {
final bool closed = !_isExpanded && _controller.isDismissed; final bool closed = !_isExpanded && _controller.isDismissed;
return AnimatedBuilder( return AnimatedBuilder(
animation: _controller.view, animation: _controller.view,
......
...@@ -133,7 +133,7 @@ class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> { ...@@ -133,7 +133,7 @@ class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> {
return 0.0; return 0.0;
case CollapseMode.parallax: case CollapseMode.parallax:
final double deltaExtent = settings.maxExtent - settings.minExtent; final double deltaExtent = settings.maxExtent - settings.minExtent;
return -Tween<double>(begin: 0.0, end: deltaExtent / 4.0).lerp(t); return -Tween<double>(begin: 0.0, end: deltaExtent / 4.0).transform(t);
} }
return null; return null;
} }
...@@ -193,7 +193,7 @@ class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> { ...@@ -193,7 +193,7 @@ class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> {
color: titleStyle.color.withOpacity(opacity) color: titleStyle.color.withOpacity(opacity)
); );
final bool effectiveCenterTitle = _getEffectiveCenterTitle(theme); final bool effectiveCenterTitle = _getEffectiveCenterTitle(theme);
final double scaleValue = Tween<double>(begin: 1.5, end: 1.0).lerp(t); final double scaleValue = Tween<double>(begin: 1.5, end: 1.0).transform(t);
final Matrix4 scaleTransform = Matrix4.identity() final Matrix4 scaleTransform = Matrix4.identity()
..scale(scaleValue, scaleValue, 1.0); ..scale(scaleValue, scaleValue, 1.0);
final Alignment titleAlignment = _getTitleAlignment(effectiveCenterTitle); final Alignment titleAlignment = _getTitleAlignment(effectiveCenterTitle);
......
...@@ -332,25 +332,28 @@ class _ScalingFabMotionAnimator extends FloatingActionButtonAnimator { ...@@ -332,25 +332,28 @@ class _ScalingFabMotionAnimator extends FloatingActionButtonAnimator {
// then from 0 back to 1 in the second half. // then from 0 back to 1 in the second half.
const Curve curve = Interval(0.5, 1.0, curve: Curves.ease); const Curve curve = Interval(0.5, 1.0, curve: Curves.ease);
return _AnimationSwap<double>( return _AnimationSwap<double>(
ReverseAnimation(CurveTween(curve: curve.flipped).animate(parent)), ReverseAnimation(parent.drive(CurveTween(curve: curve.flipped))),
CurveTween(curve: curve).animate(parent), parent.drive(CurveTween(curve: curve)),
parent, parent,
0.5, 0.5,
); );
} }
@override
Animation<double> getRotationAnimation({Animation<double> parent}) {
// Because we only see the last half of the rotation tween, // Because we only see the last half of the rotation tween,
// it needs to go twice as far. // it needs to go twice as far.
final Tween<double> rotationTween = Tween<double>( static final Animatable<double> _rotationTween = Tween<double>(
begin: 1.0 - kFloatingActionButtonTurnInterval * 2, begin: 1.0 - kFloatingActionButtonTurnInterval * 2.0,
end: 1.0, end: 1.0,
); );
static final Animatable<double> _thresholdCenterTween = CurveTween(curve: const Threshold(0.5));
@override
Animation<double> getRotationAnimation({Animation<double> parent}) {
// This rotation will turn on the way in, but not on the way out. // This rotation will turn on the way in, but not on the way out.
return _AnimationSwap<double>( return _AnimationSwap<double>(
rotationTween.animate(parent), parent.drive(_rotationTween),
ReverseAnimation(CurveTween(curve: const Threshold(0.5)).animate(parent)), ReverseAnimation(parent.drive(_thresholdCenterTween)),
parent, parent,
0.5, 0.5,
); );
......
...@@ -58,10 +58,10 @@ class InkHighlight extends InteractiveInkFeature { ...@@ -58,10 +58,10 @@ class InkHighlight extends InteractiveInkFeature {
..addListener(controller.markNeedsPaint) ..addListener(controller.markNeedsPaint)
..addStatusListener(_handleAlphaStatusChanged) ..addStatusListener(_handleAlphaStatusChanged)
..forward(); ..forward();
_alpha = IntTween( _alpha = _alphaController.drive(IntTween(
begin: 0, begin: 0,
end: color.alpha end: color.alpha,
).animate(_alphaController); ));
controller.addInkFeature(this); controller.addInkFeature(this);
} }
......
...@@ -96,6 +96,9 @@ class InkRipple extends InteractiveInkFeature { ...@@ -96,6 +96,9 @@ class InkRipple extends InteractiveInkFeature {
/// or material [Theme]. /// or material [Theme].
static const InteractiveInkFeatureFactory splashFactory = _InkRippleFactory(); static const InteractiveInkFeatureFactory splashFactory = _InkRippleFactory();
static final Animatable<double> _easeCurveTween = CurveTween(curve: Curves.ease);
static final Animatable<double> _fadeOutIntervalTween = CurveTween(curve: const Interval(_kFadeOutIntervalStart, 1.0));
/// Begin a ripple, centered at [position] relative to [referenceBox]. /// Begin a ripple, centered at [position] relative to [referenceBox].
/// ///
/// The [controller] argument is typically obtained via /// The [controller] argument is typically obtained via
...@@ -140,10 +143,10 @@ class InkRipple extends InteractiveInkFeature { ...@@ -140,10 +143,10 @@ class InkRipple extends InteractiveInkFeature {
_fadeInController = AnimationController(duration: _kFadeInDuration, vsync: controller.vsync) _fadeInController = AnimationController(duration: _kFadeInDuration, vsync: controller.vsync)
..addListener(controller.markNeedsPaint) ..addListener(controller.markNeedsPaint)
..forward(); ..forward();
_fadeIn = IntTween( _fadeIn = _fadeInController.drive(IntTween(
begin: 0, begin: 0,
end: color.alpha, end: color.alpha,
).animate(_fadeInController); ));
// Controls the splash radius and its center. Starts upon confirm. // Controls the splash radius and its center. Starts upon confirm.
_radiusController = AnimationController(duration: _kUnconfirmedRippleDuration, vsync: controller.vsync) _radiusController = AnimationController(duration: _kUnconfirmedRippleDuration, vsync: controller.vsync)
...@@ -151,14 +154,11 @@ class InkRipple extends InteractiveInkFeature { ...@@ -151,14 +154,11 @@ class InkRipple extends InteractiveInkFeature {
..forward(); ..forward();
// Initial splash diameter is 60% of the target diameter, final // Initial splash diameter is 60% of the target diameter, final
// diameter is 10dps larger than the target diameter. // diameter is 10dps larger than the target diameter.
_radius = Tween<double>( _radius = _radiusController.drive(
Tween<double>(
begin: _targetRadius * 0.30, begin: _targetRadius * 0.30,
end: _targetRadius + 5.0, end: _targetRadius + 5.0,
).animate( ).chain(_easeCurveTween),
CurvedAnimation(
parent: _radiusController,
curve: Curves.ease,
)
); );
// Controls the splash radius and its center. Starts upon confirm however its // Controls the splash radius and its center. Starts upon confirm however its
...@@ -166,14 +166,11 @@ class InkRipple extends InteractiveInkFeature { ...@@ -166,14 +166,11 @@ class InkRipple extends InteractiveInkFeature {
_fadeOutController = AnimationController(duration: _kFadeOutDuration, vsync: controller.vsync) _fadeOutController = AnimationController(duration: _kFadeOutDuration, vsync: controller.vsync)
..addListener(controller.markNeedsPaint) ..addListener(controller.markNeedsPaint)
..addStatusListener(_handleAlphaStatusChanged); ..addStatusListener(_handleAlphaStatusChanged);
_fadeOut = IntTween( _fadeOut = _fadeOutController.drive(
IntTween(
begin: color.alpha, begin: color.alpha,
end: 0, end: 0,
).animate( ).chain(_fadeOutIntervalTween),
CurvedAnimation(
parent: _fadeOutController,
curve: const Interval(_kFadeOutIntervalStart, 1.0)
),
); );
controller.addInkFeature(this); controller.addInkFeature(this);
......
...@@ -140,17 +140,17 @@ class InkSplash extends InteractiveInkFeature { ...@@ -140,17 +140,17 @@ class InkSplash extends InteractiveInkFeature {
_radiusController = AnimationController(duration: _kUnconfirmedSplashDuration, vsync: controller.vsync) _radiusController = AnimationController(duration: _kUnconfirmedSplashDuration, vsync: controller.vsync)
..addListener(controller.markNeedsPaint) ..addListener(controller.markNeedsPaint)
..forward(); ..forward();
_radius = Tween<double>( _radius = _radiusController.drive(Tween<double>(
begin: _kSplashInitialSize, begin: _kSplashInitialSize,
end: _targetRadius end: _targetRadius,
).animate(_radiusController); ));
_alphaController = AnimationController(duration: _kSplashFadeDuration, vsync: controller.vsync) _alphaController = AnimationController(duration: _kSplashFadeDuration, vsync: controller.vsync)
..addListener(controller.markNeedsPaint) ..addListener(controller.markNeedsPaint)
..addStatusListener(_handleAlphaStatusChanged); ..addStatusListener(_handleAlphaStatusChanged);
_alpha = IntTween( _alpha = _alphaController.drive(IntTween(
begin: color.alpha, begin: color.alpha,
end: 0 end: 0,
).animate(_alphaController); ));
controller.addInkFeature(this); controller.addInkFeature(this);
} }
......
...@@ -8,7 +8,7 @@ import 'package:flutter/widgets.dart'; ...@@ -8,7 +8,7 @@ import 'package:flutter/widgets.dart';
import 'theme.dart'; import 'theme.dart';
// Fractional offset from 1/4 screen below the top to fully on screen. // Fractional offset from 1/4 screen below the top to fully on screen.
final Tween<Offset> _kBottomUpTween = Tween<Offset>( final Animatable<Offset> _kBottomUpTween = Tween<Offset>(
begin: const Offset(0.0, 0.25), begin: const Offset(0.0, 0.25),
end: Offset.zero, end: Offset.zero,
); );
...@@ -18,18 +18,17 @@ class _MountainViewPageTransition extends StatelessWidget { ...@@ -18,18 +18,17 @@ class _MountainViewPageTransition extends StatelessWidget {
_MountainViewPageTransition({ _MountainViewPageTransition({
Key key, Key key,
@required bool fade, @required bool fade,
@required Animation<double> routeAnimation, @required Animation<double> routeAnimation, // The route's linear 0.0 - 1.0 animation.
@required this.child, @required this.child,
}) : _positionAnimation = _kBottomUpTween.animate(CurvedAnimation( }) : _positionAnimation = routeAnimation.drive(_kBottomUpTween.chain(_fastOutSlowInTween)),
parent: routeAnimation, // The route's linear 0.0 - 1.0 animation. _opacityAnimation = fade
curve: Curves.fastOutSlowIn, ? routeAnimation.drive(_easeInTween) // Eyeballed from other Material apps.
)), : const AlwaysStoppedAnimation<double>(1.0),
_opacityAnimation = fade ? CurvedAnimation(
parent: routeAnimation,
curve: Curves.easeIn, // Eyeballed from other Material apps.
) : const AlwaysStoppedAnimation<double>(1.0),
super(key: key); super(key: key);
static final Animatable<double> _fastOutSlowInTween = CurveTween(curve: Curves.fastOutSlowIn);
static final Animatable<double> _easeInTween = CurveTween(curve: Curves.easeIn);
final Animation<Offset> _positionAnimation; final Animation<Offset> _positionAnimation;
final Animation<double> _opacityAnimation; final Animation<double> _opacityAnimation;
final Widget child; final Widget child;
......
...@@ -150,36 +150,32 @@ class RefreshIndicatorState extends State<RefreshIndicator> with TickerProviderS ...@@ -150,36 +150,32 @@ class RefreshIndicatorState extends State<RefreshIndicator> with TickerProviderS
bool _isIndicatorAtTop; bool _isIndicatorAtTop;
double _dragOffset; double _dragOffset;
static final Animatable<double> _threeQuarterTween = Tween<double>(begin: 0.0, end: 0.75);
static final Animatable<double> _kDragSizeFactorLimitTween = Tween<double>(begin: 0.0, end: _kDragSizeFactorLimit);
static final Animatable<double> _oneToZeroTween = Tween<double>(begin: 1.0, end: 0.0);
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_positionController = AnimationController(vsync: this); _positionController = AnimationController(vsync: this);
_positionFactor = Tween<double>( _positionFactor = _positionController.drive(_kDragSizeFactorLimitTween);
begin: 0.0, _value = _positionController.drive(_threeQuarterTween); // The "value" of the circular progress indicator during a drag.
end: _kDragSizeFactorLimit,
).animate(_positionController);
_value = Tween<double>( // The "value" of the circular progress indicator during a drag.
begin: 0.0,
end: 0.75,
).animate(_positionController);
_scaleController = AnimationController(vsync: this); _scaleController = AnimationController(vsync: this);
_scaleFactor = Tween<double>( _scaleFactor = _scaleController.drive(_oneToZeroTween);
begin: 1.0,
end: 0.0,
).animate(_scaleController);
} }
@override @override
void didChangeDependencies() { void didChangeDependencies() {
final ThemeData theme = Theme.of(context); final ThemeData theme = Theme.of(context);
_valueColor = ColorTween( _valueColor = _positionController.drive(
ColorTween(
begin: (widget.color ?? theme.accentColor).withOpacity(0.0), begin: (widget.color ?? theme.accentColor).withOpacity(0.0),
end: (widget.color ?? theme.accentColor).withOpacity(1.0) end: (widget.color ?? theme.accentColor).withOpacity(1.0)
).animate(CurvedAnimation( ).chain(CurveTween(
parent: _positionController,
curve: const Interval(0.0, 1.0 / _kDragSizeFactorLimit) curve: const Interval(0.0, 1.0 / _kDragSizeFactorLimit)
)); )),
);
super.didChangeDependencies(); super.didChangeDependencies();
} }
......
...@@ -536,6 +536,11 @@ class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTr ...@@ -536,6 +536,11 @@ class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTr
} }
} }
static final Animatable<double> _entranceTurnTween = Tween<double>(
begin: 1.0 - kFloatingActionButtonTurnInterval,
end: 1.0,
).chain(CurveTween(curve: Curves.easeIn));
void _updateAnimations() { void _updateAnimations() {
// Get the animations for exit and entrance. // Get the animations for exit and entrance.
final CurvedAnimation previousExitScaleAnimation = CurvedAnimation( final CurvedAnimation previousExitScaleAnimation = CurvedAnimation(
...@@ -553,15 +558,7 @@ class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTr ...@@ -553,15 +558,7 @@ class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTr
parent: _currentController, parent: _currentController,
curve: Curves.easeIn, curve: Curves.easeIn,
); );
final Animation<double> currentEntranceRotationAnimation = Tween<double>( final Animation<double> currentEntranceRotationAnimation = _currentController.drive(_entranceTurnTween);
begin: 1.0 - kFloatingActionButtonTurnInterval,
end: 1.0,
).animate(
CurvedAnimation(
parent: _currentController,
curve: Curves.easeIn
),
);
// Get the animations for when the FAB is moving. // Get the animations for when the FAB is moving.
final Animation<double> moveScaleAnimation = widget.fabMotionAnimator.getScaleAnimation(parent: widget.fabMoveAnimation); final Animation<double> moveScaleAnimation = widget.fabMotionAnimator.getScaleAnimation(parent: widget.fabMoveAnimation);
...@@ -570,10 +567,7 @@ class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTr ...@@ -570,10 +567,7 @@ class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTr
// Aggregate the animations. // Aggregate the animations.
_previousScaleAnimation = AnimationMin<double>(moveScaleAnimation, previousExitScaleAnimation); _previousScaleAnimation = AnimationMin<double>(moveScaleAnimation, previousExitScaleAnimation);
_currentScaleAnimation = AnimationMin<double>(moveScaleAnimation, currentEntranceScaleAnimation); _currentScaleAnimation = AnimationMin<double>(moveScaleAnimation, currentEntranceScaleAnimation);
_extendedCurrentScaleAnimation = CurvedAnimation( _extendedCurrentScaleAnimation = _currentScaleAnimation.drive(CurveTween(curve: const Interval(0.0, 0.1)));
parent: _currentScaleAnimation,
curve: const Interval(0.0, 0.1),
);
_previousRotationAnimation = TrainHoppingAnimation(previousExitRotationAnimation, moveRotationAnimation); _previousRotationAnimation = TrainHoppingAnimation(previousExitRotationAnimation, moveRotationAnimation);
_currentRotationAnimation = TrainHoppingAnimation(currentEntranceRotationAnimation, moveRotationAnimation); _currentRotationAnimation = TrainHoppingAnimation(currentEntranceRotationAnimation, moveRotationAnimation);
......
...@@ -587,7 +587,7 @@ class _RenderSlider extends RenderBox { ...@@ -587,7 +587,7 @@ class _RenderSlider extends RenderBox {
static const double _preferredTrackWidth = 144.0; static const double _preferredTrackWidth = 144.0;
static const double _preferredTotalWidth = _preferredTrackWidth + _overlayDiameter; static const double _preferredTotalWidth = _preferredTrackWidth + _overlayDiameter;
static const Duration _minimumInteractionTime = Duration(milliseconds: 500); static const Duration _minimumInteractionTime = Duration(milliseconds: 500);
static final Tween<double> _overlayRadiusTween = Tween<double>(begin: 0.0, end: _overlayRadius); static final Animatable<double> _overlayRadiusTween = Tween<double>(begin: 0.0, end: _overlayRadius);
_SliderState _state; _SliderState _state;
Animation<double> _overlayAnimation; Animation<double> _overlayAnimation;
......
...@@ -1010,10 +1010,10 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin { ...@@ -1010,10 +1010,10 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
vsync: this, vsync: this,
); );
_thetaTween = Tween<double>(begin: _getThetaForTime(widget.selectedTime)); _thetaTween = Tween<double>(begin: _getThetaForTime(widget.selectedTime));
_theta = _thetaTween.animate(CurvedAnimation( _theta = _thetaController
parent: _thetaController, .drive(CurveTween(curve: Curves.fastOutSlowIn))
curve: Curves.fastOutSlowIn .drive(_thetaTween)
))..addListener(() => setState(() { })); ..addListener(() => setState(() { /* _theta.value has changed */ }));
} }
ThemeData themeData; ThemeData themeData;
......
...@@ -11,7 +11,7 @@ import 'package:flutter/scheduler.dart'; ...@@ -11,7 +11,7 @@ import 'package:flutter/scheduler.dart';
import 'constants.dart'; import 'constants.dart';
const Duration _kToggleDuration = Duration(milliseconds: 200); const Duration _kToggleDuration = Duration(milliseconds: 200);
final Tween<double> _kRadialReactionRadiusTween = Tween<double>(begin: 0.0, end: kRadialReactionRadius); final Animatable<double> _kRadialReactionRadiusTween = Tween<double>(begin: 0.0, end: kRadialReactionRadius);
/// A base class for material style toggleable controls with toggle animations. /// A base class for material style toggleable controls with toggle animations.
/// ///
......
...@@ -439,15 +439,14 @@ abstract class RenderSliverFloatingPersistentHeader extends RenderSliverPersiste ...@@ -439,15 +439,14 @@ abstract class RenderSliverFloatingPersistentHeader extends RenderSliverPersiste
markNeedsLayout(); markNeedsLayout();
}); });
// Recreating the animation rather than updating a cached value, only _animation = _controller.drive(
// to avoid the extra complexity of managing the animation's lifetime. Tween<double>(
_animation = Tween<double>(
begin: _effectiveScrollOffset, begin: _effectiveScrollOffset,
end: direction == ScrollDirection.forward ? 0.0 : maxExtent, end: direction == ScrollDirection.forward ? 0.0 : maxExtent,
).animate(CurvedAnimation( ).chain(CurveTween(
parent: _controller,
curve: snapConfiguration.curve, curve: snapConfiguration.curve,
)); )),
);
_controller.forward(from: 0.0); _controller.forward(from: 0.0);
} }
......
...@@ -243,29 +243,19 @@ class _AnimatedCrossFadeState extends State<AnimatedCrossFade> with TickerProvid ...@@ -243,29 +243,19 @@ class _AnimatedCrossFadeState extends State<AnimatedCrossFade> with TickerProvid
_controller.value = 1.0; _controller.value = 1.0;
_firstAnimation = _initAnimation(widget.firstCurve, true); _firstAnimation = _initAnimation(widget.firstCurve, true);
_secondAnimation = _initAnimation(widget.secondCurve, false); _secondAnimation = _initAnimation(widget.secondCurve, false);
} _controller.addStatusListener((AnimationStatus status) {
Animation<double> _initAnimation(Curve curve, bool inverted) {
Animation<double> animation = CurvedAnimation(
parent: _controller,
curve: curve,
);
if (inverted) {
animation = Tween<double>(
begin: 1.0,
end: 0.0,
).animate(animation);
}
animation.addStatusListener((AnimationStatus status) {
setState(() { setState(() {
// Trigger a rebuild because it depends on _isTransitioning, which // Trigger a rebuild because it depends on _isTransitioning, which
// changes its value together with animation status. // changes its value together with animation status.
}); });
}); });
}
return animation; Animation<double> _initAnimation(Curve curve, bool inverted) {
Animation<double> result = _controller.drive(CurveTween(curve: curve));
if (inverted)
result = result.drive(Tween<double>(begin: 1.0, end: 0.0));
return result;
} }
@override @override
...@@ -302,8 +292,8 @@ class _AnimatedCrossFadeState extends State<AnimatedCrossFade> with TickerProvid ...@@ -302,8 +292,8 @@ class _AnimatedCrossFadeState extends State<AnimatedCrossFade> with TickerProvid
Widget build(BuildContext context) { Widget build(BuildContext context) {
const Key kFirstChildKey = ValueKey<CrossFadeState>(CrossFadeState.showFirst); const Key kFirstChildKey = ValueKey<CrossFadeState>(CrossFadeState.showFirst);
const Key kSecondChildKey = ValueKey<CrossFadeState>(CrossFadeState.showSecond); const Key kSecondChildKey = ValueKey<CrossFadeState>(CrossFadeState.showSecond);
final bool transitioningForwards = _controller.status == AnimationStatus.completed || _controller.status == AnimationStatus.forward; final bool transitioningForwards = _controller.status == AnimationStatus.completed ||
_controller.status == AnimationStatus.forward;
Key topKey; Key topKey;
Widget topChild; Widget topChild;
Animation<double> topAnimation; Animation<double> topAnimation;
......
...@@ -323,12 +323,14 @@ class _DismissibleState extends State<Dismissible> with TickerProviderStateMixin ...@@ -323,12 +323,14 @@ class _DismissibleState extends State<Dismissible> with TickerProviderStateMixin
void _updateMoveAnimation() { void _updateMoveAnimation() {
final double end = _dragExtent.sign; final double end = _dragExtent.sign;
_moveAnimation = Tween<Offset>( _moveAnimation = _moveController.drive(
Tween<Offset>(
begin: Offset.zero, begin: Offset.zero,
end: _directionIsXAxis end: _directionIsXAxis
? Offset(end, widget.crossAxisEndOffset) ? Offset(end, widget.crossAxisEndOffset)
: Offset(widget.crossAxisEndOffset, end), : Offset(widget.crossAxisEndOffset, end),
).animate(_moveController); ),
);
} }
_FlingGestureKind _describeFlingGesture(Velocity velocity) { _FlingGestureKind _describeFlingGesture(Velocity velocity) {
...@@ -424,13 +426,16 @@ class _DismissibleState extends State<Dismissible> with TickerProviderStateMixin ...@@ -424,13 +426,16 @@ class _DismissibleState extends State<Dismissible> with TickerProviderStateMixin
_resizeController.forward(); _resizeController.forward();
setState(() { setState(() {
_sizePriorToCollapse = context.size; _sizePriorToCollapse = context.size;
_resizeAnimation = Tween<double>( _resizeAnimation = _resizeController.drive(
CurveTween(
curve: _kResizeTimeCurve
),
).drive(
Tween<double>(
begin: 1.0, begin: 1.0,
end: 0.0 end: 0.0
).animate(CurvedAnimation( ),
parent: _resizeController, );
curve: _kResizeTimeCurve
));
}); });
} }
} }
......
...@@ -326,6 +326,8 @@ class _HeroFlight { ...@@ -326,6 +326,8 @@ class _HeroFlight {
return RectTween(begin: begin, end: end); return RectTween(begin: begin, end: end);
} }
static final Animatable<double> _reverseTween = Tween<double>(begin: 1.0, end: 0.0);
// The OverlayEntry WidgetBuilder callback for the hero's overlay. // The OverlayEntry WidgetBuilder callback for the hero's overlay.
Widget _buildOverlay(BuildContext context) { Widget _buildOverlay(BuildContext context) {
assert(manifest != null); assert(manifest != null);
...@@ -347,9 +349,9 @@ class _HeroFlight { ...@@ -347,9 +349,9 @@ class _HeroFlight {
// The toHero no longer exists or it's no longer the flight's destination. // The toHero no longer exists or it's no longer the flight's destination.
// Continue flying while fading out. // Continue flying while fading out.
if (_heroOpacity.isCompleted) { if (_heroOpacity.isCompleted) {
_heroOpacity = Tween<double>(begin: 1.0, end: 0.0) _heroOpacity = _proxyAnimation.drive(
.chain(CurveTween(curve: Interval(_proxyAnimation.value, 1.0))) _reverseTween.chain(CurveTween(curve: Interval(_proxyAnimation.value, 1.0))),
.animate(_proxyAnimation); );
} }
} else if (toHeroBox.hasSize) { } else if (toHeroBox.hasSize) {
// The toHero has been laid out. If it's no longer where the hero animation is // The toHero has been laid out. If it's no longer where the hero animation is
...@@ -460,10 +462,12 @@ class _HeroFlight { ...@@ -460,10 +462,12 @@ class _HeroFlight {
assert(manifest.toHero == newManifest.fromHero); assert(manifest.toHero == newManifest.fromHero);
assert(manifest.toRoute == newManifest.fromRoute); assert(manifest.toRoute == newManifest.fromRoute);
_proxyAnimation.parent = Tween<double>( _proxyAnimation.parent = newManifest.animation.drive(
Tween<double>(
begin: manifest.animation.value, begin: manifest.animation.value,
end: 1.0, end: 1.0,
).animate(newManifest.animation); ),
);
if (manifest.fromHero != newManifest.toHero) { if (manifest.fromHero != newManifest.toHero) {
manifest.fromHero.endFlight(); manifest.fromHero.endFlight();
......
...@@ -1105,7 +1105,7 @@ class _AnimatedOpacityState extends ImplicitlyAnimatedWidgetState<AnimatedOpacit ...@@ -1105,7 +1105,7 @@ class _AnimatedOpacityState extends ImplicitlyAnimatedWidgetState<AnimatedOpacit
@override @override
void didUpdateTweens() { void didUpdateTweens() {
_opacityAnimation = _opacity.animate(animation); _opacityAnimation = animation.drive(_opacity);
} }
@override @override
......
...@@ -259,8 +259,8 @@ class _GlowController extends ChangeNotifier { ...@@ -259,8 +259,8 @@ class _GlowController extends ChangeNotifier {
parent: _glowController, parent: _glowController,
curve: Curves.decelerate, curve: Curves.decelerate,
)..addListener(notifyListeners); )..addListener(notifyListeners);
_glowOpacity = _glowOpacityTween.animate(decelerator); _glowOpacity = decelerator.drive(_glowOpacityTween);
_glowSize = _glowSizeTween.animate(decelerator); _glowSize = decelerator.drive(_glowSizeTween);
_displacementTicker = vsync.createTicker(_tickDisplacement); _displacementTicker = vsync.createTicker(_tickDisplacement);
} }
......
...@@ -16,6 +16,9 @@ import 'overlay.dart'; ...@@ -16,6 +16,9 @@ import 'overlay.dart';
import 'page_storage.dart'; import 'page_storage.dart';
import 'transitions.dart'; import 'transitions.dart';
// Examples can assume:
// dynamic routeObserver;
const Color _kTransparent = Color(0x00000000); const Color _kTransparent = Color(0x00000000);
/// A route that displays widgets in the [Navigator]'s [Overlay]. /// A route that displays widgets in the [Navigator]'s [Overlay].
...@@ -1171,19 +1174,20 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T ...@@ -1171,19 +1174,20 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
final GlobalKey _subtreeKey = GlobalKey(); final GlobalKey _subtreeKey = GlobalKey();
final PageStorageBucket _storageBucket = PageStorageBucket(); final PageStorageBucket _storageBucket = PageStorageBucket();
static final Animatable<double> _easeCurveTween = CurveTween(curve: Curves.ease);
// one of the builders // one of the builders
OverlayEntry _modalBarrier; OverlayEntry _modalBarrier;
Widget _buildModalBarrier(BuildContext context) { Widget _buildModalBarrier(BuildContext context) {
Widget barrier; Widget barrier;
if (barrierColor != null && !offstage) { // changedInternalState is called if these update if (barrierColor != null && !offstage) { // changedInternalState is called if these update
assert(barrierColor != _kTransparent); assert(barrierColor != _kTransparent);
final Animation<Color> color = ColorTween( final Animation<Color> color = animation.drive(
ColorTween(
begin: _kTransparent, begin: _kTransparent,
end: barrierColor, // changedInternalState is called if this updates end: barrierColor, // changedInternalState is called if this updates
).animate(CurvedAnimation( ).chain(_easeCurveTween),
parent: animation, );
curve: Curves.ease,
));
barrier = AnimatedModalBarrier( barrier = AnimatedModalBarrier(
color: color, color: color,
dismissible: barrierDismissible, // changedInternalState is called if this updates dismissible: barrierDismissible, // changedInternalState is called if this updates
......
...@@ -21,7 +21,7 @@ void main() { ...@@ -21,7 +21,7 @@ void main() {
expect(chain, hasOneLineDescription); expect(chain, hasOneLineDescription);
}); });
test('Can animated tweens', () { test('Can animate tweens', () {
final Tween<double> tween = Tween<double>(begin: 0.30, end: 0.50); final Tween<double> tween = Tween<double>(begin: 0.30, end: 0.50);
final AnimationController controller = AnimationController( final AnimationController controller = AnimationController(
vsync: const TestVSync(), vsync: const TestVSync(),
...@@ -33,6 +33,18 @@ void main() { ...@@ -33,6 +33,18 @@ void main() {
expect(animation.toStringDetails(), hasOneLineDescription); expect(animation.toStringDetails(), hasOneLineDescription);
}); });
test('Can drive tweens', () {
final Tween<double> tween = Tween<double>(begin: 0.30, end: 0.50);
final AnimationController controller = AnimationController(
vsync: const TestVSync(),
);
final Animation<double> animation = controller.drive(tween);
controller.value = 0.50;
expect(animation.value, 0.40);
expect(animation, hasOneLineDescription);
expect(animation.toStringDetails(), hasOneLineDescription);
});
test('SizeTween', () { test('SizeTween', () {
final SizeTween tween = SizeTween(begin: Size.zero, end: const Size(20.0, 30.0)); final SizeTween tween = SizeTween(begin: Size.zero, end: const Size(20.0, 30.0));
expect(tween.lerp(0.5), equals(const Size(10.0, 15.0))); expect(tween.lerp(0.5), equals(const Size(10.0, 15.0)));
......
...@@ -18,8 +18,7 @@ void main() { ...@@ -18,8 +18,7 @@ void main() {
); );
expect(a, hasOneLineDescription); expect(a, hasOneLineDescription);
expect(a, equals(b)); expect(a.toString(), equals(b.toString()));
expect(a.hashCode, equals(b.hashCode));
}); });
test('MaterialRectArcTween control test', () { test('MaterialRectArcTween control test', () {
...@@ -33,8 +32,7 @@ void main() { ...@@ -33,8 +32,7 @@ void main() {
end: Rect.fromLTWH(0.0, 10.0, 10.0, 10.0) end: Rect.fromLTWH(0.0, 10.0, 10.0, 10.0)
); );
expect(a, hasOneLineDescription); expect(a, hasOneLineDescription);
expect(a, equals(b)); expect(a.toString(), equals(b.toString()));
expect(a.hashCode, equals(b.hashCode));
}); });
test('on-axis MaterialPointArcTween', () { test('on-axis MaterialPointArcTween', () {
......
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