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

Animation API improvements (#21540)

parent adcf226e
......@@ -267,7 +267,7 @@ linter:
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<String> block, List<Section> sections) {
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;
......
<script async="" defer=""
<script async="" defer=""
src="//www.google.com/insights/consumersurveys/async_survey?site=i2bdmo2dzky7fqws25nicgx5f4">
</script>
\ No newline at end of file
</script>
......@@ -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);
......
......@@ -15,7 +15,7 @@
- (nullable UIViewController *)popViewControllerAnimated:(BOOL)animated {
FlutterViewController* root = (FlutterViewController*)[self.viewControllers objectAtIndex:0];
FlutterBasicMessageChannel* messageChannel =
[FlutterBasicMessageChannel messageChannelWithName:@"navigation-test"
binaryMessenger:root
......
......@@ -318,18 +318,15 @@ class _BackdropDemoState extends State<BackdropDemo> with SingleTickerProviderSt
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),
),
);
......
......@@ -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<double> _animation;
FadeTransition transition(BottomNavigationBarType type, BuildContext context) {
Color iconColor;
......@@ -51,10 +50,12 @@ class NavigationIconView {
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,
......
......@@ -23,6 +23,13 @@ class _DrawerDemoState extends State<DrawerDemo> with TickerProviderStateMixin {
'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 @@ class _DrawerDemoState extends State<DrawerDemo> with TickerProviderStateMixin {
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
......
......@@ -117,10 +117,10 @@ class _GridPhotoViewerState extends State<GridPhotoViewer> with SingleTickerProv
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);
......
......@@ -35,7 +35,7 @@ class _CustomThumbShape extends SliderComponentShape {
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 @@ class _CustomValueIndicatorShape extends SliderComponentShape {
return Size.fromRadius(isEnabled ? _indicatorSize : _disabledIndicatorSize);
}
static final Tween<double> sizeTween = Tween<double>(
static final Animatable<double> sizeTween = Tween<double>(
begin: _disabledIndicatorSize,
end: _indicatorSize,
);
......
......@@ -120,7 +120,7 @@ class _RecipeGridPageState extends State<RecipeGridPage> {
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(
......
......@@ -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<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 @@ class _BackdropState extends State<Backdrop> with SingleTickerProviderStateMixin
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 @@ class _BackdropState extends State<Backdrop> with SingleTickerProviderStateMixin
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 @@ class _BackdropState extends State<Backdrop> with SingleTickerProviderStateMixin
}
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 @@ class _BackdropState extends State<Backdrop> with SingleTickerProviderStateMixin
color: Theme.of(context).canvasColor,
clipper: ShapeBorderClipper(
shape: BeveledRectangleBorder(
borderRadius: _kFrontHeadingBevelRadius.lerp(_controller.value),
borderRadius: _kFrontHeadingBevelRadius.transform(_controller.value),
),
),
clipBehavior: Clip.antiAlias,
......
......@@ -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<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 @@ abstract class Animation<T> extends Listenable implements ValueListenable<T> {
/// 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()})';
......
......@@ -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<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 @@ 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<double>
with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
/// Creates an animation controller.
///
/// * [value] is the initial value of the animation. If defaults to the lower
......
......@@ -248,6 +248,13 @@ class ProxyAnimation extends Animation<double>
/// 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 @@ class ReverseAnimation extends Animation<double>
/// 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<double>
/// 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 @@ class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<do
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 @@ class TrainHoppingAnimation extends Animation<double>
/// 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 @@ class TrainHoppingAnimation extends Animation<double>
assert(_currentTrain != null);
bool hop = false;
if (_nextTrain != null) {
assert(_mode != null);
switch (_mode) {
case _TrainHoppingMode.minimize:
hop = _nextTrain.value <= _currentTrain.value;
......
......@@ -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.
///
......
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<T> extends Animatable<T> {
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 @@ class TweenSequenceItem<T> {
/// 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:
///
......
......@@ -125,16 +125,10 @@ class _CupertinoButtonState extends State<CupertinoButton> with SingleTickerProv
// 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,9 +138,22 @@ class _CupertinoButtonState extends State<CupertinoButton> 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<CupertinoButton> 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<CupertinoButton> 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,
......
......@@ -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<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 @@ class _NavigationBarComponentsTransition {
}
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 @@ 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<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 @@ 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(
......
......@@ -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<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 @@ 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<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 @@ Future<T> showCupertinoModalPopup<T>({
);
}
final Animatable<double> _dialogTween = Tween<double>(begin: 1.2, end: 1.0)
.chain(CurveTween(curve: Curves.fastOutSlowIn));
Widget _buildCupertinoDialogTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
final CurvedAnimation fadeAnimation = CurvedAnimation(
parent: animation,
......@@ -874,15 +868,7 @@ Widget _buildCupertinoDialogTransitions(BuildContext context, Animation<double>
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),
),
);
}
......
......@@ -316,7 +316,7 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
// 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 @@ class _RadialPainter extends CustomPainter {
);
canvas.drawCircle(
center,
radiusTween.lerp(circle.animation.value),
radiusTween.transform(circle.animation.value),
paint,
);
}
......
......@@ -1220,7 +1220,7 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip
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,
);
......
......@@ -694,6 +694,9 @@ class _SortArrowState extends State<_SortArrow> with TickerProviderStateMixin {
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 @@ class _SortArrowState extends State<_SortArrow> with TickerProviderStateMixin {
)
..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;
}
......
......@@ -525,6 +525,9 @@ class MonthPicker extends StatefulWidget {
}
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 @@ class _MonthPickerState extends State<MonthPicker> with SingleTickerProviderStat
_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
......
......@@ -67,16 +67,14 @@ class _ExpandIconState extends State<ExpandIcon> with SingleTickerProviderStateM
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;
......
......@@ -79,14 +79,22 @@ class ExpansionTile extends StatefulWidget {
}
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 @@ class _ExpansionTileState extends State<ExpansionTile> 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<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 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
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 @@ class _ExpansionTileState extends State<ExpansionTile> 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<ExpansionTile> with SingleTickerProvider
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 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
),
ClipRect(
child: Align(
heightFactor: _easeInAnimation.value,
heightFactor: _heightFactor.value,
child: child,
),
),
......@@ -172,17 +182,23 @@ class _ExpansionTileState extends State<ExpansionTile> 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,
......
......@@ -133,7 +133,7 @@ class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> {
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 @@ class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> {
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);
......
......@@ -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<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,
);
......
......@@ -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);
}
......
......@@ -96,6 +96,9 @@ class InkRipple extends InteractiveInkFeature {
/// 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 @@ 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<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 @@ 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);
......
......@@ -140,17 +140,17 @@ class InkSplash extends InteractiveInkFeature {
_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);
}
......
......@@ -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<Offset> _kBottomUpTween = Tween<Offset>(
final Animatable<Offset> _kBottomUpTween = Tween<Offset>(
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<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;
......
......@@ -150,36 +150,32 @@ class RefreshIndicatorState extends State<RefreshIndicator> with TickerProviderS
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();
}
......
......@@ -536,6 +536,11 @@ class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTr
}
}
static final Animatable<double> _entranceTurnTween = Tween<double>(
begin: 1.0 - kFloatingActionButtonTurnInterval,
end: 1.0,
).chain(CurveTween(curve: Curves.easeIn));
void _updateAnimations() {
// 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<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 @@ class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTr
// 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);
......
......@@ -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<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;
......
......@@ -1010,10 +1010,10 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
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;
......
......@@ -11,7 +11,7 @@ import 'package:flutter/scheduler.dart';
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.
///
......
......@@ -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<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);
}
......
......@@ -243,29 +243,19 @@ class _AnimatedCrossFadeState extends State<AnimatedCrossFade> with TickerProvid
_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 @@ class _AnimatedCrossFadeState extends State<AnimatedCrossFade> with TickerProvid
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;
......
......@@ -323,12 +323,14 @@ class _DismissibleState extends State<Dismissible> with TickerProviderStateMixin
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 @@ class _DismissibleState extends State<Dismissible> with TickerProviderStateMixin
_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
),
);
});
}
}
......
......@@ -326,6 +326,8 @@ class _HeroFlight {
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 @@ 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<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 @@ class _HeroFlight {
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();
......
......@@ -1105,7 +1105,7 @@ class _AnimatedOpacityState extends ImplicitlyAnimatedWidgetState<AnimatedOpacit
@override
void didUpdateTweens() {
_opacityAnimation = _opacity.animate(animation);
_opacityAnimation = animation.drive(_opacity);
}
@override
......
......@@ -259,8 +259,8 @@ class _GlowController extends ChangeNotifier {
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);
}
......
......@@ -16,6 +16,9 @@ import 'overlay.dart';
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 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
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
......
......@@ -21,7 +21,7 @@ void main() {
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 @@ void main() {
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)));
......
......@@ -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', () {
......
......@@ -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:
......
......@@ -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
......
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())
......
......@@ -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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment