Animation API improvements (#21540)
diff --git a/dev/bots/analyze-sample-code.dart b/dev/bots/analyze-sample-code.dart
index 341e03e..e4e643a 100644
--- a/dev/bots/analyze-sample-code.dart
+++ b/dev/bots/analyze-sample-code.dart
@@ -267,7 +267,7 @@
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 @@
sections.add(Section(line, 'Future<Null> 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<String> buffer = <String>[];
int subblocks = 0;
diff --git a/dev/docs/survey.html b/dev/docs/survey.html
index 8f022a6..096e19b 100644
--- a/dev/docs/survey.html
+++ b/dev/docs/survey.html
@@ -1,3 +1,3 @@
-<script async="" defer=""
+<script async="" defer=""
src="//www.google.com/insights/consumersurveys/async_survey?site=i2bdmo2dzky7fqws25nicgx5f4">
-</script>
\ No newline at end of file
+</script>
diff --git a/dev/integration_tests/flavors/ios/Runner/AppDelegate.m b/dev/integration_tests/flavors/ios/Runner/AppDelegate.m
index 4082691..11b4078 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 45c58da..5c030e5 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 b0d6c0e..9f7b624 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 @@
final Size panelSize = constraints.biggest;
final double panelTop = panelSize.height - panelTitleHeight;
- final Animation<RelativeRect> 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<RelativeRect> 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 9264ff6..85856c1 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 @@
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 @@
final String _title;
final BottomNavigationBarItem item;
final AnimationController controller;
- CurvedAnimation _animation;
+ Animation<double> _animation;
FadeTransition transition(BottomNavigationBarType type, BuildContext context) {
Color iconColor;
@@ -51,10 +50,12 @@
return FadeTransition(
opacity: _animation,
child: SlideTransition(
- position: Tween<Offset>(
- begin: const Offset(0.0, 0.02), // Slightly down.
- end: Offset.zero,
- ).animate(_animation),
+ position: _animation.drive(
+ Tween<Offset>(
+ 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 19bd0c5..fd868af 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 @@
'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;
Animation<double> _drawerContentsOpacity;
Animation<Offset> _drawerDetailsPosition;
@@ -39,13 +46,7 @@
parent: ReverseAnimation(_controller),
curve: Curves.fastOutSlowIn,
);
- _drawerDetailsPosition = Tween<Offset>(
- 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 6bf4594..cb9661f 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 @@
return;
final Offset direction = details.velocity.pixelsPerSecond / magnitude;
final double distance = (Offset.zero & context.size).shortestSide;
- _flingAnimation = Tween<Offset>(
+ _flingAnimation = _controller.drive(Tween<Offset>(
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 5a8d4cc..7306587 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 @@
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,
end: _thumbSize,
);
@@ -74,7 +74,7 @@
return Size.fromRadius(isEnabled ? _indicatorSize : _disabledIndicatorSize);
}
- static final Tween<double> sizeTween = Tween<double>(
+ static final Animatable<double> sizeTween = Tween<double>(
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 fec72f7..29ed01f 100644
--- a/examples/flutter_gallery/lib/demo/pesto_demo.dart
+++ b/examples/flutter_gallery/lib/demo/pesto_demo.dart
@@ -120,7 +120,7 @@
final Size size = constraints.biggest;
final double appBarHeight = size.height - statusBarHeight;
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;
return Padding(
padding: EdgeInsets.only(
diff --git a/examples/flutter_gallery/lib/gallery/backdrop.dart b/examples/flutter_gallery/lib/gallery/backdrop.dart
index 92074f6..171d5c3 100644
--- a/examples/flutter_gallery/lib/gallery/backdrop.dart
+++ b/examples/flutter_gallery/lib/gallery/backdrop.dart
@@ -12,7 +12,7 @@
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<BorderRadius> _kFrontHeadingBevelRadius = BorderRadiusTween(
+final Animatable<BorderRadius> _kFrontHeadingBevelRadius = BorderRadiusTween(
begin: const BorderRadius.only(
topLeft: Radius.circular(12.0),
topRight: Radius.circular(12.0),
@@ -199,6 +199,9 @@
AnimationController _controller;
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
void initState() {
super.initState();
@@ -207,14 +210,7 @@
value: 1.0,
vsync: this,
);
-
- _frontOpacity =
- Tween<double>(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 @@
}
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),
end: const RelativeRect.fromLTRB(0.0, _kBackAppBarHeight, 0.0, 0.0),
- ).animate(_controller);
+ ));
final List<Widget> layers = <Widget>[
// Back layer
@@ -301,7 +297,7 @@
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 be2c9f6..65a951c 100644
--- a/packages/flutter/lib/src/animation/animation.dart
+++ b/packages/flutter/lib/src/animation/animation.dart
@@ -6,6 +6,8 @@
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 @@
///
/// 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<double>`s into other kinds of `Animation`s.
abstract class Animation<T> extends Listenable implements ValueListenable<T> {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
@@ -80,6 +87,76 @@
/// 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<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
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 a1cd6c3..f418065 100644
--- a/packages/flutter/lib/src/animation/animation_controller.dart
+++ b/packages/flutter/lib/src/animation/animation_controller.dart
@@ -17,7 +17,8 @@
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 @@
/// 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 @@
/// 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<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
/// Future<Null> fadeOutAndUpdateState() async {
@@ -110,14 +189,20 @@
/// }
/// ```
///
-/// ...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<double>
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 d0d6672..a6c1c79 100644
--- a/packages/flutter/lib/src/animation/animations.dart
+++ b/packages/flutter/lib/src/animation/animations.dart
@@ -248,6 +248,13 @@
/// 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<double>
with AnimationLazyListenerMixin, AnimationLocalStatusListenersMixin {
@@ -312,15 +319,8 @@
/// 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 @@
/// 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<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> {
/// Creates a curved animation.
///
@@ -428,11 +464,19 @@
enum _TrainHoppingMode { minimize, maximize }
-/// This animation starts by proxying one animation, but can be given a
-/// second animation. When their times cross (either because the second is
+/// This animation starts by proxying one animation, but when the value of that
+/// animation crosses the value of the second (either because the second is
/// going in the opposite direction, or because the one overtakes the other),
-/// the animation hops over to proxying the second animation, and the second
-/// animation becomes the new "first" performance.
+/// the animation hops over to proxying the second animation.
+///
+/// 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
/// listeners of its own, instead of shutting down when all its listeners are
@@ -444,33 +488,41 @@
/// 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<double> get currentTrain => _currentTrain;
Animation<double> _currentTrain;
Animation<double> _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 @@
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 9e270ff..f1596c5 100644
--- a/packages/flutter/lib/src/animation/curves.dart
+++ b/packages/flutter/lib/src/animation/curves.dart
@@ -6,11 +6,22 @@
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 @@
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 @@
/// 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 @@
/// 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 23f4100..ee21292 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 'animations.dart';
import 'curves.dart';
+// Examples can assume:
+// Animation<Offset> _animation;
+
/// An object that can produce a value of type `T` given an [Animation<double>]
/// 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<T> {
/// 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<double> 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<double> 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<T> animate(Animation<double> parent) {
return _AnimatedEvaluation<T>(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<T> chain(Animatable<double> parent) {
return _ChainedEvaluation<T>(parent, this);
}
@@ -65,9 +103,8 @@
final Animatable<T> _evaluatable;
@override
- T evaluate(Animation<double> animation) {
- final double value = _parent.evaluate(animation);
- return _evaluatable.evaluate(AlwaysStoppedAnimation<double>(value));
+ T transform(double t) {
+ return _evaluatable.transform(_parent.transform(t));
}
@override
@@ -87,27 +124,85 @@
/// 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<Offset>] that is controlled by that controller, and save it in
-/// `_animation`:
+/// `_animation`. Here are two possible ways of expressing this:
///
/// ```dart
-/// Animation<Offset> _animation = Tween<Offset>(
+/// _animation = _controller.drive(
+/// Tween<Offset>(
+/// begin: const Offset(100.0, 50.0),
+/// end: const Offset(200.0, 300.0),
+/// ),
+/// );
+/// ```
+///
+/// ```dart
+/// _animation = Tween<Offset>(
/// 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<Offset>] 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<Offset>] 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<T>`.
+///
+/// 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<double>`.
+///
+/// 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<T extends dynamic> extends Animatable<T> {
/// Creates a tween.
///
@@ -133,6 +228,7 @@
/// 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 @@
/// 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<double> animation) {
- final double t = animation.value;
+ T transform(double t) {
if (t == 0.0)
return begin;
if (t == 1.0)
@@ -161,20 +257,6 @@
}
@override
- bool operator ==(dynamic other) {
- if (identical(this, other))
- return true;
- if (other.runtimeType != runtimeType)
- return false;
- final Tween<T> 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 @@
///
/// This class specializes the interpolation of [Tween<int>] 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 @@
/// 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<double> 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<double> {
/// Creates a curve tween.
///
@@ -332,8 +434,7 @@
Curve curve;
@override
- double evaluate(Animation<double> 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 cb2b1de..1ff8861 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 @@
T _evaluateAt(double t, int index) {
final TweenSequenceItem<T> element = _items[index];
final double tInterval = _intervals[index].value(t);
- return element.tween.evaluate(AlwaysStoppedAnimation<double>(tInterval));
+ return element.tween.transform(tInterval);
}
@override
- T evaluate(Animation<double> 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 @@
/// 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 817b6f0..046cc11 100644
--- a/packages/flutter/lib/src/cupertino/button.dart
+++ b/packages/flutter/lib/src/cupertino/button.dart
@@ -125,16 +125,10 @@
// Eyeballed values. Feel free to tweak.
static const Duration kFadeOutDuration = Duration(milliseconds: 10);
static const Duration kFadeInDuration = Duration(milliseconds: 100);
- Tween<double> _opacityTween;
+ final Tween<double> _opacityTween = Tween<double>(begin: 1.0);
AnimationController _animationController;
-
- void _setTween() {
- _opacityTween = Tween<double>(
- begin: 1.0,
- end: widget.pressedOpacity ?? 1.0,
- );
- }
+ Animation<double> _opacityAnimation;
@override
void initState() {
@@ -144,22 +138,29 @@
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();
_animationController = null;
super.dispose();
}
- @override
- void didUpdateWidget(CupertinoButton old) {
- super.didUpdateWidget(old);
- _setTween();
- }
-
bool _buttonHeldDown = false;
void _handleTapDown(TapDownDetails event) {
@@ -217,10 +218,7 @@
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 104df40..594cbbe 100644
--- a/packages/flutter/lib/src/cupertino/nav_bar.dart
+++ b/packages/flutter/lib/src/cupertino/nav_bar.dart
@@ -1520,11 +1520,11 @@
// paintBounds are based on offset zero so it's ok to expand the Rects.
bottomNavBar.renderBox.paintBounds.expandToInclude(topNavBar.renderBox.paintBounds);
- static final Tween<double> fadeOut = Tween<double>(
+ static final Animatable<double> fadeOut = Tween<double>(
begin: 1.0,
end: 0.0,
);
- static final Tween<double> fadeIn = Tween<double>(
+ static final Animatable<double> fadeIn = Tween<double>(
begin: 0.0,
end: 1.0,
);
@@ -1601,15 +1601,15 @@
}
Animation<double> 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<double> 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 @@
);
return PositionedTransition(
- rect: positionTween.animate(animation),
+ rect: animation.drive(positionTween),
child: FadeTransition(
opacity: fadeOutBy(0.2),
child: DefaultTextStyle(
@@ -1687,12 +1687,12 @@
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 @@
// 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 @@
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 @@
// 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 @@
// 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 @@
);
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 @@
Animation<double> midClickOpacity;
if (topBackLabelOpacity != null && topBackLabelOpacity.opacity.value < 1.0) {
- midClickOpacity = Tween<double>(
+ midClickOpacity = animation.drive(Tween<double>(
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 @@
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 @@
// 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 @@
);
return PositionedTransition(
- rect: positionTween.animate(animation),
+ rect: animation.drive(positionTween),
child: FadeTransition(
opacity: fadeInFrom(0.25),
child: DefaultTextStyle(
@@ -2005,7 +2005,7 @@
);
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 2f33cb5..fc7103d 100644
--- a/packages/flutter/lib/src/cupertino/route.dart
+++ b/packages/flutter/lib/src/cupertino/route.dart
@@ -19,19 +19,19 @@
const Duration _kModalPopupTransitionDuration = Duration(milliseconds: 335);
// 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),
end: Offset.zero,
);
// 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,
end: const Offset(-1.0/3.0, 0.0),
);
// 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),
end: Offset.zero,
);
@@ -354,28 +354,22 @@
@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 @@
Key key,
@required Animation<double> 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<Offset> _positionAnimation;
@@ -859,6 +850,9 @@
);
}
+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) {
final CurvedAnimation fadeAnimation = CurvedAnimation(
parent: animation,
@@ -874,15 +868,7 @@
opacity: fadeAnimation,
child: ScaleTransition(
child: child,
- scale: Tween<double>(
- 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 8ec520c..1116501 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 @@
// animation is complete.
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() {
for (AnimationController controller in _controllers)
@@ -655,7 +655,7 @@
);
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 2d737ac..f80ee1d 100644
--- a/packages/flutter/lib/src/material/chip.dart
+++ b/packages/flutter/lib/src/material/chip.dart
@@ -1220,7 +1220,7 @@
Animation<double> enableAnimation;
Animation<double> selectionFade;
- static final Tween<double> pressedShadowTween = Tween<double>(
+ static final Animatable<double> pressedShadowTween = Tween<double>(
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 f76be29..578d01c 100644
--- a/packages/flutter/lib/src/material/data_table.dart
+++ b/packages/flutter/lib/src/material/data_table.dart
@@ -694,6 +694,9 @@
bool _down;
+ static final Animatable<double> _turnTween = Tween<double>(begin: 0.0, end: math.pi)
+ .chain(CurveTween(curve: Curves.easeIn));
+
@override
void initState() {
super.initState();
@@ -706,18 +709,13 @@
)
..addListener(_rebuild);
_opacityController.value = widget.visible ? 1.0 : 0.0;
- _orientationAnimation = Tween<double>(
- 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 f6c1e7b..602b9bd 100644
--- a/packages/flutter/lib/src/material/date_picker.dart
+++ b/packages/flutter/lib/src/material/date_picker.dart
@@ -525,6 +525,9 @@
}
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
void initState() {
super.initState();
@@ -538,12 +541,7 @@
_chevronOpacityController = AnimationController(
duration: const Duration(milliseconds: 250), vsync: this
);
- _chevronOpacityAnimation = Tween<double>(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 ee87f5e..943767c 100644
--- a/packages/flutter/lib/src/material/expand_icon.dart
+++ b/packages/flutter/lib/src/material/expand_icon.dart
@@ -67,16 +67,14 @@
AnimationController _controller;
Animation<double> _iconTurns;
+ static final Animatable<double> _iconTurnTween = Tween<double>(begin: 0.0, end: 0.5)
+ .chain(CurveTween(curve: Curves.fastOutSlowIn));
+
@override
void initState() {
super.initState();
_controller = AnimationController(duration: kThemeAnimationDuration, vsync: this);
- _iconTurns = Tween<double>(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 0254c61..b9f9141 100644
--- a/packages/flutter/lib/src/material/expansion_tile.dart
+++ b/packages/flutter/lib/src/material/expansion_tile.dart
@@ -79,14 +79,22 @@
}
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;
- CurvedAnimation _easeOutAnimation;
- CurvedAnimation _easeInAnimation;
- ColorTween _borderColor;
- ColorTween _headerColor;
- ColorTween _iconColor;
- ColorTween _backgroundColor;
Animation<double> _iconTurns;
+ Animation<double> _heightFactor;
+ Animation<Color> _borderColor;
+ Animation<Color> _headerColor;
+ Animation<Color> _iconColor;
+ Animation<Color> _backgroundColor;
bool _isExpanded = false;
@@ -94,13 +102,12 @@
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<double>(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 @@
void _handleTap() {
setState(() {
_isExpanded = !_isExpanded;
- if (_isExpanded)
+ if (_isExpanded) {
_controller.forward();
- else
- _controller.reverse().then<void>((Null value) {
+ } else {
+ _controller.reverse().then<void>((void value) {
+ if (!mounted)
+ return;
setState(() {
// Rebuild without widget.children.
});
});
+ }
PageStorage.of(context)?.writeState(context, _isExpanded);
});
if (widget.onExpansionChanged != null)
@@ -131,12 +141,12 @@
}
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 @@
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconTheme.merge(
- data: IconThemeData(color: _iconColor.evaluate(_easeInAnimation)),
+ data: IconThemeData(color: _iconColor.value),
child: ListTile(
onTap: _handleTap,
leading: widget.leading,
@@ -162,7 +172,7 @@
),
ClipRect(
child: Align(
- heightFactor: _easeInAnimation.value,
+ heightFactor: _heightFactor.value,
child: child,
),
),
@@ -172,17 +182,23 @@
}
@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 a53abd7..1f37b47 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 @@
return 0.0;
case CollapseMode.parallax:
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;
}
@@ -193,7 +193,7 @@
color: titleStyle.color.withOpacity(opacity)
);
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()
..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 5f4d196..3da2248 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 @@
// then from 0 back to 1 in the second half.
const Curve curve = Interval(0.5, 1.0, curve: Curves.ease);
return _AnimationSwap<double>(
- 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<double> _rotationTween = Tween<double>(
+ begin: 1.0 - kFloatingActionButtonTurnInterval * 2.0,
+ end: 1.0,
+ );
+
+ static final Animatable<double> _thresholdCenterTween = CurveTween(curve: const Threshold(0.5));
+
@override
Animation<double> getRotationAnimation({Animation<double> parent}) {
- // Because we only see the last half of the rotation tween,
- // it needs to go twice as far.
- final Tween<double> rotationTween = Tween<double>(
- begin: 1.0 - kFloatingActionButtonTurnInterval * 2,
- end: 1.0,
- );
// This rotation will turn on the way in, but not on the way out.
return _AnimationSwap<double>(
- 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 591654c..23cad86c 100644
--- a/packages/flutter/lib/src/material/ink_highlight.dart
+++ b/packages/flutter/lib/src/material/ink_highlight.dart
@@ -58,10 +58,10 @@
..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 0cae37b..b42f90e 100644
--- a/packages/flutter/lib/src/material/ink_ripple.dart
+++ b/packages/flutter/lib/src/material/ink_ripple.dart
@@ -96,6 +96,9 @@
/// or material [Theme].
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].
///
/// The [controller] argument is typically obtained via
@@ -140,10 +143,10 @@
_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 @@
..forward();
// Initial splash diameter is 60% of the target diameter, final
// diameter is 10dps larger than the target diameter.
- _radius = Tween<double>(
- begin: _targetRadius * 0.30,
- end: _targetRadius + 5.0,
- ).animate(
- CurvedAnimation(
- parent: _radiusController,
- curve: Curves.ease,
- )
+ _radius = _radiusController.drive(
+ Tween<double>(
+ 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 @@
_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 766abc1..96482f8 100644
--- a/packages/flutter/lib/src/material/ink_splash.dart
+++ b/packages/flutter/lib/src/material/ink_splash.dart
@@ -140,17 +140,17 @@
_radiusController = AnimationController(duration: _kUnconfirmedSplashDuration, vsync: controller.vsync)
..addListener(controller.markNeedsPaint)
..forward();
- _radius = Tween<double>(
+ _radius = _radiusController.drive(Tween<double>(
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 82a989e..cd9e987 100644
--- a/packages/flutter/lib/src/material/page.dart
+++ b/packages/flutter/lib/src/material/page.dart
@@ -8,7 +8,7 @@
import 'theme.dart';
// 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),
end: Offset.zero,
);
@@ -18,18 +18,17 @@
_MountainViewPageTransition({
Key key,
@required bool fade,
- @required Animation<double> routeAnimation,
+ @required Animation<double> 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<double>(1.0),
+ }) : _positionAnimation = routeAnimation.drive(_kBottomUpTween.chain(_fastOutSlowInTween)),
+ _opacityAnimation = fade
+ ? routeAnimation.drive(_easeInTween) // Eyeballed from other Material apps.
+ : const AlwaysStoppedAnimation<double>(1.0),
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<double> _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 da410e9..3a3602b 100644
--- a/packages/flutter/lib/src/material/refresh_indicator.dart
+++ b/packages/flutter/lib/src/material/refresh_indicator.dart
@@ -150,36 +150,32 @@
bool _isIndicatorAtTop;
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
void initState() {
super.initState();
_positionController = AnimationController(vsync: this);
- _positionFactor = Tween<double>(
- begin: 0.0,
- 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);
+ _positionFactor = _positionController.drive(_kDragSizeFactorLimitTween);
+ _value = _positionController.drive(_threeQuarterTween); // The "value" of the circular progress indicator during a drag.
_scaleController = AnimationController(vsync: this);
- _scaleFactor = Tween<double>(
- 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 175fa14..9d0c6c6 100644
--- a/packages/flutter/lib/src/material/scaffold.dart
+++ b/packages/flutter/lib/src/material/scaffold.dart
@@ -536,6 +536,11 @@
}
}
+ static final Animatable<double> _entranceTurnTween = Tween<double>(
+ 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 @@
parent: _currentController,
curve: Curves.easeIn,
);
- final Animation<double> currentEntranceRotationAnimation = Tween<double>(
- begin: 1.0 - kFloatingActionButtonTurnInterval,
- end: 1.0,
- ).animate(
- CurvedAnimation(
- parent: _currentController,
- curve: Curves.easeIn
- ),
- );
+ final Animation<double> currentEntranceRotationAnimation = _currentController.drive(_entranceTurnTween);
// Get the animations for when the FAB is moving.
final Animation<double> moveScaleAnimation = widget.fabMotionAnimator.getScaleAnimation(parent: widget.fabMoveAnimation);
@@ -570,10 +567,7 @@
// Aggregate the animations.
_previousScaleAnimation = AnimationMin<double>(moveScaleAnimation, previousExitScaleAnimation);
_currentScaleAnimation = AnimationMin<double>(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 6890f53..36ac5df 100644
--- a/packages/flutter/lib/src/material/slider.dart
+++ b/packages/flutter/lib/src/material/slider.dart
@@ -587,7 +587,7 @@
static const double _preferredTrackWidth = 144.0;
static const double _preferredTotalWidth = _preferredTrackWidth + _overlayDiameter;
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;
Animation<double> _overlayAnimation;
diff --git a/packages/flutter/lib/src/material/time_picker.dart b/packages/flutter/lib/src/material/time_picker.dart
index a40bfcc..d9739a9 100644
--- a/packages/flutter/lib/src/material/time_picker.dart
+++ b/packages/flutter/lib/src/material/time_picker.dart
@@ -1010,10 +1010,10 @@
vsync: this,
);
_thetaTween = Tween<double>(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 bee9e4e..571b5fb 100644
--- a/packages/flutter/lib/src/material/toggleable.dart
+++ b/packages/flutter/lib/src/material/toggleable.dart
@@ -11,7 +11,7 @@
import 'constants.dart';
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.
///
diff --git a/packages/flutter/lib/src/rendering/sliver_persistent_header.dart b/packages/flutter/lib/src/rendering/sliver_persistent_header.dart
index 12e0388..f94353a 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 @@
markNeedsLayout();
});
- // Recreating the animation rather than updating a cached value, only
- // to avoid the extra complexity of managing the animation's lifetime.
- _animation = Tween<double>(
- begin: _effectiveScrollOffset,
- end: direction == ScrollDirection.forward ? 0.0 : maxExtent,
- ).animate(CurvedAnimation(
- parent: _controller,
- curve: snapConfiguration.curve,
- ));
+ _animation = _controller.drive(
+ Tween<double>(
+ 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 13d64f9..7812b31 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 @@
_controller.value = 1.0;
_firstAnimation = _initAnimation(widget.firstCurve, true);
_secondAnimation = _initAnimation(widget.secondCurve, false);
- }
-
- 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) {
+ _controller.addStatusListener((AnimationStatus status) {
setState(() {
// Trigger a rebuild because it depends on _isTransitioning, which
// 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
@@ -302,8 +292,8 @@
Widget build(BuildContext context) {
const Key kFirstChildKey = ValueKey<CrossFadeState>(CrossFadeState.showFirst);
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;
Widget topChild;
Animation<double> topAnimation;
diff --git a/packages/flutter/lib/src/widgets/dismissible.dart b/packages/flutter/lib/src/widgets/dismissible.dart
index 27b3a8f..f945da4 100644
--- a/packages/flutter/lib/src/widgets/dismissible.dart
+++ b/packages/flutter/lib/src/widgets/dismissible.dart
@@ -323,12 +323,14 @@
void _updateMoveAnimation() {
final double end = _dragExtent.sign;
- _moveAnimation = Tween<Offset>(
- begin: Offset.zero,
- end: _directionIsXAxis
- ? Offset(end, widget.crossAxisEndOffset)
- : Offset(widget.crossAxisEndOffset, end),
- ).animate(_moveController);
+ _moveAnimation = _moveController.drive(
+ Tween<Offset>(
+ begin: Offset.zero,
+ end: _directionIsXAxis
+ ? Offset(end, widget.crossAxisEndOffset)
+ : Offset(widget.crossAxisEndOffset, end),
+ ),
+ );
}
_FlingGestureKind _describeFlingGesture(Velocity velocity) {
@@ -424,13 +426,16 @@
_resizeController.forward();
setState(() {
_sizePriorToCollapse = context.size;
- _resizeAnimation = Tween<double>(
- begin: 1.0,
- end: 0.0
- ).animate(CurvedAnimation(
- parent: _resizeController,
- curve: _kResizeTimeCurve
- ));
+ _resizeAnimation = _resizeController.drive(
+ CurveTween(
+ curve: _kResizeTimeCurve
+ ),
+ ).drive(
+ Tween<double>(
+ 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 a5a6329..fe55d7a 100644
--- a/packages/flutter/lib/src/widgets/heroes.dart
+++ b/packages/flutter/lib/src/widgets/heroes.dart
@@ -326,6 +326,8 @@
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.
Widget _buildOverlay(BuildContext context) {
assert(manifest != null);
@@ -347,9 +349,9 @@
// The toHero no longer exists or it's no longer the flight's destination.
// Continue flying while fading out.
if (_heroOpacity.isCompleted) {
- _heroOpacity = Tween<double>(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 @@
assert(manifest.toHero == newManifest.fromHero);
assert(manifest.toRoute == newManifest.fromRoute);
- _proxyAnimation.parent = Tween<double>(
- begin: manifest.animation.value,
- end: 1.0,
- ).animate(newManifest.animation);
+ _proxyAnimation.parent = newManifest.animation.drive(
+ Tween<double>(
+ 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 7bf804d..7332c88 100644
--- a/packages/flutter/lib/src/widgets/implicit_animations.dart
+++ b/packages/flutter/lib/src/widgets/implicit_animations.dart
@@ -1105,7 +1105,7 @@
@override
void didUpdateTweens() {
- _opacityAnimation = _opacity.animate(animation);
+ _opacityAnimation = animation.drive(_opacity);
}
@override
diff --git a/packages/flutter/lib/src/widgets/overscroll_indicator.dart b/packages/flutter/lib/src/widgets/overscroll_indicator.dart
index 6da2292..5a59825 100644
--- a/packages/flutter/lib/src/widgets/overscroll_indicator.dart
+++ b/packages/flutter/lib/src/widgets/overscroll_indicator.dart
@@ -259,8 +259,8 @@
parent: _glowController,
curve: Curves.decelerate,
)..addListener(notifyListeners);
- _glowOpacity = _glowOpacityTween.animate(decelerator);
- _glowSize = _glowSizeTween.animate(decelerator);
+ _glowOpacity = decelerator.drive(_glowOpacityTween);
+ _glowSize = decelerator.drive(_glowSizeTween);
_displacementTicker = vsync.createTicker(_tickDisplacement);
}
diff --git a/packages/flutter/lib/src/widgets/routes.dart b/packages/flutter/lib/src/widgets/routes.dart
index 252b4cc..fd6d3df 100644
--- a/packages/flutter/lib/src/widgets/routes.dart
+++ b/packages/flutter/lib/src/widgets/routes.dart
@@ -16,6 +16,9 @@
import 'page_storage.dart';
import 'transitions.dart';
+// Examples can assume:
+// dynamic routeObserver;
+
const Color _kTransparent = Color(0x00000000);
/// A route that displays widgets in the [Navigator]'s [Overlay].
@@ -1171,19 +1174,20 @@
final GlobalKey _subtreeKey = GlobalKey();
final PageStorageBucket _storageBucket = PageStorageBucket();
+ static final Animatable<double> _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> color = ColorTween(
- begin: _kTransparent,
- end: barrierColor, // changedInternalState is called if this updates
- ).animate(CurvedAnimation(
- parent: animation,
- curve: Curves.ease,
- ));
+ final Animation<Color> 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 b8b77a5..fda95dc 100644
--- a/packages/flutter/test/animation/tween_test.dart
+++ b/packages/flutter/test/animation/tween_test.dart
@@ -21,7 +21,7 @@
expect(chain, hasOneLineDescription);
});
- test('Can animated tweens', () {
+ test('Can animate tweens', () {
final Tween<double> tween = Tween<double>(begin: 0.30, end: 0.50);
final AnimationController controller = AnimationController(
vsync: const TestVSync(),
@@ -33,6 +33,18 @@
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', () {
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 74041f4..48776da 100644
--- a/packages/flutter/test/material/arc_test.dart
+++ b/packages/flutter/test/material/arc_test.dart
@@ -18,8 +18,7 @@
);
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 @@
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 5e2c70a..d4de6b8 100644
--- a/packages/flutter_tools/doc/daemon.md
+++ b/packages/flutter_tools/doc/daemon.md
@@ -188,7 +188,7 @@
- `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 bae329d..aed3594 100644
--- a/packages/flutter_tools/templates/module/README.md
+++ b/packages/flutter_tools/templates/module/README.md
@@ -80,7 +80,7 @@
#### 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 bb2d81e..952508a 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 21911bf..ea668b6 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 @@
s.source_files = 'Classes/**/*'
s.public_header_files = 'Classes/**/*.h'
s.dependency 'Flutter'
-
+
s.ios.deployment_target = '8.0'
end