TweenAnimationBuilder for building custom animations without managing an AnimationController (#38317)
This commit is contained in:
parent
98ef956073
commit
97df403398
@ -247,6 +247,8 @@ class TextStyleTween extends Tween<TextStyle> {
|
||||
/// usually named `AnimatedFoo`, where `Foo` is the name of the non-animated
|
||||
/// version of that widget. Commonly used implicitly animated widgets include:
|
||||
///
|
||||
/// * [TweenAnimationBuilder], which animates any property expressed by
|
||||
/// a [Tween] to a specified target value.
|
||||
/// * [AnimatedAlign], which is an implicitly animated version of [Align].
|
||||
/// * [AnimatedContainer], which is an implicitly animated version of
|
||||
/// [Container].
|
||||
|
@ -917,6 +917,11 @@ class DefaultTextStyleTransition extends AnimatedWidget {
|
||||
/// }
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [TweenAnimationBuilder], which animates a property to a target value
|
||||
/// without requiring manual management of an [AnimationController].
|
||||
class AnimatedBuilder extends AnimatedWidget {
|
||||
/// Creates an animated builder.
|
||||
///
|
||||
|
249
packages/flutter/lib/src/widgets/tween_animation_builder.dart
Normal file
249
packages/flutter/lib/src/widgets/tween_animation_builder.dart
Normal file
@ -0,0 +1,249 @@
|
||||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/animation.dart';
|
||||
|
||||
import 'framework.dart';
|
||||
import 'implicit_animations.dart';
|
||||
import 'value_listenable_builder.dart';
|
||||
|
||||
/// [Widget] builder that animates a property of a [Widget] to a target value
|
||||
/// whenever the target value changes.
|
||||
///
|
||||
/// The type of the animated property ([Color], [Rect], [double], etc.) is
|
||||
/// defined via the type of the provided [tween] (e.g. [ColorTween],
|
||||
/// [RectTween], [Tween<double>], etc.).
|
||||
///
|
||||
/// The [tween] also defines the target value for the animation: When the widget
|
||||
/// first builds, it animates from [Tween.begin] to [Tween.end]. A new animation
|
||||
/// can be triggered anytime by providing a new [tween] with a new [Tween.end]
|
||||
/// value. The new animation runs from the current animation value (which may be
|
||||
/// [Tween.end] of the old [tween], if that animation completed) to [Tween.end]
|
||||
/// of the new [tween].
|
||||
///
|
||||
/// The animation is further customized by providing a [curve] and [duration].
|
||||
///
|
||||
/// The current value of the animation along with the [child] is passed to
|
||||
/// the [builder] callback, which is expected to build a [Widget] based on the
|
||||
/// current animation value. The [builder] is called throughout the animation
|
||||
/// for every animation value until [Tween.end] is reached.
|
||||
///
|
||||
/// A provided [onEnd] callback is called whenever an animation completes.
|
||||
/// Registering an [onEnd] callback my be useful to trigger an action (like
|
||||
/// another animation) at the end of the current animation.
|
||||
///
|
||||
/// ## Performance optimizations
|
||||
///
|
||||
/// If your [builder] function contains a subtree that does not depend on the
|
||||
/// animation, it's more efficient to build that subtree once instead of
|
||||
/// rebuilding it on every animation tick.
|
||||
///
|
||||
/// If you pass the pre-built subtree as the [child] parameter, the
|
||||
/// AnimatedBuilder will pass it back to your builder function so that you
|
||||
/// can incorporate it into your build.
|
||||
///
|
||||
/// Using this pre-built child is entirely optional, but can improve
|
||||
/// performance significantly in some cases and is therefore a good practice.
|
||||
///
|
||||
/// ## Ownership of the [Tween]
|
||||
///
|
||||
/// The [TweenAnimationBuilder] takes full ownership of the provided [tween]
|
||||
/// instance and it will mutate it. Once a [Tween] has been passed to a
|
||||
/// [TweenAnimationBuilder], its properties should not be accessed or changed
|
||||
/// anymore to avoid interference with the [TweenAnimationBuilder].
|
||||
///
|
||||
/// It is good practice to never store a [Tween] provided to a
|
||||
/// [TweenAnimationBuilder] in an instance variable to avoid accidental
|
||||
/// modifications of the [Tween].
|
||||
///
|
||||
/// ## Example Code
|
||||
///
|
||||
/// {@tool snippet --template=stateful_widget_scaffold}
|
||||
/// This example shows an [IconButton] that "zooms" in when the widget first
|
||||
/// builds (its size smoothly increases from 0 to 24) and whenever the button
|
||||
/// is pressed, it smoothly changes its size to the new target value of either
|
||||
/// 48 or 24.
|
||||
///
|
||||
/// ```dart
|
||||
/// double targetValue = 24.0;
|
||||
///
|
||||
/// @override
|
||||
/// Widget build(BuildContext context) {
|
||||
/// return Center(
|
||||
/// child: TweenAnimationBuilder(
|
||||
/// tween: Tween<double>(begin: 0, end: targetValue),
|
||||
/// duration: Duration(seconds: 1),
|
||||
/// builder: (BuildContext context, double size, Widget child) {
|
||||
/// return IconButton(
|
||||
/// iconSize: size,
|
||||
/// color: Colors.blue,
|
||||
/// icon: child,
|
||||
/// onPressed: () {
|
||||
/// setState(() {
|
||||
/// targetValue = targetValue == 24.0 ? 48.0 : 24.0;
|
||||
/// });
|
||||
/// },
|
||||
/// );
|
||||
/// },
|
||||
/// child: Icon(Icons.aspect_ratio),
|
||||
/// ),
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// ## Relationship to [ImplicitlyAnimatedWidget]s and [AnimatedWidget]s
|
||||
///
|
||||
/// The [ImplicitlyAnimatedWidget] has many subclasses that provide animated
|
||||
/// versions of regular widgets. These subclasses (like [AnimatedOpacity],
|
||||
/// [AnimatedContainer], [AnimatedSize], etc.) animate changes in their
|
||||
/// properties smoothly and they are easier to use than this general-purpose
|
||||
/// builder. However, [TweenAnimationBuilder] (which itself is a subclass of
|
||||
/// [ImplicitlyAnimatedWidget]) is handy for animating any widget property to a
|
||||
/// given target value even when the framework (or third-party widget library)
|
||||
/// doesn't ship with an animated version of that widget.
|
||||
///
|
||||
/// Those [ImplicitlyAnimatedWidget]s (including this [TweenAnimationBuilder])
|
||||
/// all manage an internal [AnimationController] to drive the animation. If you
|
||||
/// want more control over the animation than just setting a target value,
|
||||
/// [duration], and [curve], have a look at (subclasses of) [AnimatedWidget]s.
|
||||
/// For those, you have to manually manage an [AnimationController] giving you
|
||||
/// full control over the animation. An example of an [AnimatedWidget] is the
|
||||
/// [AnimatedBuilder], which can be used similarly to this
|
||||
/// [TweenAnimationBuilder], but unlike the latter it is powered by a
|
||||
/// developer-managed [AnimationController].
|
||||
class TweenAnimationBuilder<T> extends ImplicitlyAnimatedWidget {
|
||||
/// Creates a [TweenAnimationBuilder].
|
||||
///
|
||||
/// The properties [tween], [duration], and [builder] are required. The values
|
||||
/// for [tween], [curve], and [builder] must not be null.
|
||||
///
|
||||
/// The [TweenAnimationBuilder] takes full ownership of the provided [tween]
|
||||
/// instance and mutates it. Once a [Tween] has been passed to a
|
||||
/// [TweenAnimationBuilder], its properties should not be accessed or changed
|
||||
/// anymore to avoid interference with the [TweenAnimationBuilder].
|
||||
const TweenAnimationBuilder({
|
||||
Key key,
|
||||
@required this.tween,
|
||||
@required Duration duration,
|
||||
Curve curve = Curves.linear,
|
||||
@required this.builder,
|
||||
this.onEnd,
|
||||
this.child,
|
||||
}) : assert(tween != null),
|
||||
assert(curve != null),
|
||||
assert(builder != null),
|
||||
super(key: key, duration: duration, curve: curve);
|
||||
|
||||
/// Defines the target value for the animation.
|
||||
///
|
||||
/// When the widget first builds, the animation runs from [Tween.begin] to
|
||||
/// [Tween.end], if [Tween.begin] is non-null. A new animation can be
|
||||
/// triggered at anytime by providing a new [Tween] with a new [Tween.end]
|
||||
/// value. The new animation runs from the current animation value (which may
|
||||
/// be [Tween.end] of the old [tween], if that animation completed) to
|
||||
/// [Tween.end] of the new [tween]. The [Tween.begin] value is ignored except
|
||||
/// for the initial animation that is triggered when the widget builds for the
|
||||
/// first time.
|
||||
///
|
||||
/// Any (subclass of) [Tween] is accepted as an argument. For example, to
|
||||
/// animate the height or width of a [Widget], use a [Tween<double>], or
|
||||
/// check out the [ColorTween] to animate the color property of a [Widget].
|
||||
///
|
||||
/// Any [Tween] provided must have a non-null [Tween.end] value.
|
||||
///
|
||||
/// ## Ownership
|
||||
///
|
||||
/// The [TweenAnimationBuilder] takes full ownership of the provided [Tween]
|
||||
/// and it will mutate the [Tween]. Once a [Tween] instance has been passed
|
||||
/// to [TweenAnimationBuilder] its properties should not be accessed or
|
||||
/// changed anymore to avoid any interference with the
|
||||
/// [TweenAnimationBuilder]. If you need to change the [Tween], create a
|
||||
/// **new instance** with the new values.
|
||||
///
|
||||
/// It is good practice to never store a [Tween] provided to a
|
||||
/// [TweenAnimationBuilder] in an instance variable to avoid accidental
|
||||
/// modifications of the [Tween].
|
||||
final Tween<T> tween;
|
||||
|
||||
/// Called every time the animation value changes.
|
||||
///
|
||||
/// The current animation value is passed to the builder along with the
|
||||
/// [child]. The builder should build a [Widget] based on the current
|
||||
/// animation value and incorporate the [child] into it, if it is non-null.
|
||||
final ValueWidgetBuilder<T> builder;
|
||||
|
||||
/// The child widget to pass to the builder.
|
||||
///
|
||||
/// If a builder callback's return value contains a subtree that does not
|
||||
/// depend on the animation, it's more efficient to build that subtree once
|
||||
/// instead of rebuilding it on every animation tick.
|
||||
///
|
||||
/// If the pre-built subtree is passed as the child parameter, the
|
||||
/// [TweenAnimationBuilder] will pass it back to the [builder] function so
|
||||
/// that it can be incorporated into the build.
|
||||
///
|
||||
/// Using this pre-built child is entirely optional, but can improve
|
||||
/// performance significantly in some cases and is therefore a good practice.
|
||||
final Widget child;
|
||||
|
||||
/// Called every time an animation completes.
|
||||
///
|
||||
/// This can be useful to trigger additional actions (e.g. another animation)
|
||||
/// at the end of the current animation.
|
||||
final VoidCallback onEnd;
|
||||
|
||||
@override
|
||||
ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState() {
|
||||
return _TweenAnimationBuilderState<T>();
|
||||
}
|
||||
}
|
||||
|
||||
class _TweenAnimationBuilderState<T> extends AnimatedWidgetBaseState<TweenAnimationBuilder<T>> {
|
||||
Tween<T> _currentTween;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_currentTween = widget.tween;
|
||||
_currentTween.begin ??= _currentTween.end;
|
||||
super.initState();
|
||||
// The statusListener is removed when the superclass disposes the controller.
|
||||
controller.addStatusListener(_onAnimationStatusChanged);
|
||||
if (_currentTween.begin != _currentTween.end) {
|
||||
controller.forward();
|
||||
}
|
||||
}
|
||||
|
||||
void _onAnimationStatusChanged(AnimationStatus status) {
|
||||
switch (status) {
|
||||
case AnimationStatus.dismissed:
|
||||
case AnimationStatus.forward:
|
||||
case AnimationStatus.reverse:
|
||||
break;
|
||||
case AnimationStatus.completed:
|
||||
if (widget.onEnd != null) {
|
||||
widget.onEnd();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void forEachTween(TweenVisitor<dynamic> visitor) {
|
||||
assert(
|
||||
widget.tween.end != null,
|
||||
'Tween provided to TweenAnimationBuilder must have non-null Tween.end value.',
|
||||
);
|
||||
_currentTween = visitor(_currentTween, widget.tween.end, (dynamic value) {
|
||||
// Constructor will never be called because null is never provided as current tween.
|
||||
assert(false);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.builder(context, _currentTween.evaluate(animation), widget.child);
|
||||
}
|
||||
}
|
@ -106,6 +106,7 @@ export 'src/widgets/texture.dart';
|
||||
export 'src/widgets/ticker_provider.dart';
|
||||
export 'src/widgets/title.dart';
|
||||
export 'src/widgets/transitions.dart';
|
||||
export 'src/widgets/tween_animation_builder.dart';
|
||||
export 'src/widgets/unique_widget.dart';
|
||||
export 'src/widgets/value_listenable_builder.dart';
|
||||
export 'src/widgets/viewport.dart';
|
||||
|
390
packages/flutter/test/widgets/tween_animation_builder_test.dart
Normal file
390
packages/flutter/test/widgets/tween_animation_builder_test.dart
Normal file
@ -0,0 +1,390 @@
|
||||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Animates forward when built', (WidgetTester tester) async {
|
||||
final List<int> values = <int>[];
|
||||
int endCount = 0;
|
||||
await tester.pumpWidget(
|
||||
TweenAnimationBuilder<int>(
|
||||
duration: const Duration(seconds: 1),
|
||||
tween: IntTween(begin: 10, end: 110),
|
||||
builder: (BuildContext context, int i, Widget child) {
|
||||
values.add(i);
|
||||
return const Placeholder();
|
||||
},
|
||||
onEnd: () {
|
||||
endCount++;
|
||||
},
|
||||
),
|
||||
);
|
||||
expect(endCount, 0);
|
||||
expect(values, <int>[10]);
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect(values, <int>[10, 60]);
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 501));
|
||||
expect(endCount, 1);
|
||||
expect(values, <int>[10, 60, 110]);
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect(endCount, 1);
|
||||
expect(values, <int>[10, 60, 110]);
|
||||
});
|
||||
|
||||
testWidgets('No initial animation when begin=null', (WidgetTester tester) async {
|
||||
final List<int> values = <int>[];
|
||||
int endCount = 0;
|
||||
await tester.pumpWidget(
|
||||
TweenAnimationBuilder<int>(
|
||||
duration: const Duration(seconds: 1),
|
||||
tween: IntTween(end: 100),
|
||||
builder: (BuildContext context, int i, Widget child) {
|
||||
values.add(i);
|
||||
return const Placeholder();
|
||||
},
|
||||
onEnd: () {
|
||||
endCount++;
|
||||
},
|
||||
),
|
||||
);
|
||||
expect(endCount, 0);
|
||||
expect(values, <int>[100]);
|
||||
await tester.pump(const Duration(seconds: 2));
|
||||
expect(endCount, 0);
|
||||
expect(values, <int>[100]);
|
||||
});
|
||||
|
||||
|
||||
testWidgets('No initial animation when begin=end', (WidgetTester tester) async {
|
||||
final List<int> values = <int>[];
|
||||
int endCount = 0;
|
||||
await tester.pumpWidget(
|
||||
TweenAnimationBuilder<int>(
|
||||
duration: const Duration(seconds: 1),
|
||||
tween: IntTween(begin: 100, end: 100),
|
||||
builder: (BuildContext context, int i, Widget child) {
|
||||
values.add(i);
|
||||
return const Placeholder();
|
||||
},
|
||||
onEnd: () {
|
||||
endCount++;
|
||||
},
|
||||
),
|
||||
);
|
||||
expect(endCount, 0);
|
||||
expect(values, <int>[100]);
|
||||
await tester.pump(const Duration(seconds: 2));
|
||||
expect(endCount, 0);
|
||||
expect(values, <int>[100]);
|
||||
});
|
||||
|
||||
testWidgets('Replace tween animates new tween', (WidgetTester tester) async {
|
||||
final List<int> values = <int>[];
|
||||
Widget buildWidget({IntTween tween}) {
|
||||
return TweenAnimationBuilder<int>(
|
||||
duration: const Duration(seconds: 1),
|
||||
tween: tween,
|
||||
builder: (BuildContext context, int i, Widget child) {
|
||||
values.add(i);
|
||||
return const Placeholder();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildWidget(tween: IntTween(begin: 0, end: 100)));
|
||||
expect(values, <int>[0]);
|
||||
await tester.pump(const Duration(seconds: 2)); // finish first animation.
|
||||
expect(values, <int>[0, 100]);
|
||||
|
||||
await tester.pumpWidget(buildWidget(tween: IntTween(begin: 100, end: 200)));
|
||||
expect(values, <int>[0, 100, 100]);
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect(values, <int>[0, 100, 100, 150]);
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect(values, <int>[0, 100, 100, 150, 200]);
|
||||
});
|
||||
|
||||
testWidgets('Curve is respected', (WidgetTester tester) async {
|
||||
final List<int> values = <int>[];
|
||||
Widget buildWidget({IntTween tween, Curve curve}) {
|
||||
return TweenAnimationBuilder<int>(
|
||||
duration: const Duration(seconds: 1),
|
||||
tween: tween,
|
||||
curve: curve,
|
||||
builder: (BuildContext context, int i, Widget child) {
|
||||
values.add(i);
|
||||
return const Placeholder();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildWidget(tween: IntTween(begin: 0, end: 100), curve: Curves.easeInExpo));
|
||||
expect(values, <int>[0]);
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect(values.last, lessThan(50));
|
||||
expect(values.last, greaterThan(0));
|
||||
|
||||
await tester.pump(const Duration(seconds: 2)); // finish animation.
|
||||
|
||||
values.clear();
|
||||
// Update curve (and tween to re-trigger animation).
|
||||
await tester.pumpWidget(buildWidget(tween: IntTween(begin: 100, end: 200), curve: Curves.linear));
|
||||
expect(values, <int>[100]);
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect(values, <int>[100, 150]);
|
||||
});
|
||||
|
||||
testWidgets('Duration is respected', (WidgetTester tester) async {
|
||||
final List<int> values = <int>[];
|
||||
Widget buildWidget({IntTween tween, Duration duration}) {
|
||||
return TweenAnimationBuilder<int>(
|
||||
tween: tween,
|
||||
duration: duration,
|
||||
builder: (BuildContext context, int i, Widget child) {
|
||||
values.add(i);
|
||||
return const Placeholder();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildWidget(tween: IntTween(begin: 0, end: 100), duration: const Duration(seconds: 1)));
|
||||
expect(values, <int>[0]);
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect(values, <int>[0, 50]);
|
||||
|
||||
await tester.pump(const Duration(seconds: 2)); // finish animation.
|
||||
|
||||
values.clear();
|
||||
// Update duration (and tween to re-trigger animation).
|
||||
await tester.pumpWidget(buildWidget(tween: IntTween(begin: 100, end: 200), duration: const Duration(seconds: 2)));
|
||||
expect(values, <int>[100]);
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect(values, <int>[100, 125]);
|
||||
});
|
||||
|
||||
testWidgets('Child is integrated into tree', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: TweenAnimationBuilder<int>(
|
||||
tween: IntTween(begin: 0, end: 100),
|
||||
duration: const Duration(seconds: 1),
|
||||
builder: (BuildContext context, int i, Widget child) {
|
||||
return child;
|
||||
},
|
||||
child: const Text('Hello World'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('Hello World'), findsOneWidget);
|
||||
});
|
||||
|
||||
group('Change tween gapless while', () {
|
||||
testWidgets('running forward', (WidgetTester tester) async {
|
||||
final List<int> values = <int>[];
|
||||
Widget buildWidget({IntTween tween}) {
|
||||
return TweenAnimationBuilder<int>(
|
||||
tween: tween,
|
||||
duration: const Duration(seconds: 1),
|
||||
builder: (BuildContext context, int i, Widget child) {
|
||||
values.add(i);
|
||||
return const Placeholder();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildWidget(
|
||||
tween: IntTween(begin: 0, end: 100),
|
||||
));
|
||||
expect(values, <int>[0]);
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect(values, <int>[0, 50]);
|
||||
|
||||
// Change tween
|
||||
await tester.pumpWidget(buildWidget(
|
||||
tween: IntTween(begin: 200, end: 300),
|
||||
));
|
||||
expect(values, <int>[0, 50, 50]); // gapless: animation continues where it left off.
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect(values, <int>[0, 50, 50, 175]); // 175 = halfway between 50 and new target 300.
|
||||
|
||||
// Run animation to end
|
||||
await tester.pump(const Duration(seconds: 2));
|
||||
expect(values, <int>[0, 50, 50, 175, 300]);
|
||||
values.clear();
|
||||
});
|
||||
|
||||
testWidgets('running forward and then reverse with same tween instance', (WidgetTester tester) async {
|
||||
final List<int> values = <int>[];
|
||||
Widget buildWidget({IntTween tween}) {
|
||||
return TweenAnimationBuilder<int>(
|
||||
tween: tween,
|
||||
duration: const Duration(seconds: 1),
|
||||
builder: (BuildContext context, int i, Widget child) {
|
||||
values.add(i);
|
||||
return const Placeholder();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
final IntTween tween1 = IntTween(begin: 0, end: 100);
|
||||
final IntTween tween2 = IntTween(begin: 200, end: 300);
|
||||
|
||||
await tester.pumpWidget(buildWidget(
|
||||
tween: tween1,
|
||||
));
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
await tester.pumpWidget(buildWidget(
|
||||
tween: tween2,
|
||||
));
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
await tester.pump(const Duration(seconds: 2));
|
||||
expect(values, <int>[0, 50, 50, 175, 300]);
|
||||
values.clear();
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('Changing tween while gapless tween change is in progress', (WidgetTester tester) async {
|
||||
final List<int> values = <int>[];
|
||||
Widget buildWidget({IntTween tween}) {
|
||||
return TweenAnimationBuilder<int>(
|
||||
tween: tween,
|
||||
duration: const Duration(seconds: 1),
|
||||
builder: (BuildContext context, int i, Widget child) {
|
||||
values.add(i);
|
||||
return const Placeholder();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
final IntTween tween1 = IntTween(begin: 0, end: 100);
|
||||
final IntTween tween2 = IntTween(begin: 200, end: 300);
|
||||
final IntTween tween3 = IntTween(begin: 400, end: 501);
|
||||
|
||||
await tester.pumpWidget(buildWidget(
|
||||
tween: tween1,
|
||||
));
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect(values, <int>[0, 50]);
|
||||
values.clear();
|
||||
|
||||
// Change tween
|
||||
await tester.pumpWidget(buildWidget(
|
||||
tween: tween2,
|
||||
));
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect(values, <int>[50, 175]);
|
||||
values.clear();
|
||||
|
||||
await tester.pumpWidget(buildWidget(
|
||||
tween: tween3,
|
||||
));
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect(values, <int>[175, 338, 501]);
|
||||
});
|
||||
|
||||
testWidgets('Changing curve while no animation is running does not trigger animation', (WidgetTester tester) async {
|
||||
final List<int> values = <int>[];
|
||||
Widget buildWidget({Curve curve}) {
|
||||
return TweenAnimationBuilder<int>(
|
||||
tween: IntTween(begin: 0, end: 100),
|
||||
curve: curve,
|
||||
duration: const Duration(seconds: 1),
|
||||
builder: (BuildContext context, int i, Widget child) {
|
||||
values.add(i);
|
||||
return const Placeholder();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildWidget(
|
||||
curve: Curves.linear,
|
||||
));
|
||||
await tester.pump(const Duration(seconds: 2));
|
||||
expect(values, <int>[0, 100]);
|
||||
values.clear();
|
||||
|
||||
await tester.pumpWidget(buildWidget(
|
||||
curve: Curves.easeInExpo,
|
||||
));
|
||||
expect(values, <int>[100]);
|
||||
await tester.pump(const Duration(seconds: 2));
|
||||
expect(values, <int>[100]);
|
||||
});
|
||||
|
||||
testWidgets('Setting same tween and direction does not trigger animation', (WidgetTester tester) async {
|
||||
final List<int> values = <int>[];
|
||||
Widget buildWidget({IntTween tween}) {
|
||||
return TweenAnimationBuilder<int>(
|
||||
tween: tween,
|
||||
duration: const Duration(seconds: 1),
|
||||
builder: (BuildContext context, int i, Widget child) {
|
||||
values.add(i);
|
||||
return const Placeholder();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildWidget(
|
||||
tween: IntTween(begin: 0, end: 100),
|
||||
));
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect(values, <int>[0, 50, 100]);
|
||||
values.clear();
|
||||
|
||||
await tester.pumpWidget(buildWidget(
|
||||
tween: IntTween(begin: 0, end: 100),
|
||||
));
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect(values, everyElement(100));
|
||||
});
|
||||
|
||||
testWidgets('Setting same tween and direction while gapless animation is in progress works', (WidgetTester tester) async {
|
||||
final List<int> values = <int>[];
|
||||
Widget buildWidget({IntTween tween}) {
|
||||
return TweenAnimationBuilder<int>(
|
||||
tween: tween,
|
||||
duration: const Duration(seconds: 1),
|
||||
builder: (BuildContext context, int i, Widget child) {
|
||||
values.add(i);
|
||||
return const Placeholder();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildWidget(
|
||||
tween: IntTween(begin: 0, end: 100),
|
||||
));
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect(values, <int>[0, 50]);
|
||||
await tester.pumpWidget(buildWidget(
|
||||
tween: IntTween(begin: 200, end: 300),
|
||||
));
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect(values, <int>[0, 50, 50, 175]);
|
||||
|
||||
await tester.pumpWidget(buildWidget(
|
||||
tween: IntTween(begin: 200, end: 300),
|
||||
));
|
||||
expect(values, <int>[0, 50, 50, 175, 175]);
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect(values, <int>[0, 50, 50, 175, 175, 300]);
|
||||
|
||||
values.clear();
|
||||
await tester.pump(const Duration(seconds: 2));
|
||||
expect(values, everyElement(300));
|
||||
});
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user