diff --git a/dev/bots/analyze-sample-code.dart b/dev/bots/analyze-sample-code.dart index 341e03e251..e4e643ad52 100644 --- a/dev/bots/analyze-sample-code.dart +++ b/dev/bots/analyze-sample-code.dart @@ -267,7 +267,7 @@ linter: 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]; - if (errorCode == 'unused_element') { + if (errorCode == 'unused_element' || errorCode == 'unused_local_variable') { // We don't really care if sample code isn't used! } else if (actualLine == null) { if (errorCode == 'missing_identifier' && lineNumber > 1 && buffer[lineNumber - 2].endsWith(',')) { @@ -330,6 +330,9 @@ void processBlock(Line line, List block, List
sections) { sections.add(Section(line, 'Future expression$_expressionId() async { ', block.toList(), ' }')); } else if (block.first.startsWith('class ') || block.first.startsWith('enum ')) { 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 { final List buffer = []; int subblocks = 0; diff --git a/dev/docs/survey.html b/dev/docs/survey.html index 8f022a68a6..096e19b046 100644 --- a/dev/docs/survey.html +++ b/dev/docs/survey.html @@ -1,3 +1,3 @@ - \ No newline at end of file + diff --git a/dev/integration_tests/flavors/ios/Runner/AppDelegate.m b/dev/integration_tests/flavors/ios/Runner/AppDelegate.m index 40826913f6..11b40785e9 100644 --- a/dev/integration_tests/flavors/ios/Runner/AppDelegate.m +++ b/dev/integration_tests/flavors/ios/Runner/AppDelegate.m @@ -8,7 +8,7 @@ // Override point for customization after application launch. FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController; FlutterMethodChannel* flavorChannel = [FlutterMethodChannel methodChannelWithName:@"flavor" binaryMessenger:controller]; - + [flavorChannel setMethodCallHandler:^(FlutterMethodCall *call, FlutterResult result) { NSString* flavor = (NSString*)[[NSBundle mainBundle].infoDictionary valueForKey:@"Flavor"]; result(flavor); diff --git a/dev/integration_tests/platform_interaction/ios/Runner/TestNavigationController.m b/dev/integration_tests/platform_interaction/ios/Runner/TestNavigationController.m index 45c58dacfe..5c030e59eb 100644 --- a/dev/integration_tests/platform_interaction/ios/Runner/TestNavigationController.m +++ b/dev/integration_tests/platform_interaction/ios/Runner/TestNavigationController.m @@ -15,7 +15,7 @@ - (nullable UIViewController *)popViewControllerAnimated:(BOOL)animated { FlutterViewController* root = (FlutterViewController*)[self.viewControllers objectAtIndex:0]; - + FlutterBasicMessageChannel* messageChannel = [FlutterBasicMessageChannel messageChannelWithName:@"navigation-test" binaryMessenger:root diff --git a/examples/flutter_gallery/lib/demo/material/backdrop_demo.dart b/examples/flutter_gallery/lib/demo/material/backdrop_demo.dart index b0d6c0e7aa..9f7b62486f 100644 --- a/examples/flutter_gallery/lib/demo/material/backdrop_demo.dart +++ b/examples/flutter_gallery/lib/demo/material/backdrop_demo.dart @@ -318,18 +318,15 @@ class _BackdropDemoState extends State with SingleTickerProviderSt final Size panelSize = constraints.biggest; final double panelTop = panelSize.height - panelTitleHeight; - final Animation panelAnimation = RelativeRectTween( - begin: RelativeRect.fromLTRB( - 0.0, - panelTop - MediaQuery.of(context).padding.bottom, - 0.0, - panelTop - panelSize.height, - ), - end: const RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0), - ).animate( - CurvedAnimation( - parent: _controller, - curve: Curves.linear, + final Animation panelAnimation = _controller.drive( + RelativeRectTween( + begin: RelativeRect.fromLTRB( + 0.0, + panelTop - MediaQuery.of(context).padding.bottom, + 0.0, + panelTop - panelSize.height, + ), + end: const RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0), ), ); diff --git a/examples/flutter_gallery/lib/demo/material/bottom_navigation_demo.dart b/examples/flutter_gallery/lib/demo/material/bottom_navigation_demo.dart index 9264ff6b8d..85856c1ba5 100644 --- a/examples/flutter_gallery/lib/demo/material/bottom_navigation_demo.dart +++ b/examples/flutter_gallery/lib/demo/material/bottom_navigation_demo.dart @@ -24,10 +24,9 @@ class NavigationIconView { duration: kThemeAnimationDuration, vsync: vsync, ) { - _animation = CurvedAnimation( - parent: controller, + _animation = controller.drive(CurveTween( curve: const Interval(0.5, 1.0, curve: Curves.fastOutSlowIn), - ); + )); } final Widget _icon; @@ -35,7 +34,7 @@ class NavigationIconView { final String _title; final BottomNavigationBarItem item; final AnimationController controller; - CurvedAnimation _animation; + Animation _animation; FadeTransition transition(BottomNavigationBarType type, BuildContext context) { Color iconColor; @@ -51,10 +50,12 @@ class NavigationIconView { return FadeTransition( opacity: _animation, child: SlideTransition( - position: Tween( - begin: const Offset(0.0, 0.02), // Slightly down. - end: Offset.zero, - ).animate(_animation), + position: _animation.drive( + Tween( + begin: const Offset(0.0, 0.02), // Slightly down. + end: Offset.zero, + ), + ), child: IconTheme( data: IconThemeData( color: iconColor, diff --git a/examples/flutter_gallery/lib/demo/material/drawer_demo.dart b/examples/flutter_gallery/lib/demo/material/drawer_demo.dart index 19bd0c545c..fd868af497 100644 --- a/examples/flutter_gallery/lib/demo/material/drawer_demo.dart +++ b/examples/flutter_gallery/lib/demo/material/drawer_demo.dart @@ -23,6 +23,13 @@ class _DrawerDemoState extends State with TickerProviderStateMixin { 'A', 'B', 'C', 'D', 'E', ]; + static final Animatable _drawerDetailsTween = Tween( + begin: const Offset(0.0, -1.0), + end: Offset.zero, + ).chain(CurveTween( + curve: Curves.fastOutSlowIn, + )); + AnimationController _controller; Animation _drawerContentsOpacity; Animation _drawerDetailsPosition; @@ -39,13 +46,7 @@ class _DrawerDemoState extends State with TickerProviderStateMixin { parent: ReverseAnimation(_controller), curve: Curves.fastOutSlowIn, ); - _drawerDetailsPosition = Tween( - begin: const Offset(0.0, -1.0), - end: Offset.zero, - ).animate(CurvedAnimation( - parent: _controller, - curve: Curves.fastOutSlowIn, - )); + _drawerDetailsPosition = _controller.drive(_drawerDetailsTween); } @override diff --git a/examples/flutter_gallery/lib/demo/material/grid_list_demo.dart b/examples/flutter_gallery/lib/demo/material/grid_list_demo.dart index 6bf4594773..cb9661f032 100644 --- a/examples/flutter_gallery/lib/demo/material/grid_list_demo.dart +++ b/examples/flutter_gallery/lib/demo/material/grid_list_demo.dart @@ -117,10 +117,10 @@ class _GridPhotoViewerState extends State with SingleTickerProv return; final Offset direction = details.velocity.pixelsPerSecond / magnitude; final double distance = (Offset.zero & context.size).shortestSide; - _flingAnimation = Tween( + _flingAnimation = _controller.drive(Tween( begin: _offset, end: _clampOffset(_offset + direction * distance) - ).animate(_controller); + )); _controller ..value = 0.0 ..fling(velocity: magnitude / 1000.0); diff --git a/examples/flutter_gallery/lib/demo/material/slider_demo.dart b/examples/flutter_gallery/lib/demo/material/slider_demo.dart index 5a8d4ccd0a..73065876e7 100644 --- a/examples/flutter_gallery/lib/demo/material/slider_demo.dart +++ b/examples/flutter_gallery/lib/demo/material/slider_demo.dart @@ -35,7 +35,7 @@ class _CustomThumbShape extends SliderComponentShape { return isEnabled ? const Size.fromRadius(_thumbSize) : const Size.fromRadius(_disabledThumbSize); } - static final Tween sizeTween = Tween( + static final Animatable sizeTween = Tween( begin: _disabledThumbSize, end: _thumbSize, ); @@ -74,7 +74,7 @@ class _CustomValueIndicatorShape extends SliderComponentShape { return Size.fromRadius(isEnabled ? _indicatorSize : _disabledIndicatorSize); } - static final Tween sizeTween = Tween( + static final Animatable sizeTween = Tween( begin: _disabledIndicatorSize, end: _indicatorSize, ); diff --git a/examples/flutter_gallery/lib/demo/pesto_demo.dart b/examples/flutter_gallery/lib/demo/pesto_demo.dart index fec72f7722..29ed01f2fd 100644 --- a/examples/flutter_gallery/lib/demo/pesto_demo.dart +++ b/examples/flutter_gallery/lib/demo/pesto_demo.dart @@ -120,7 +120,7 @@ class _RecipeGridPageState extends State { final Size size = constraints.biggest; final double appBarHeight = size.height - statusBarHeight; final double t = (appBarHeight - kToolbarHeight) / (_kAppBarHeight - kToolbarHeight); - final double extraPadding = Tween(begin: 10.0, end: 24.0).lerp(t); + final double extraPadding = Tween(begin: 10.0, end: 24.0).transform(t); final double logoHeight = appBarHeight - 1.5 * extraPadding; return Padding( padding: EdgeInsets.only( diff --git a/examples/flutter_gallery/lib/gallery/backdrop.dart b/examples/flutter_gallery/lib/gallery/backdrop.dart index 92074f604e..171d5c3fdc 100644 --- a/examples/flutter_gallery/lib/gallery/backdrop.dart +++ b/examples/flutter_gallery/lib/gallery/backdrop.dart @@ -12,7 +12,7 @@ const double _kFrontClosedHeight = 92.0; // front layer height when closed const double _kBackAppBarHeight = 56.0; // back layer (options) appbar height // The size of the front layer heading's left and right beveled corners. -final Tween _kFrontHeadingBevelRadius = BorderRadiusTween( +final Animatable _kFrontHeadingBevelRadius = BorderRadiusTween( begin: const BorderRadius.only( topLeft: Radius.circular(12.0), topRight: Radius.circular(12.0), @@ -199,6 +199,9 @@ class _BackdropState extends State with SingleTickerProviderStateMixin AnimationController _controller; Animation _frontOpacity; + static final Animatable _frontOpacityTween = Tween(begin: 0.2, end: 1.0) + .chain(CurveTween(curve: const Interval(0.0, 0.4, curve: Curves.easeInOut))); + @override void initState() { super.initState(); @@ -207,14 +210,7 @@ class _BackdropState extends State with SingleTickerProviderStateMixin value: 1.0, vsync: this, ); - - _frontOpacity = - Tween(begin: 0.2, end: 1.0).animate( - CurvedAnimation( - parent: _controller, - curve: const Interval(0.0, 0.4, curve: Curves.easeInOut), - ), - ); + _frontOpacity = _controller.drive(_frontOpacityTween); } @override @@ -254,10 +250,10 @@ class _BackdropState extends State with SingleTickerProviderStateMixin } Widget _buildStack(BuildContext context, BoxConstraints constraints) { - final Animation frontRelativeRect = RelativeRectTween( + final Animation frontRelativeRect = _controller.drive(RelativeRectTween( begin: RelativeRect.fromLTRB(0.0, constraints.biggest.height - _kFrontClosedHeight, 0.0, 0.0), end: const RelativeRect.fromLTRB(0.0, _kBackAppBarHeight, 0.0, 0.0), - ).animate(_controller); + )); final List layers = [ // Back layer @@ -301,7 +297,7 @@ class _BackdropState extends State with SingleTickerProviderStateMixin color: Theme.of(context).canvasColor, clipper: ShapeBorderClipper( shape: BeveledRectangleBorder( - borderRadius: _kFrontHeadingBevelRadius.lerp(_controller.value), + borderRadius: _kFrontHeadingBevelRadius.transform(_controller.value), ), ), clipBehavior: Clip.antiAlias, diff --git a/packages/flutter/lib/src/animation/animation.dart b/packages/flutter/lib/src/animation/animation.dart index be2c9f6d63..65a951cd24 100644 --- a/packages/flutter/lib/src/animation/animation.dart +++ b/packages/flutter/lib/src/animation/animation.dart @@ -6,6 +6,8 @@ import 'dart:ui' show VoidCallback; import 'package:flutter/foundation.dart'; +import 'tween.dart'; + /// The status of an animation enum AnimationStatus { /// The animation is stopped at the beginning @@ -38,6 +40,11 @@ typedef AnimationStatusListener = void Function(AnimationStatus status); /// /// To create a new animation that you can run forward and backward, consider /// using [AnimationController]. +/// +/// See also: +/// +/// * [Tween], which can be used to create [Animation] subclasses that +/// convert `Animation`s into other kinds of `Animation`s. abstract class Animation extends Listenable implements ValueListenable { /// Abstract const constructor. This constructor enables subclasses to provide /// const constructors so that they can be used in const expressions. @@ -80,6 +87,76 @@ abstract class Animation extends Listenable implements ValueListenable { /// Whether this animation is stopped at the end. bool get isCompleted => status == AnimationStatus.completed; + /// Chains a [Tween] (or [CurveTween]) to this [Animation]. + /// + /// This method is only valid for `Animation` 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` that swings from top left to top right as the + /// controller goes from 0.0 to 1.0: + /// + /// ```dart + /// Animation _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 _tween = AlignmentTween(begin: Alignment.topLeft, end: Alignment.topRight) + /// .chain(CurveTween(curve: Curves.easeIn)); + /// // ... + /// Animation _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 _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 drive(Animatable child) { + assert(this is Animation); + return child.animate(this as dynamic); // TODO(ianh): Clean this once https://github.com/dart-lang/sdk/issues/32120 is fixed. + } + @override String toString() { return '${describeIdentity(this)}(${toStringDetails()})'; diff --git a/packages/flutter/lib/src/animation/animation_controller.dart b/packages/flutter/lib/src/animation/animation_controller.dart index a1cd6c3300..f418065811 100644 --- a/packages/flutter/lib/src/animation/animation_controller.dart +++ b/packages/flutter/lib/src/animation/animation_controller.dart @@ -17,7 +17,8 @@ import 'listener_helpers.dart'; export 'package:flutter/scheduler.dart' show TickerFuture, TickerCanceled; // Examples can assume: -// AnimationController _controller; +// AnimationController _controller, fadeAnimationController, sizeAnimationController; +// bool dismissed; /// The direction in which an animation is running. enum _AnimationDirection { @@ -77,15 +78,39 @@ enum AnimationBehavior { /// 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). /// -/// An AnimationController needs a [TickerProvider], which is configured using -/// the `vsync` argument on the constructor. If you are creating an -/// AnimationController from a [State], then you can use the -/// [TickerProviderStateMixin] and [SingleTickerProviderStateMixin] classes to -/// obtain a suitable [TickerProvider]. 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. +/// ## Ticker providers +/// +/// An [AnimationController] needs a [TickerProvider], which is configured using +/// the `vsync` argument on the constructor. +/// +/// The [TickerProvider] interface describes a factory for [Ticker] objects. A +/// [Ticker] is an object that knows how to register itself with the +/// [SchedulerBinding] and fires a callback every frame. The +/// [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 /// completes when the animation completes successfully, and never throws an @@ -94,7 +119,61 @@ enum AnimationBehavior { /// completes when the animation completes successfully, and completes with an /// 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 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 /// Future fadeOutAndUpdateState() async { @@ -110,14 +189,20 @@ enum AnimationBehavior { /// } /// ``` /// -/// ...which asynchronously runs one animation, then runs another, then changes -/// the state of the widget, without having to verify [State.mounted] is still -/// true at each step, and without having to chain futures together explicitly. -/// (This assumes that the controllers are created in [State.initState] and -/// disposed in [State.dispose].) +/// The assumption in the code above is that the animation controllers are being +/// disposed in the [State] subclass' override of the [State.dispose] method. +/// Since disposing the controller cancels the animation (raising a +/// [TickerCanceled] exception), the code here can skip verifying whether +/// [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 with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin { - /// Creates an animation controller. /// /// * [value] is the initial value of the animation. If defaults to the lower diff --git a/packages/flutter/lib/src/animation/animations.dart b/packages/flutter/lib/src/animation/animations.dart index d0d6672e8b..a6c1c79a06 100644 --- a/packages/flutter/lib/src/animation/animations.dart +++ b/packages/flutter/lib/src/animation/animations.dart @@ -248,6 +248,13 @@ class ProxyAnimation extends Animation /// 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 /// 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 with AnimationLazyListenerMixin, AnimationLocalStatusListenersMixin { @@ -312,15 +319,8 @@ class ReverseAnimation extends Animation /// An animation that applies a curve to another animation. /// /// [CurvedAnimation] is useful when you want to apply a non-linear [Curve] to -/// an animation object wrapped in the [CurvedAnimation]. -/// -/// 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); -///``` +/// an animation object, especially if you want different curves when the +/// animation is going forward vs when it is going backward. /// /// Depending on the given curve, the output of the [CurvedAnimation] could have /// a wider range than its input. For example, elastic curves such as @@ -328,6 +328,42 @@ class ReverseAnimation extends Animation /// range of 0.0 to 1.0. /// /// 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 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 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 with AnimationWithParentMixin { /// Creates a curved animation. /// @@ -428,11 +464,19 @@ class CurvedAnimation extends Animation with AnimationWithParentMixin /// Creates a train-hopping animation. /// /// 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 }) : assert(_currentTrain != null) { if (_nextTrain != null) { - if (_currentTrain.value > _nextTrain.value) { + if (_currentTrain.value == _nextTrain.value) { + _currentTrain = _nextTrain; + _nextTrain = null; + } else if (_currentTrain.value > _nextTrain.value) { _mode = _TrainHoppingMode.maximize; } else { + assert(_currentTrain.value < _nextTrain.value); _mode = _TrainHoppingMode.minimize; - if (_currentTrain.value == _nextTrain.value) { - _currentTrain = _nextTrain; - _nextTrain = null; - } } } _currentTrain.addStatusListener(_statusChangeHandler); _currentTrain.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 get currentTrain => _currentTrain; Animation _currentTrain; Animation _nextTrain; _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; AnimationStatus _lastStatus; @@ -491,6 +543,7 @@ class TrainHoppingAnimation extends Animation assert(_currentTrain != null); bool hop = false; if (_nextTrain != null) { + assert(_mode != null); switch (_mode) { case _TrainHoppingMode.minimize: hop = _nextTrain.value <= _currentTrain.value; diff --git a/packages/flutter/lib/src/animation/curves.dart b/packages/flutter/lib/src/animation/curves.dart index 9e270ff6c5..f1596c5e28 100644 --- a/packages/flutter/lib/src/animation/curves.dart +++ b/packages/flutter/lib/src/animation/curves.dart @@ -6,11 +6,22 @@ import 'dart:math' as math; 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. /// -/// 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 abstract class Curve { /// Abstract const constructor. This constructor enables subclasses to provide @@ -26,7 +37,8 @@ abstract class Curve { double transform(double t); /// 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_flipped.mp4} @@ -34,6 +46,8 @@ abstract class Curve { /// See also: /// /// * [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); @override @@ -248,13 +262,21 @@ class Cubic extends 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 -/// increases from 0.0 to 1.0) and returns the inverse of the given curve's value -/// (i.e., 1.0 minus the given curve's value). +/// increases from 0.0 to 1.0) and returns the inverse of the given curve's +/// 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 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_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 { /// Creates a flipped curve. /// diff --git a/packages/flutter/lib/src/animation/tween.dart b/packages/flutter/lib/src/animation/tween.dart index 23f4100316..ee21292b22 100644 --- a/packages/flutter/lib/src/animation/tween.dart +++ b/packages/flutter/lib/src/animation/tween.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:ui' show Color, Size, Rect, hashValues; +import 'dart:ui' show Color, Size, Rect; import 'package:flutter/foundation.dart'; @@ -10,27 +10,65 @@ import 'animation.dart'; import 'animations.dart'; import 'curves.dart'; +// Examples can assume: +// Animation _animation; + /// An object that can produce a value of type `T` given an [Animation] /// as input. /// /// Typically, the values of the input animation are nominally in the range 0.0 /// to 1.0. In principle, however, any value could be provided. +/// +/// The main subclass of [Animatable] is [Tween]. abstract class Animatable { /// Abstract const constructor. This constructor enables subclasses to provide /// const constructors so that they can be used in const expressions. const Animatable(); - /// The current value of this object for the given animation. - T evaluate(Animation animation); + /// Returns the value of the object at point `t`. + /// + /// The value of `t` is nominally a fraction in the range 0.0 to 1.0, though + /// in practice it may extend outside this range. + /// + /// See also: + /// + /// * [evaluate], which is a shorthand for applying [transform] to the value + /// of an [Animation]. + /// * [Curve.transform], a similar method for easing curves. + T transform(double t); - /// Returns a new Animation that is driven by the given animation but that + /// The current value of this object for the given [Animation]. + /// + /// This function is implemented by deferring to [transform]. Subclasses that + /// want to provide custom behavior should override [transform], not + /// [evaluate]. + /// + /// See also: + /// + /// * [transform], which is similar but takes a `t` value directly instead of + /// an [Animation]. + /// * [animate], which creates an [Animation] out of this object, continually + /// applying [evaluate]. + T evaluate(Animation animation) => transform(animation.value); + + /// Returns a new [Animation] that is driven by the given animation but that /// takes on values determined by this object. + /// + /// Essentially this returns an [Animation] that automatically applies the + /// [evaluate] method to the parent's value. + /// + /// See also: + /// + /// * [AnimationController.drive], which does the same thing from the + /// opposite starting point. Animation animate(Animation parent) { return _AnimatedEvaluation(parent, this); } - /// Returns a new Animatable whose value is determined by first evaluating + /// Returns a new [Animatable] whose value is determined by first evaluating /// the given parent and then evaluating this object. + /// + /// This allows [Tween]s to be chained before obtaining an [Animation]. Animatable chain(Animatable parent) { return _ChainedEvaluation(parent, this); } @@ -65,9 +103,8 @@ class _ChainedEvaluation extends Animatable { final Animatable _evaluatable; @override - T evaluate(Animation animation) { - final double value = _parent.evaluate(animation); - return _evaluatable.evaluate(AlwaysStoppedAnimation(value)); + T transform(double t) { + return _evaluatable.transform(_parent.transform(t)); } @override @@ -87,27 +124,85 @@ class _ChainedEvaluation extends Animatable { /// You can chain [Tween] objects together using the [chain] method, so that a /// single [Animation] object is configured by multiple [Tween] objects called /// in succession. This is different than calling the [animate] method twice, -/// which results in two [Animation] separate objects, each configured with a +/// which results in two separate [Animation] objects, each configured with a /// single [Tween]. /// /// ## Sample code /// /// Suppose `_controller` is an [AnimationController], and we want to create an /// [Animation] that is controlled by that controller, and save it in -/// `_animation`: +/// `_animation`. Here are two possible ways of expressing this: /// /// ```dart -/// Animation _animation = Tween( +/// _animation = _controller.drive( +/// Tween( +/// begin: const Offset(100.0, 50.0), +/// end: const Offset(200.0, 300.0), +/// ), +/// ); +/// ``` +/// +/// ```dart +/// _animation = Tween( /// begin: const Offset(100.0, 50.0), /// end: const Offset(200.0, 300.0), /// ).animate(_controller); /// ``` /// -/// That would provide an `_animation` that, over the lifetime of the -/// `_controller`'s animation, returns a value that depicts a point along the -/// line between the two offsets above. If we used a [MaterialPointArcTween] -/// instead of a [Tween] in the code above, the points would follow a -/// pleasing curve instead of a straight line, with no other changes necessary. +/// In both cases, the `_animation` variable holds an object that, over the +/// lifetime of the `_controller`'s animation, returns a value +/// (`_animation.value`) that depicts a point along the line between the two +/// offsets above. If we used a [MaterialPointArcTween] instead of a +/// [Tween] in the code above, the points would follow a pleasing curve +/// instead of a straight line, with no other changes necessary. +/// +/// ## Performance optimizations +/// +/// Tweens are mutable; specifically, their [begin] and [end] values can be +/// changed at runtime. An object created with [Animation.drive] using a [Tween] +/// will immediately honor changes to that underlying [Tween] (though the +/// listeners will only be triggered if the [Animation] is actively animating). +/// This can be used to change an animation on the fly without having to +/// recreate all the objects in the chain from the [AnimationController] to the +/// final [Tween]. +/// +/// If a [Tween]'s values are never changed, however, a further optimisation can +/// be applied: the object can be stored in a `static final` variable, so that +/// the exact same instance is used whenever the [Tween] is needed. This is +/// preferable to creating an identical [Tween] afresh each time a [State.build] +/// method is called, for example. +/// +/// ## Types with special considerations +/// +/// Classes with [lerp] static methods typically have corresponding dedicated +/// [Tween] subclasses that call that method. For example, [ColorTween] uses +/// [Color.lerp] to implement the [ColorTween.lerp] method. +/// +/// Types that define `+` and `-` operators to combine values (`T + T → T` and +/// `T - T → T`) and an `*` operator to scale by multiplying with a double (`T * +/// double → T`) can be directly used with `Tween`. +/// +/// This does not extend to any type with `+`, `-`, and `*` operators. In +/// particular, [int] does not satisfy this precise contract (`int * double` +/// actually returns [num], not [int]). There are therefore two specific classes +/// that can be used to interpolate integers: +/// +/// * [IntTween], which is an approximation of a linear interpolation (using +/// [double.round]). +/// * [StepTween], which uses [double.floor] to ensure that the result is +/// never greater than it would be using if a `Tween`. +/// +/// The relevant operators on [Size] also don't fulfill this contract, so +/// [SizeTween] uses [Size.lerp]. +/// +/// In addition, some of the types that _do_ have suitable `+`, `-`, and `*` +/// operators still have dedicated [Tween] subclasses that perform the +/// interpolation in a more specialized manner. One such class is +/// [MaterialPointArcTween], which is mentioned above. The [AlignmentTween], and +/// [AlignmentGeometryTween], and [FractionalOffsetTween] are another group of +/// [Tween]s that use dedicated `lerp` methods instead of merely relying on the +/// operators (in particular, this allows them to handle null values in a more +/// useful manner). class Tween extends Animatable { /// Creates a tween. /// @@ -133,6 +228,7 @@ class Tween extends Animatable { /// The default implementation of this method uses the [+], [-], and [*] /// operators on `T`. The [begin] and [end] properties must therefore be /// non-null by the time this method is called. + @protected T lerp(double t) { assert(begin != null); assert(end != null); @@ -144,15 +240,15 @@ class Tween extends Animatable { /// This method returns `begin` and `end` when the animation values are 0.0 or /// 1.0, respectively. /// - /// This function is implemented by deferring to [lerp]. Subclasses that want to - /// provide custom behavior should override [lerp], not [evaluate]. + /// This function is implemented by deferring to [lerp]. Subclasses that want + /// to provide custom behavior should override [lerp], not [transform] (nor + /// [evaluate]). /// /// See the constructor for details about whether the [begin] and [end] /// properties may be null when this is called. It varies from subclass to /// subclass. @override - T evaluate(Animation animation) { - final double t = animation.value; + T transform(double t) { if (t == 0.0) return begin; if (t == 1.0) @@ -160,20 +256,6 @@ class Tween extends Animatable { return lerp(t); } - @override - bool operator ==(dynamic other) { - if (identical(this, other)) - return true; - if (other.runtimeType != runtimeType) - return false; - final Tween typedOther = other; - return begin == typedOther.begin - && end == typedOther.end; - } - - @override - int get hashCode => hashValues(begin, end); - @override String toString() => '$runtimeType($begin \u2192 $end)'; } @@ -282,7 +364,7 @@ class IntTween extends Tween { /// /// This class specializes the interpolation of [Tween] to be /// appropriate for integers by interpolating between the given begin -/// and end values and then using [int.floor] to return the current +/// and end values and then using [double.floor] to return the current /// integer component, dropping the fractional component. /// /// This results in a value that is never greater than the equivalent @@ -321,6 +403,26 @@ class ConstantTween extends Tween { /// This class differs from [CurvedAnimation] in that [CurvedAnimation] applies /// a curve to an existing [Animation] object whereas [CurveTween] can be /// chained with another [Tween] prior to receiving the underlying [Animation]. +/// ([CurvedAnimation] also has the additional ability of having different +/// curves when the animation is going forward vs when it is going backward, +/// which can be useful in some scenarios.) +/// +/// ## 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 animation = controller.drive( +/// CurveTween(curve: Curves.ease), +/// ); +/// ``` +/// +/// See also: +/// +/// * [CurvedAnimation], for an alternative way of expressing the sample above. +/// * [AnimationController], for examples of creating and disposing of an +/// [AnimationController]. class CurveTween extends Animatable { /// Creates a curve tween. /// @@ -332,8 +434,7 @@ class CurveTween extends Animatable { Curve curve; @override - double evaluate(Animation animation) { - final double t = animation.value; + double transform(double t) { if (t == 0.0 || t == 1.0) { assert(curve.transform(t).round() == t); return t; diff --git a/packages/flutter/lib/src/animation/tween_sequence.dart b/packages/flutter/lib/src/animation/tween_sequence.dart index cb2b1de491..1ff88614bd 100644 --- a/packages/flutter/lib/src/animation/tween_sequence.dart +++ b/packages/flutter/lib/src/animation/tween_sequence.dart @@ -1,7 +1,6 @@ import 'package:flutter/foundation.dart'; import 'animation.dart'; -import 'animations.dart'; import 'tween.dart'; /// Enables creating an [Animation] whose value is defined by a @@ -67,12 +66,11 @@ class TweenSequence extends Animatable { T _evaluateAt(double t, int index) { final TweenSequenceItem element = _items[index]; final double tInterval = _intervals[index].value(t); - return element.tween.evaluate(AlwaysStoppedAnimation(tInterval)); + return element.tween.transform(tInterval); } @override - T evaluate(Animation animation) { - final double t = animation.value; + T transform(double t) { assert(t >= 0.0 && t <= 1.0); if (t == 1.0) return _evaluateAt(t, _items.length - 1); @@ -100,6 +98,8 @@ class TweenSequenceItem { /// animation's duration indicated by [weight] and this item's position /// in the list of items. /// + /// ## Sample code + /// /// 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: /// diff --git a/packages/flutter/lib/src/cupertino/button.dart b/packages/flutter/lib/src/cupertino/button.dart index 817b6f0e63..046cc113d4 100644 --- a/packages/flutter/lib/src/cupertino/button.dart +++ b/packages/flutter/lib/src/cupertino/button.dart @@ -125,16 +125,10 @@ class _CupertinoButtonState extends State with SingleTickerProv // Eyeballed values. Feel free to tweak. static const Duration kFadeOutDuration = Duration(milliseconds: 10); static const Duration kFadeInDuration = Duration(milliseconds: 100); - Tween _opacityTween; + final Tween _opacityTween = Tween(begin: 1.0); AnimationController _animationController; - - void _setTween() { - _opacityTween = Tween( - begin: 1.0, - end: widget.pressedOpacity ?? 1.0, - ); - } + Animation _opacityAnimation; @override void initState() { @@ -144,9 +138,22 @@ class _CupertinoButtonState extends State with SingleTickerProv value: 0.0, vsync: this, ); + _opacityAnimation = _animationController + .drive(CurveTween(curve: Curves.decelerate)) + .drive(_opacityTween); _setTween(); } + @override + void didUpdateWidget(CupertinoButton old) { + super.didUpdateWidget(old); + _setTween(); + } + + void _setTween() { + _opacityTween.end = widget.pressedOpacity ?? 1.0; + } + @override void dispose() { _animationController.dispose(); @@ -154,12 +161,6 @@ class _CupertinoButtonState extends State with SingleTickerProv super.dispose(); } - @override - void didUpdateWidget(CupertinoButton old) { - super.didUpdateWidget(old); - _setTween(); - } - bool _buttonHeldDown = false; void _handleTapDown(TapDownDetails event) { @@ -217,10 +218,7 @@ class _CupertinoButtonState extends State with SingleTickerProv minHeight: widget.minSize, ), child: FadeTransition( - opacity: _opacityTween.animate(CurvedAnimation( - parent: _animationController, - curve: Curves.decelerate, - )), + opacity: _opacityAnimation, child: DecoratedBox( decoration: BoxDecoration( borderRadius: widget.borderRadius, diff --git a/packages/flutter/lib/src/cupertino/nav_bar.dart b/packages/flutter/lib/src/cupertino/nav_bar.dart index 104df40fe4..594cbbef7d 100644 --- a/packages/flutter/lib/src/cupertino/nav_bar.dart +++ b/packages/flutter/lib/src/cupertino/nav_bar.dart @@ -1520,11 +1520,11 @@ class _NavigationBarComponentsTransition { // paintBounds are based on offset zero so it's ok to expand the Rects. bottomNavBar.renderBox.paintBounds.expandToInclude(topNavBar.renderBox.paintBounds); - static final Tween fadeOut = Tween( + static final Animatable fadeOut = Tween( begin: 1.0, end: 0.0, ); - static final Tween fadeIn = Tween( + static final Animatable fadeIn = Tween( begin: 0.0, end: 1.0, ); @@ -1601,15 +1601,15 @@ class _NavigationBarComponentsTransition { } Animation fadeInFrom(double t, { Curve curve = Curves.easeIn }) { - return fadeIn.animate( - CurvedAnimation(curve: Interval(t, 1.0, curve: curve), parent: animation), - ); + return animation.drive(fadeIn.chain( + CurveTween(curve: Interval(t, 1.0, curve: curve)), + )); } Animation fadeOutBy(double t, { Curve curve = Curves.easeOut }) { - return fadeOut.animate( - CurvedAnimation(curve: Interval(0.0, t, curve: curve), parent: animation), - ); + return animation.drive(fadeOut.chain( + CurveTween(curve: Interval(0.0, t, curve: curve)), + )); } Widget get bottomLeading { @@ -1663,7 +1663,7 @@ class _NavigationBarComponentsTransition { ); return PositionedTransition( - rect: positionTween.animate(animation), + rect: animation.drive(positionTween), child: FadeTransition( opacity: fadeOutBy(0.2), child: DefaultTextStyle( @@ -1687,12 +1687,12 @@ class _NavigationBarComponentsTransition { if (bottomMiddle != null && topBackLabel != null) { return PositionedTransition( - rect: slideFromLeadingEdge( + rect: animation.drive(slideFromLeadingEdge( fromKey: bottomComponents.middleKey, fromNavBarBox: bottomNavBarBox, toKey: topComponents.backLabelKey, toNavBarBox: topNavBarBox, - ).animate(animation), + )), child: FadeTransition( // A custom middle widget like a segmented control fades away faster. opacity: fadeOutBy(bottomHasUserMiddle ? 0.4 : 0.7), @@ -1701,10 +1701,10 @@ class _NavigationBarComponentsTransition { // edge of a constantly sized outer box. alignment: AlignmentDirectional.centerStart, child: DefaultTextStyleTransition( - style: TextStyleTween( + style: animation.drive(TextStyleTween( begin: _kMiddleTitleTextStyle, end: topActionsStyle, - ).animate(animation), + )), child: bottomMiddle.child, ), ), @@ -1742,12 +1742,12 @@ class _NavigationBarComponentsTransition { if (bottomLargeTitle != null && topBackLabel != null) { return PositionedTransition( - rect: slideFromLeadingEdge( + rect: animation.drive(slideFromLeadingEdge( fromKey: bottomComponents.largeTitleKey, fromNavBarBox: bottomNavBarBox, toKey: topComponents.backLabelKey, toNavBarBox: topNavBarBox, - ).animate(animation), + )), child: FadeTransition( opacity: fadeOutBy(0.6), child: Align( @@ -1755,10 +1755,10 @@ class _NavigationBarComponentsTransition { // edge of a constantly sized outer box. alignment: AlignmentDirectional.centerStart, child: DefaultTextStyleTransition( - style: TextStyleTween( + style: animation.drive(TextStyleTween( begin: _kLargeTitleTextStyle, end: topActionsStyle, - ).animate(animation), + )), maxLines: 1, overflow: TextOverflow.ellipsis, child: bottomLargeTitle.child, @@ -1779,7 +1779,7 @@ class _NavigationBarComponentsTransition { // Just shift slightly towards the right instead of moving to the back // label position. return PositionedTransition( - rect: positionTween.animate(animation), + rect: animation.drive(positionTween), child: FadeTransition( opacity: fadeOutBy(0.4), // Keep the font when transitioning into a non-back-label leading. @@ -1850,7 +1850,7 @@ class _NavigationBarComponentsTransition { ); return PositionedTransition( - rect: positionTween.animate(animation), + rect: animation.drive(positionTween), child: FadeTransition( opacity: fadeInFrom(bottomBackChevron == null ? 0.7 : 0.4), child: DefaultTextStyle( @@ -1877,10 +1877,10 @@ class _NavigationBarComponentsTransition { Animation midClickOpacity; if (topBackLabelOpacity != null && topBackLabelOpacity.opacity.value < 1.0) { - midClickOpacity = Tween( + midClickOpacity = animation.drive(Tween( begin: 0.0, end: topBackLabelOpacity.opacity.value, - ).animate(animation); + )); } // Pick up from an incoming transition from the large title. This is @@ -1893,19 +1893,19 @@ class _NavigationBarComponentsTransition { bottomLargeExpanded ) { return PositionedTransition( - rect: slideFromLeadingEdge( + rect: animation.drive(slideFromLeadingEdge( fromKey: bottomComponents.largeTitleKey, fromNavBarBox: bottomNavBarBox, toKey: topComponents.backLabelKey, toNavBarBox: topNavBarBox, - ).animate(animation), + )), child: FadeTransition( opacity: midClickOpacity ?? fadeInFrom(0.4), child: DefaultTextStyleTransition( - style: TextStyleTween( + style: animation.drive(TextStyleTween( begin: _kLargeTitleTextStyle, end: topActionsStyle, - ).animate(animation), + )), maxLines: 1, overflow: TextOverflow.ellipsis, child: topBackLabel.child, @@ -1918,19 +1918,19 @@ class _NavigationBarComponentsTransition { // and expanded instead of middle. if (bottomMiddle != null && topBackLabel != null) { return PositionedTransition( - rect: slideFromLeadingEdge( + rect: animation.drive(slideFromLeadingEdge( fromKey: bottomComponents.middleKey, fromNavBarBox: bottomNavBarBox, toKey: topComponents.backLabelKey, toNavBarBox: topNavBarBox, - ).animate(animation), + )), child: FadeTransition( opacity: midClickOpacity ?? fadeInFrom(0.3), child: DefaultTextStyleTransition( - style: TextStyleTween( + style: animation.drive(TextStyleTween( begin: _kMiddleTitleTextStyle, end: topActionsStyle, - ).animate(animation), + )), child: topBackLabel.child, ), ), @@ -1962,7 +1962,7 @@ class _NavigationBarComponentsTransition { ); return PositionedTransition( - rect: positionTween.animate(animation), + rect: animation.drive(positionTween), child: FadeTransition( opacity: fadeInFrom(0.25), child: DefaultTextStyle( @@ -2005,7 +2005,7 @@ class _NavigationBarComponentsTransition { ); return PositionedTransition( - rect: positionTween.animate(animation), + rect: animation.drive(positionTween), child: FadeTransition( opacity: fadeInFrom(0.3), child: DefaultTextStyle( diff --git a/packages/flutter/lib/src/cupertino/route.dart b/packages/flutter/lib/src/cupertino/route.dart index 2f33cb5a15..fc7103d89c 100644 --- a/packages/flutter/lib/src/cupertino/route.dart +++ b/packages/flutter/lib/src/cupertino/route.dart @@ -19,19 +19,19 @@ const Color _kModalBarrierColor = Color(0x6604040F); const Duration _kModalPopupTransitionDuration = Duration(milliseconds: 335); // Offset from offscreen to the right to fully on screen. -final Tween _kRightMiddleTween = Tween( +final Animatable _kRightMiddleTween = Tween( begin: const Offset(1.0, 0.0), end: Offset.zero, ); // Offset from fully on screen to 1/3 offscreen to the left. -final Tween _kMiddleLeftTween = Tween( +final Animatable _kMiddleLeftTween = Tween( begin: Offset.zero, end: const Offset(-1.0/3.0, 0.0), ); // Offset from offscreen below to fully on screen. -final Tween _kBottomUpTween = Tween( +final Animatable _kBottomUpTween = Tween( begin: const Offset(0.0, 1.0), end: Offset.zero, ); @@ -354,28 +354,22 @@ class CupertinoPageTransition extends StatelessWidget { @required this.child, @required bool linearTransition, }) : assert(linearTransition != null), - _primaryPositionAnimation = linearTransition - ? _kRightMiddleTween.animate(primaryRouteAnimation) - : _kRightMiddleTween.animate( - CurvedAnimation( - parent: primaryRouteAnimation, - curve: Curves.easeOut, - reverseCurve: Curves.easeIn, - ) - ), - _secondaryPositionAnimation = _kMiddleLeftTween.animate( - CurvedAnimation( - parent: secondaryRouteAnimation, - curve: Curves.easeOut, - reverseCurve: Curves.easeIn, - ) - ), - _primaryShadowAnimation = _kGradientShadowTween.animate( + _primaryPositionAnimation = (linearTransition ? primaryRouteAnimation : CurvedAnimation( parent: primaryRouteAnimation, curve: Curves.easeOut, + reverseCurve: Curves.easeIn, ) - ), + ).drive(_kRightMiddleTween), + _secondaryPositionAnimation = CurvedAnimation( + parent: secondaryRouteAnimation, + curve: Curves.easeOut, + reverseCurve: Curves.easeIn, + ).drive(_kMiddleLeftTween), + _primaryShadowAnimation = CurvedAnimation( + parent: primaryRouteAnimation, + curve: Curves.easeOut, + ).drive(_kGradientShadowTween), super(key: key); // When this page is coming in to cover another page. @@ -418,12 +412,9 @@ class CupertinoFullscreenDialogTransition extends StatelessWidget { Key key, @required Animation animation, @required this.child, - }) : _positionAnimation = _kBottomUpTween.animate( - CurvedAnimation( - parent: animation, - curve: Curves.easeInOut, - ) - ), + }) : _positionAnimation = animation + .drive(CurveTween(curve: Curves.easeInOut)) + .drive(_kBottomUpTween), super(key: key); final Animation _positionAnimation; @@ -859,6 +850,9 @@ Future showCupertinoModalPopup({ ); } +final Animatable _dialogTween = Tween(begin: 1.2, end: 1.0) + .chain(CurveTween(curve: Curves.fastOutSlowIn)); + Widget _buildCupertinoDialogTransitions(BuildContext context, Animation animation, Animation secondaryAnimation, Widget child) { final CurvedAnimation fadeAnimation = CurvedAnimation( parent: animation, @@ -874,15 +868,7 @@ Widget _buildCupertinoDialogTransitions(BuildContext context, Animation opacity: fadeAnimation, child: ScaleTransition( child: child, - scale: Tween( - begin: 1.2, - end: 1.0, - ).animate( - CurvedAnimation( - parent: animation, - curve: Curves.fastOutSlowIn, - ), - ), + scale: animation.drive(_dialogTween), ), ); } diff --git a/packages/flutter/lib/src/material/bottom_navigation_bar.dart b/packages/flutter/lib/src/material/bottom_navigation_bar.dart index 8ec520ca71..1116501fe6 100644 --- a/packages/flutter/lib/src/material/bottom_navigation_bar.dart +++ b/packages/flutter/lib/src/material/bottom_navigation_bar.dart @@ -316,7 +316,7 @@ class _BottomNavigationBarState extends State with TickerPr // animation is complete. Color _backgroundColor; - static final Tween _flexTween = Tween(begin: 1.0, end: 1.5); + static final Animatable _flexTween = Tween(begin: 1.0, end: 1.5); void _resetState() { for (AnimationController controller in _controllers) @@ -655,7 +655,7 @@ class _RadialPainter extends CustomPainter { ); canvas.drawCircle( center, - radiusTween.lerp(circle.animation.value), + radiusTween.transform(circle.animation.value), paint, ); } diff --git a/packages/flutter/lib/src/material/chip.dart b/packages/flutter/lib/src/material/chip.dart index 2d737ace25..f80ee1df84 100644 --- a/packages/flutter/lib/src/material/chip.dart +++ b/packages/flutter/lib/src/material/chip.dart @@ -1220,7 +1220,7 @@ class _RawChipState extends State with TickerProviderStateMixin enableAnimation; Animation selectionFade; - static final Tween pressedShadowTween = Tween( + static final Animatable pressedShadowTween = Tween( begin: 0.0, end: _kPressElevation, ); diff --git a/packages/flutter/lib/src/material/data_table.dart b/packages/flutter/lib/src/material/data_table.dart index f76be29abb..578d01c823 100644 --- a/packages/flutter/lib/src/material/data_table.dart +++ b/packages/flutter/lib/src/material/data_table.dart @@ -694,6 +694,9 @@ class _SortArrowState extends State<_SortArrow> with TickerProviderStateMixin { bool _down; + static final Animatable _turnTween = Tween(begin: 0.0, end: math.pi) + .chain(CurveTween(curve: Curves.easeIn)); + @override void initState() { super.initState(); @@ -706,18 +709,13 @@ class _SortArrowState extends State<_SortArrow> with TickerProviderStateMixin { ) ..addListener(_rebuild); _opacityController.value = widget.visible ? 1.0 : 0.0; - _orientationAnimation = Tween( - begin: 0.0, - end: math.pi, - ).animate(CurvedAnimation( - parent: _orientationController = AnimationController( - duration: widget.duration, - vsync: this, - ), - curve: Curves.easeIn - )) - ..addListener(_rebuild) - ..addStatusListener(_resetOrientationAnimation); + _orientationController = AnimationController( + duration: widget.duration, + vsync: this, + ); + _orientationAnimation = _orientationController.drive(_turnTween) + ..addListener(_rebuild) + ..addStatusListener(_resetOrientationAnimation); if (widget.visible) _orientationOffset = widget.down ? 0.0 : math.pi; } diff --git a/packages/flutter/lib/src/material/date_picker.dart b/packages/flutter/lib/src/material/date_picker.dart index f6c1e7b685..602b9bd90b 100644 --- a/packages/flutter/lib/src/material/date_picker.dart +++ b/packages/flutter/lib/src/material/date_picker.dart @@ -525,6 +525,9 @@ class MonthPicker extends StatefulWidget { } class _MonthPickerState extends State with SingleTickerProviderStateMixin { + static final Animatable _chevronOpacityTween = Tween(begin: 1.0, end: 0.0) + .chain(CurveTween(curve: Curves.easeInOut)); + @override void initState() { super.initState(); @@ -538,12 +541,7 @@ class _MonthPickerState extends State with SingleTickerProviderStat _chevronOpacityController = AnimationController( duration: const Duration(milliseconds: 250), vsync: this ); - _chevronOpacityAnimation = Tween(begin: 1.0, end: 0.0).animate( - CurvedAnimation( - parent: _chevronOpacityController, - curve: Curves.easeInOut, - ) - ); + _chevronOpacityAnimation = _chevronOpacityController.drive(_chevronOpacityTween); } @override diff --git a/packages/flutter/lib/src/material/expand_icon.dart b/packages/flutter/lib/src/material/expand_icon.dart index ee87f5ee0f..943767c9d2 100644 --- a/packages/flutter/lib/src/material/expand_icon.dart +++ b/packages/flutter/lib/src/material/expand_icon.dart @@ -67,16 +67,14 @@ class _ExpandIconState extends State with SingleTickerProviderStateM AnimationController _controller; Animation _iconTurns; + static final Animatable _iconTurnTween = Tween(begin: 0.0, end: 0.5) + .chain(CurveTween(curve: Curves.fastOutSlowIn)); + @override void initState() { super.initState(); _controller = AnimationController(duration: kThemeAnimationDuration, vsync: this); - _iconTurns = Tween(begin: 0.0, end: 0.5).animate( - CurvedAnimation( - parent: _controller, - curve: Curves.fastOutSlowIn - ) - ); + _iconTurns = _controller.drive(_iconTurnTween); // If the widget is initially expanded, rotate the icon without animating it. if (widget.isExpanded) { _controller.value = math.pi; diff --git a/packages/flutter/lib/src/material/expansion_tile.dart b/packages/flutter/lib/src/material/expansion_tile.dart index 0254c6188e..b9f91416f0 100644 --- a/packages/flutter/lib/src/material/expansion_tile.dart +++ b/packages/flutter/lib/src/material/expansion_tile.dart @@ -79,14 +79,22 @@ class ExpansionTile extends StatefulWidget { } class _ExpansionTileState extends State with SingleTickerProviderStateMixin { + static final Animatable _easeOutTween = CurveTween(curve: Curves.easeOut); + static final Animatable _easeInTween = CurveTween(curve: Curves.easeIn); + static final Animatable _halfTween = Tween(begin: 0.0, end: 0.5); + + final ColorTween _borderColorTween = ColorTween(); + final ColorTween _headerColorTween = ColorTween(); + final ColorTween _iconColorTween = ColorTween(); + final ColorTween _backgroundColorTween = ColorTween(); + AnimationController _controller; - CurvedAnimation _easeOutAnimation; - CurvedAnimation _easeInAnimation; - ColorTween _borderColor; - ColorTween _headerColor; - ColorTween _iconColor; - ColorTween _backgroundColor; Animation _iconTurns; + Animation _heightFactor; + Animation _borderColor; + Animation _headerColor; + Animation _iconColor; + Animation _backgroundColor; bool _isExpanded = false; @@ -94,13 +102,12 @@ class _ExpansionTileState extends State with SingleTickerProvider void initState() { super.initState(); _controller = AnimationController(duration: _kExpand, vsync: this); - _easeOutAnimation = CurvedAnimation(parent: _controller, curve: Curves.easeOut); - _easeInAnimation = CurvedAnimation(parent: _controller, curve: Curves.easeIn); - _borderColor = ColorTween(); - _headerColor = ColorTween(); - _iconColor = ColorTween(); - _iconTurns = Tween(begin: 0.0, end: 0.5).animate(_easeInAnimation); - _backgroundColor = ColorTween(); + _heightFactor = _controller.drive(_easeInTween); + _iconTurns = _controller.drive(_halfTween.chain(_easeInTween)); + _borderColor = _controller.drive(_borderColorTween.chain(_easeOutTween)); + _headerColor = _controller.drive(_headerColorTween.chain(_easeInTween)); + _iconColor = _controller.drive(_iconColorTween.chain(_easeInTween)); + _backgroundColor = _controller.drive(_backgroundColorTween.chain(_easeOutTween)); _isExpanded = PageStorage.of(context)?.readState(context) ?? widget.initiallyExpanded; if (_isExpanded) @@ -116,14 +123,17 @@ class _ExpansionTileState extends State with SingleTickerProvider void _handleTap() { setState(() { _isExpanded = !_isExpanded; - if (_isExpanded) + if (_isExpanded) { _controller.forward(); - else - _controller.reverse().then((Null value) { + } else { + _controller.reverse().then((void value) { + if (!mounted) + return; setState(() { // Rebuild without widget.children. }); }); + } PageStorage.of(context)?.writeState(context, _isExpanded); }); if (widget.onExpansionChanged != null) @@ -131,12 +141,12 @@ class _ExpansionTileState extends State with SingleTickerProvider } Widget _buildChildren(BuildContext context, Widget child) { - final Color borderSideColor = _borderColor.evaluate(_easeOutAnimation) ?? Colors.transparent; - final Color titleColor = _headerColor.evaluate(_easeInAnimation); + final Color borderSideColor = _borderColor.value ?? Colors.transparent; + final Color titleColor = _headerColor.value; return Container( decoration: BoxDecoration( - color: _backgroundColor.evaluate(_easeOutAnimation) ?? Colors.transparent, + color: _backgroundColor.value ?? Colors.transparent, border: Border( top: BorderSide(color: borderSideColor), bottom: BorderSide(color: borderSideColor), @@ -146,7 +156,7 @@ class _ExpansionTileState extends State with SingleTickerProvider mainAxisSize: MainAxisSize.min, children: [ IconTheme.merge( - data: IconThemeData(color: _iconColor.evaluate(_easeInAnimation)), + data: IconThemeData(color: _iconColor.value), child: ListTile( onTap: _handleTap, leading: widget.leading, @@ -162,7 +172,7 @@ class _ExpansionTileState extends State with SingleTickerProvider ), ClipRect( child: Align( - heightFactor: _easeInAnimation.value, + heightFactor: _heightFactor.value, child: child, ), ), @@ -172,17 +182,23 @@ class _ExpansionTileState extends State with SingleTickerProvider } @override - Widget build(BuildContext context) { + void didChangeDependencies() { final ThemeData theme = Theme.of(context); - _borderColor.end = theme.dividerColor; - _headerColor + _borderColorTween + ..end = theme.dividerColor; + _headerColorTween ..begin = theme.textTheme.subhead.color ..end = theme.accentColor; - _iconColor + _iconColorTween ..begin = theme.unselectedWidgetColor ..end = theme.accentColor; - _backgroundColor.end = widget.backgroundColor; + _backgroundColorTween + ..end = widget.backgroundColor; + super.didChangeDependencies(); + } + @override + Widget build(BuildContext context) { final bool closed = !_isExpanded && _controller.isDismissed; return AnimatedBuilder( animation: _controller.view, diff --git a/packages/flutter/lib/src/material/flexible_space_bar.dart b/packages/flutter/lib/src/material/flexible_space_bar.dart index a53abd743a..1f37b47668 100644 --- a/packages/flutter/lib/src/material/flexible_space_bar.dart +++ b/packages/flutter/lib/src/material/flexible_space_bar.dart @@ -133,7 +133,7 @@ class _FlexibleSpaceBarState extends State { return 0.0; case CollapseMode.parallax: final double deltaExtent = settings.maxExtent - settings.minExtent; - return -Tween(begin: 0.0, end: deltaExtent / 4.0).lerp(t); + return -Tween(begin: 0.0, end: deltaExtent / 4.0).transform(t); } return null; } @@ -193,7 +193,7 @@ class _FlexibleSpaceBarState extends State { color: titleStyle.color.withOpacity(opacity) ); final bool effectiveCenterTitle = _getEffectiveCenterTitle(theme); - final double scaleValue = Tween(begin: 1.5, end: 1.0).lerp(t); + final double scaleValue = Tween(begin: 1.5, end: 1.0).transform(t); final Matrix4 scaleTransform = Matrix4.identity() ..scale(scaleValue, scaleValue, 1.0); final Alignment titleAlignment = _getTitleAlignment(effectiveCenterTitle); diff --git a/packages/flutter/lib/src/material/floating_action_button_location.dart b/packages/flutter/lib/src/material/floating_action_button_location.dart index 5f4d196bfa..3da2248ca2 100644 --- a/packages/flutter/lib/src/material/floating_action_button_location.dart +++ b/packages/flutter/lib/src/material/floating_action_button_location.dart @@ -332,25 +332,28 @@ class _ScalingFabMotionAnimator extends FloatingActionButtonAnimator { // then from 0 back to 1 in the second half. const Curve curve = Interval(0.5, 1.0, curve: Curves.ease); return _AnimationSwap( - ReverseAnimation(CurveTween(curve: curve.flipped).animate(parent)), - CurveTween(curve: curve).animate(parent), + ReverseAnimation(parent.drive(CurveTween(curve: curve.flipped))), + parent.drive(CurveTween(curve: curve)), parent, 0.5, ); } + // Because we only see the last half of the rotation tween, + // it needs to go twice as far. + static final Animatable _rotationTween = Tween( + begin: 1.0 - kFloatingActionButtonTurnInterval * 2.0, + end: 1.0, + ); + + static final Animatable _thresholdCenterTween = CurveTween(curve: const Threshold(0.5)); + @override Animation getRotationAnimation({Animation parent}) { - // Because we only see the last half of the rotation tween, - // it needs to go twice as far. - final Tween rotationTween = Tween( - begin: 1.0 - kFloatingActionButtonTurnInterval * 2, - end: 1.0, - ); // This rotation will turn on the way in, but not on the way out. return _AnimationSwap( - rotationTween.animate(parent), - ReverseAnimation(CurveTween(curve: const Threshold(0.5)).animate(parent)), + parent.drive(_rotationTween), + ReverseAnimation(parent.drive(_thresholdCenterTween)), parent, 0.5, ); diff --git a/packages/flutter/lib/src/material/ink_highlight.dart b/packages/flutter/lib/src/material/ink_highlight.dart index 591654ca80..23cad86c0e 100644 --- a/packages/flutter/lib/src/material/ink_highlight.dart +++ b/packages/flutter/lib/src/material/ink_highlight.dart @@ -58,10 +58,10 @@ class InkHighlight extends InteractiveInkFeature { ..addListener(controller.markNeedsPaint) ..addStatusListener(_handleAlphaStatusChanged) ..forward(); - _alpha = IntTween( + _alpha = _alphaController.drive(IntTween( begin: 0, - end: color.alpha - ).animate(_alphaController); + end: color.alpha, + )); controller.addInkFeature(this); } diff --git a/packages/flutter/lib/src/material/ink_ripple.dart b/packages/flutter/lib/src/material/ink_ripple.dart index 0cae37b5fc..b42f90ece2 100644 --- a/packages/flutter/lib/src/material/ink_ripple.dart +++ b/packages/flutter/lib/src/material/ink_ripple.dart @@ -96,6 +96,9 @@ class InkRipple extends InteractiveInkFeature { /// or material [Theme]. static const InteractiveInkFeatureFactory splashFactory = _InkRippleFactory(); + static final Animatable _easeCurveTween = CurveTween(curve: Curves.ease); + static final Animatable _fadeOutIntervalTween = CurveTween(curve: const Interval(_kFadeOutIntervalStart, 1.0)); + /// Begin a ripple, centered at [position] relative to [referenceBox]. /// /// The [controller] argument is typically obtained via @@ -140,10 +143,10 @@ class InkRipple extends InteractiveInkFeature { _fadeInController = AnimationController(duration: _kFadeInDuration, vsync: controller.vsync) ..addListener(controller.markNeedsPaint) ..forward(); - _fadeIn = IntTween( + _fadeIn = _fadeInController.drive(IntTween( begin: 0, end: color.alpha, - ).animate(_fadeInController); + )); // Controls the splash radius and its center. Starts upon confirm. _radiusController = AnimationController(duration: _kUnconfirmedRippleDuration, vsync: controller.vsync) @@ -151,14 +154,11 @@ class InkRipple extends InteractiveInkFeature { ..forward(); // Initial splash diameter is 60% of the target diameter, final // diameter is 10dps larger than the target diameter. - _radius = Tween( - begin: _targetRadius * 0.30, - end: _targetRadius + 5.0, - ).animate( - CurvedAnimation( - parent: _radiusController, - curve: Curves.ease, - ) + _radius = _radiusController.drive( + Tween( + begin: _targetRadius * 0.30, + end: _targetRadius + 5.0, + ).chain(_easeCurveTween), ); // Controls the splash radius and its center. Starts upon confirm however its @@ -166,14 +166,11 @@ class InkRipple extends InteractiveInkFeature { _fadeOutController = AnimationController(duration: _kFadeOutDuration, vsync: controller.vsync) ..addListener(controller.markNeedsPaint) ..addStatusListener(_handleAlphaStatusChanged); - _fadeOut = IntTween( - begin: color.alpha, - end: 0, - ).animate( - CurvedAnimation( - parent: _fadeOutController, - curve: const Interval(_kFadeOutIntervalStart, 1.0) - ), + _fadeOut = _fadeOutController.drive( + IntTween( + begin: color.alpha, + end: 0, + ).chain(_fadeOutIntervalTween), ); controller.addInkFeature(this); diff --git a/packages/flutter/lib/src/material/ink_splash.dart b/packages/flutter/lib/src/material/ink_splash.dart index 766abc1928..96482f8743 100644 --- a/packages/flutter/lib/src/material/ink_splash.dart +++ b/packages/flutter/lib/src/material/ink_splash.dart @@ -140,17 +140,17 @@ class InkSplash extends InteractiveInkFeature { _radiusController = AnimationController(duration: _kUnconfirmedSplashDuration, vsync: controller.vsync) ..addListener(controller.markNeedsPaint) ..forward(); - _radius = Tween( + _radius = _radiusController.drive(Tween( begin: _kSplashInitialSize, - end: _targetRadius - ).animate(_radiusController); + end: _targetRadius, + )); _alphaController = AnimationController(duration: _kSplashFadeDuration, vsync: controller.vsync) ..addListener(controller.markNeedsPaint) ..addStatusListener(_handleAlphaStatusChanged); - _alpha = IntTween( + _alpha = _alphaController.drive(IntTween( begin: color.alpha, - end: 0 - ).animate(_alphaController); + end: 0, + )); controller.addInkFeature(this); } diff --git a/packages/flutter/lib/src/material/page.dart b/packages/flutter/lib/src/material/page.dart index 82a989e7eb..cd9e987139 100644 --- a/packages/flutter/lib/src/material/page.dart +++ b/packages/flutter/lib/src/material/page.dart @@ -8,7 +8,7 @@ import 'package:flutter/widgets.dart'; import 'theme.dart'; // Fractional offset from 1/4 screen below the top to fully on screen. -final Tween _kBottomUpTween = Tween( +final Animatable _kBottomUpTween = Tween( begin: const Offset(0.0, 0.25), end: Offset.zero, ); @@ -18,18 +18,17 @@ class _MountainViewPageTransition extends StatelessWidget { _MountainViewPageTransition({ Key key, @required bool fade, - @required Animation routeAnimation, + @required Animation routeAnimation, // The route's linear 0.0 - 1.0 animation. @required this.child, - }) : _positionAnimation = _kBottomUpTween.animate(CurvedAnimation( - parent: routeAnimation, // The route's linear 0.0 - 1.0 animation. - curve: Curves.fastOutSlowIn, - )), - _opacityAnimation = fade ? CurvedAnimation( - parent: routeAnimation, - curve: Curves.easeIn, // Eyeballed from other Material apps. - ) : const AlwaysStoppedAnimation(1.0), + }) : _positionAnimation = routeAnimation.drive(_kBottomUpTween.chain(_fastOutSlowInTween)), + _opacityAnimation = fade + ? routeAnimation.drive(_easeInTween) // Eyeballed from other Material apps. + : const AlwaysStoppedAnimation(1.0), super(key: key); + static final Animatable _fastOutSlowInTween = CurveTween(curve: Curves.fastOutSlowIn); + static final Animatable _easeInTween = CurveTween(curve: Curves.easeIn); + final Animation _positionAnimation; final Animation _opacityAnimation; final Widget child; diff --git a/packages/flutter/lib/src/material/refresh_indicator.dart b/packages/flutter/lib/src/material/refresh_indicator.dart index da410e90dc..3a3602b276 100644 --- a/packages/flutter/lib/src/material/refresh_indicator.dart +++ b/packages/flutter/lib/src/material/refresh_indicator.dart @@ -150,36 +150,32 @@ class RefreshIndicatorState extends State with TickerProviderS bool _isIndicatorAtTop; double _dragOffset; + static final Animatable _threeQuarterTween = Tween(begin: 0.0, end: 0.75); + static final Animatable _kDragSizeFactorLimitTween = Tween(begin: 0.0, end: _kDragSizeFactorLimit); + static final Animatable _oneToZeroTween = Tween(begin: 1.0, end: 0.0); + @override void initState() { super.initState(); _positionController = AnimationController(vsync: this); - _positionFactor = Tween( - begin: 0.0, - end: _kDragSizeFactorLimit, - ).animate(_positionController); - _value = Tween( // The "value" of the circular progress indicator during a drag. - begin: 0.0, - end: 0.75, - ).animate(_positionController); + _positionFactor = _positionController.drive(_kDragSizeFactorLimitTween); + _value = _positionController.drive(_threeQuarterTween); // The "value" of the circular progress indicator during a drag. _scaleController = AnimationController(vsync: this); - _scaleFactor = Tween( - begin: 1.0, - end: 0.0, - ).animate(_scaleController); + _scaleFactor = _scaleController.drive(_oneToZeroTween); } @override void didChangeDependencies() { final ThemeData theme = Theme.of(context); - _valueColor = ColorTween( - begin: (widget.color ?? theme.accentColor).withOpacity(0.0), - end: (widget.color ?? theme.accentColor).withOpacity(1.0) - ).animate(CurvedAnimation( - parent: _positionController, - curve: const Interval(0.0, 1.0 / _kDragSizeFactorLimit) - )); + _valueColor = _positionController.drive( + ColorTween( + begin: (widget.color ?? theme.accentColor).withOpacity(0.0), + end: (widget.color ?? theme.accentColor).withOpacity(1.0) + ).chain(CurveTween( + curve: const Interval(0.0, 1.0 / _kDragSizeFactorLimit) + )), + ); super.didChangeDependencies(); } diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index 175fa14c1b..9d0c6c6642 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart @@ -536,6 +536,11 @@ class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTr } } + static final Animatable _entranceTurnTween = Tween( + begin: 1.0 - kFloatingActionButtonTurnInterval, + end: 1.0, + ).chain(CurveTween(curve: Curves.easeIn)); + void _updateAnimations() { // Get the animations for exit and entrance. final CurvedAnimation previousExitScaleAnimation = CurvedAnimation( @@ -553,15 +558,7 @@ class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTr parent: _currentController, curve: Curves.easeIn, ); - final Animation currentEntranceRotationAnimation = Tween( - begin: 1.0 - kFloatingActionButtonTurnInterval, - end: 1.0, - ).animate( - CurvedAnimation( - parent: _currentController, - curve: Curves.easeIn - ), - ); + final Animation currentEntranceRotationAnimation = _currentController.drive(_entranceTurnTween); // Get the animations for when the FAB is moving. final Animation moveScaleAnimation = widget.fabMotionAnimator.getScaleAnimation(parent: widget.fabMoveAnimation); @@ -570,10 +567,7 @@ class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTr // Aggregate the animations. _previousScaleAnimation = AnimationMin(moveScaleAnimation, previousExitScaleAnimation); _currentScaleAnimation = AnimationMin(moveScaleAnimation, currentEntranceScaleAnimation); - _extendedCurrentScaleAnimation = CurvedAnimation( - parent: _currentScaleAnimation, - curve: const Interval(0.0, 0.1), - ); + _extendedCurrentScaleAnimation = _currentScaleAnimation.drive(CurveTween(curve: const Interval(0.0, 0.1))); _previousRotationAnimation = TrainHoppingAnimation(previousExitRotationAnimation, moveRotationAnimation); _currentRotationAnimation = TrainHoppingAnimation(currentEntranceRotationAnimation, moveRotationAnimation); diff --git a/packages/flutter/lib/src/material/slider.dart b/packages/flutter/lib/src/material/slider.dart index 6890f538b0..36ac5df116 100644 --- a/packages/flutter/lib/src/material/slider.dart +++ b/packages/flutter/lib/src/material/slider.dart @@ -587,7 +587,7 @@ class _RenderSlider extends RenderBox { static const double _preferredTrackWidth = 144.0; static const double _preferredTotalWidth = _preferredTrackWidth + _overlayDiameter; static const Duration _minimumInteractionTime = Duration(milliseconds: 500); - static final Tween _overlayRadiusTween = Tween(begin: 0.0, end: _overlayRadius); + static final Animatable _overlayRadiusTween = Tween(begin: 0.0, end: _overlayRadius); _SliderState _state; Animation _overlayAnimation; diff --git a/packages/flutter/lib/src/material/time_picker.dart b/packages/flutter/lib/src/material/time_picker.dart index a40bfccedc..d9739a9b86 100644 --- a/packages/flutter/lib/src/material/time_picker.dart +++ b/packages/flutter/lib/src/material/time_picker.dart @@ -1010,10 +1010,10 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin { vsync: this, ); _thetaTween = Tween(begin: _getThetaForTime(widget.selectedTime)); - _theta = _thetaTween.animate(CurvedAnimation( - parent: _thetaController, - curve: Curves.fastOutSlowIn - ))..addListener(() => setState(() { })); + _theta = _thetaController + .drive(CurveTween(curve: Curves.fastOutSlowIn)) + .drive(_thetaTween) + ..addListener(() => setState(() { /* _theta.value has changed */ })); } ThemeData themeData; diff --git a/packages/flutter/lib/src/material/toggleable.dart b/packages/flutter/lib/src/material/toggleable.dart index bee9e4e497..571b5fb68b 100644 --- a/packages/flutter/lib/src/material/toggleable.dart +++ b/packages/flutter/lib/src/material/toggleable.dart @@ -11,7 +11,7 @@ import 'package:flutter/scheduler.dart'; import 'constants.dart'; const Duration _kToggleDuration = Duration(milliseconds: 200); -final Tween _kRadialReactionRadiusTween = Tween(begin: 0.0, end: kRadialReactionRadius); +final Animatable _kRadialReactionRadiusTween = Tween(begin: 0.0, end: kRadialReactionRadius); /// A base class for material style toggleable controls with toggle animations. /// diff --git a/packages/flutter/lib/src/rendering/sliver_persistent_header.dart b/packages/flutter/lib/src/rendering/sliver_persistent_header.dart index 12e038821d..f94353a7ed 100644 --- a/packages/flutter/lib/src/rendering/sliver_persistent_header.dart +++ b/packages/flutter/lib/src/rendering/sliver_persistent_header.dart @@ -439,15 +439,14 @@ abstract class RenderSliverFloatingPersistentHeader extends RenderSliverPersiste markNeedsLayout(); }); - // Recreating the animation rather than updating a cached value, only - // to avoid the extra complexity of managing the animation's lifetime. - _animation = Tween( - begin: _effectiveScrollOffset, - end: direction == ScrollDirection.forward ? 0.0 : maxExtent, - ).animate(CurvedAnimation( - parent: _controller, - curve: snapConfiguration.curve, - )); + _animation = _controller.drive( + Tween( + begin: _effectiveScrollOffset, + end: direction == ScrollDirection.forward ? 0.0 : maxExtent, + ).chain(CurveTween( + curve: snapConfiguration.curve, + )), + ); _controller.forward(from: 0.0); } diff --git a/packages/flutter/lib/src/widgets/animated_cross_fade.dart b/packages/flutter/lib/src/widgets/animated_cross_fade.dart index 13d64f9d40..7812b31e80 100644 --- a/packages/flutter/lib/src/widgets/animated_cross_fade.dart +++ b/packages/flutter/lib/src/widgets/animated_cross_fade.dart @@ -243,29 +243,19 @@ class _AnimatedCrossFadeState extends State with TickerProvid _controller.value = 1.0; _firstAnimation = _initAnimation(widget.firstCurve, true); _secondAnimation = _initAnimation(widget.secondCurve, false); - } - - Animation _initAnimation(Curve curve, bool inverted) { - Animation animation = CurvedAnimation( - parent: _controller, - curve: curve, - ); - - if (inverted) { - animation = Tween( - begin: 1.0, - end: 0.0, - ).animate(animation); - } - - animation.addStatusListener((AnimationStatus status) { + _controller.addStatusListener((AnimationStatus status) { setState(() { // Trigger a rebuild because it depends on _isTransitioning, which // changes its value together with animation status. }); }); + } - return animation; + Animation _initAnimation(Curve curve, bool inverted) { + Animation result = _controller.drive(CurveTween(curve: curve)); + if (inverted) + result = result.drive(Tween(begin: 1.0, end: 0.0)); + return result; } @override @@ -302,8 +292,8 @@ class _AnimatedCrossFadeState extends State with TickerProvid Widget build(BuildContext context) { const Key kFirstChildKey = ValueKey(CrossFadeState.showFirst); const Key kSecondChildKey = ValueKey(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; Widget topChild; Animation topAnimation; diff --git a/packages/flutter/lib/src/widgets/dismissible.dart b/packages/flutter/lib/src/widgets/dismissible.dart index 27b3a8f5c0..f945da48a5 100644 --- a/packages/flutter/lib/src/widgets/dismissible.dart +++ b/packages/flutter/lib/src/widgets/dismissible.dart @@ -323,12 +323,14 @@ class _DismissibleState extends State with TickerProviderStateMixin void _updateMoveAnimation() { final double end = _dragExtent.sign; - _moveAnimation = Tween( - begin: Offset.zero, - end: _directionIsXAxis - ? Offset(end, widget.crossAxisEndOffset) - : Offset(widget.crossAxisEndOffset, end), - ).animate(_moveController); + _moveAnimation = _moveController.drive( + Tween( + begin: Offset.zero, + end: _directionIsXAxis + ? Offset(end, widget.crossAxisEndOffset) + : Offset(widget.crossAxisEndOffset, end), + ), + ); } _FlingGestureKind _describeFlingGesture(Velocity velocity) { @@ -424,13 +426,16 @@ class _DismissibleState extends State with TickerProviderStateMixin _resizeController.forward(); setState(() { _sizePriorToCollapse = context.size; - _resizeAnimation = Tween( - begin: 1.0, - end: 0.0 - ).animate(CurvedAnimation( - parent: _resizeController, - curve: _kResizeTimeCurve - )); + _resizeAnimation = _resizeController.drive( + CurveTween( + curve: _kResizeTimeCurve + ), + ).drive( + Tween( + begin: 1.0, + end: 0.0 + ), + ); }); } } diff --git a/packages/flutter/lib/src/widgets/heroes.dart b/packages/flutter/lib/src/widgets/heroes.dart index a5a63297d1..fe55d7ac32 100644 --- a/packages/flutter/lib/src/widgets/heroes.dart +++ b/packages/flutter/lib/src/widgets/heroes.dart @@ -326,6 +326,8 @@ class _HeroFlight { return RectTween(begin: begin, end: end); } + static final Animatable _reverseTween = Tween(begin: 1.0, end: 0.0); + // The OverlayEntry WidgetBuilder callback for the hero's overlay. Widget _buildOverlay(BuildContext context) { assert(manifest != null); @@ -347,9 +349,9 @@ class _HeroFlight { // The toHero no longer exists or it's no longer the flight's destination. // Continue flying while fading out. if (_heroOpacity.isCompleted) { - _heroOpacity = Tween(begin: 1.0, end: 0.0) - .chain(CurveTween(curve: Interval(_proxyAnimation.value, 1.0))) - .animate(_proxyAnimation); + _heroOpacity = _proxyAnimation.drive( + _reverseTween.chain(CurveTween(curve: Interval(_proxyAnimation.value, 1.0))), + ); } } else if (toHeroBox.hasSize) { // The toHero has been laid out. If it's no longer where the hero animation is @@ -460,10 +462,12 @@ class _HeroFlight { assert(manifest.toHero == newManifest.fromHero); assert(manifest.toRoute == newManifest.fromRoute); - _proxyAnimation.parent = Tween( - begin: manifest.animation.value, - end: 1.0, - ).animate(newManifest.animation); + _proxyAnimation.parent = newManifest.animation.drive( + Tween( + begin: manifest.animation.value, + end: 1.0, + ), + ); if (manifest.fromHero != newManifest.toHero) { manifest.fromHero.endFlight(); diff --git a/packages/flutter/lib/src/widgets/implicit_animations.dart b/packages/flutter/lib/src/widgets/implicit_animations.dart index 7bf804d717..7332c88d42 100644 --- a/packages/flutter/lib/src/widgets/implicit_animations.dart +++ b/packages/flutter/lib/src/widgets/implicit_animations.dart @@ -1105,7 +1105,7 @@ class _AnimatedOpacityState extends ImplicitlyAnimatedWidgetState extends TransitionRoute with LocalHistoryRoute _easeCurveTween = CurveTween(curve: Curves.ease); + // one of the builders OverlayEntry _modalBarrier; Widget _buildModalBarrier(BuildContext context) { Widget barrier; if (barrierColor != null && !offstage) { // changedInternalState is called if these update assert(barrierColor != _kTransparent); - final Animation color = ColorTween( - begin: _kTransparent, - end: barrierColor, // changedInternalState is called if this updates - ).animate(CurvedAnimation( - parent: animation, - curve: Curves.ease, - )); + final Animation color = animation.drive( + ColorTween( + begin: _kTransparent, + end: barrierColor, // changedInternalState is called if this updates + ).chain(_easeCurveTween), + ); barrier = AnimatedModalBarrier( color: color, dismissible: barrierDismissible, // changedInternalState is called if this updates diff --git a/packages/flutter/test/animation/tween_test.dart b/packages/flutter/test/animation/tween_test.dart index b8b77a5501..fda95dc526 100644 --- a/packages/flutter/test/animation/tween_test.dart +++ b/packages/flutter/test/animation/tween_test.dart @@ -21,7 +21,7 @@ void main() { expect(chain, hasOneLineDescription); }); - test('Can animated tweens', () { + test('Can animate tweens', () { final Tween tween = Tween(begin: 0.30, end: 0.50); final AnimationController controller = AnimationController( vsync: const TestVSync(), @@ -33,6 +33,18 @@ void main() { expect(animation.toStringDetails(), hasOneLineDescription); }); + test('Can drive tweens', () { + final Tween tween = Tween(begin: 0.30, end: 0.50); + final AnimationController controller = AnimationController( + vsync: const TestVSync(), + ); + final Animation animation = controller.drive(tween); + controller.value = 0.50; + expect(animation.value, 0.40); + expect(animation, hasOneLineDescription); + expect(animation.toStringDetails(), hasOneLineDescription); + }); + test('SizeTween', () { 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))); diff --git a/packages/flutter/test/material/arc_test.dart b/packages/flutter/test/material/arc_test.dart index 74041f4c91..48776dac21 100644 --- a/packages/flutter/test/material/arc_test.dart +++ b/packages/flutter/test/material/arc_test.dart @@ -18,8 +18,7 @@ void main() { ); expect(a, hasOneLineDescription); - expect(a, equals(b)); - expect(a.hashCode, equals(b.hashCode)); + expect(a.toString(), equals(b.toString())); }); test('MaterialRectArcTween control test', () { @@ -33,8 +32,7 @@ void main() { end: Rect.fromLTWH(0.0, 10.0, 10.0, 10.0) ); expect(a, hasOneLineDescription); - expect(a, equals(b)); - expect(a.hashCode, equals(b.hashCode)); + expect(a.toString(), equals(b.toString())); }); test('on-axis MaterialPointArcTween', () { diff --git a/packages/flutter_tools/doc/daemon.md b/packages/flutter_tools/doc/daemon.md index 5e2c70ad54..d4de6b8062 100644 --- a/packages/flutter_tools/doc/daemon.md +++ b/packages/flutter_tools/doc/daemon.md @@ -188,7 +188,7 @@ The returned `params` will contain: - `emulatorName` - the name of the emulator created; this will have been auto-generated if you did not supply one - `error` - when `success`=`false`, a message explaining why the creation of the emulator failed -## 'flutter run --machine' and 'flutter attach --machine' +## 'flutter run --machine' and 'flutter attach --machine' When running `flutter run --machine` or `flutter attach --machine` the following subset of the daemon is available: diff --git a/packages/flutter_tools/templates/module/README.md b/packages/flutter_tools/templates/module/README.md index bae329db18..aed3594072 100644 --- a/packages/flutter_tools/templates/module/README.md +++ b/packages/flutter_tools/templates/module/README.md @@ -80,7 +80,7 @@ suitable only when the Flutter part declares no plugin dependencies. #### host_app_ephemeral_cocoapods Written to `.ios/` on top of `host_app_ephemeral`. - + Adds CocoaPods support. Combined contents define an ephemeral host app suitable for when the diff --git a/packages/flutter_tools/templates/plugin/ios-swift.tmpl/Classes/SwiftpluginClass.swift.tmpl b/packages/flutter_tools/templates/plugin/ios-swift.tmpl/Classes/SwiftpluginClass.swift.tmpl index bb2d81eedf..952508a7c1 100644 --- a/packages/flutter_tools/templates/plugin/ios-swift.tmpl/Classes/SwiftpluginClass.swift.tmpl +++ b/packages/flutter_tools/templates/plugin/ios-swift.tmpl/Classes/SwiftpluginClass.swift.tmpl @@ -1,6 +1,6 @@ import Flutter import UIKit - + public class Swift{{pluginClass}}: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel(name: "{{projectName}}", binaryMessenger: registrar.messenger()) diff --git a/packages/flutter_tools/templates/plugin/ios.tmpl/projectName.podspec.tmpl b/packages/flutter_tools/templates/plugin/ios.tmpl/projectName.podspec.tmpl index 21911bf2cf..ea668b66ef 100644 --- a/packages/flutter_tools/templates/plugin/ios.tmpl/projectName.podspec.tmpl +++ b/packages/flutter_tools/templates/plugin/ios.tmpl/projectName.podspec.tmpl @@ -15,7 +15,7 @@ Pod::Spec.new do |s| s.source_files = 'Classes/**/*' s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' - + s.ios.deployment_target = '8.0' end