Skip to content

Commit

Permalink
add lifecycle methods to playanimation and customanimation
Browse files Browse the repository at this point in the history
  • Loading branch information
felixblaschke committed Oct 3, 2021
1 parent f14fe9a commit 4e73ff3
Show file tree
Hide file tree
Showing 10 changed files with 205 additions and 22 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## next
## 4.0.0

- **New:** Added lifecycle callbacks `onStart` and `onComplete` for `PlayAnimation` and `CustomAnimation` widgets.
- **Breaking change:** The liquid feature has been separated into an own package [sa3_liquid](https://pub.dev/packages/sa3_liquid).
- **Breaking change:** Removed deprecated uppercase variants of `CustomAnimationControl` enum. Use the lower case ones.
- **Update:** Removed supercharged dependency
Expand Down
46 changes: 38 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,24 @@ var widget = PlayAnimation<Color?>(
);
```

#### Track animation status

You can track the status of the animation by setting the `onStart` and `onComplete` callbacks.

```dart
import 'package:flutter/material.dart';
import 'package:simple_animations/simple_animations.dart';
var widget = PlayAnimation<Color?>(
// Track animation status
onStart: () => print('Animation started'),
onComplete: () => print('Animation complete'),
tween: ColorTween(begin: Colors.red, end: Colors.blue),
builder: (context, child, value) =>
Container(color: value, width: 100, height: 100),
);
```

#### Working with child widgets

Animations are highly demanding because parts of your apps are recomputed many times per second. It's important to keep these computations as low as possible.
Expand Down Expand Up @@ -434,7 +452,7 @@ var widget = MirrorAnimation<Color?>(

Use `CustomAnimation` if the animation widgets discussed above aren't sufficient for you use case. Beside all parameters mentioned for `PlayAnimation` it allows you actively control the animation.

#### Take over control
#### Control the animation

The `control` parameter can be set to the following values:

Expand Down Expand Up @@ -517,7 +535,7 @@ var widget = CustomAnimation<Color?>(
// set start position at 50%
startPosition: 0.5,
// full duration is 10 seconds
// full duration is 5 seconds
duration: const Duration(seconds: 5),
tween: ColorTween(begin: Colors.red, end: Colors.blue),
Expand All @@ -527,17 +545,29 @@ var widget = CustomAnimation<Color?>(
);
```

This animation will start playing right in the middle of the specified animation and only will animate for 5 seconds.

#### Listen to AnimationStatus
This animation will start playing right in the middle of the specified animation and only will animate for 2.5 seconds.

Behind the scenes there is an `AnimationController` processing the animation. `CustomAnimation` exposes it's `AnimationStatusListener` to enable you to react to finished animations.
#### Track animation status

You can specify your own listener at the `animationStatusListener` parameter.
You can track the status of the animation by setting the `onStart` and `onComplete` callbacks.

```dart
// ignore_for_file: avoid_print
import 'package:flutter/material.dart';
import 'package:simple_animations/simple_animations.dart';
var widget = CustomAnimation<Color?>(
onStart: () => print('Animation started'),
onComplete: () => print('Animation complete'),
tween: ColorTween(begin: Colors.red, end: Colors.blue),
builder: (context, child, value) {
return Container(color: value, width: 100, height: 100);
},
);
```

Or you can access the [`AnimationStatusListener`](https://api.flutter.dev/flutter/animation/AnimationStatusListener.html) within the internal [`AnimationController`](https://api.flutter.dev/flutter/animation/AnimationController-class.html).

```dart
import 'package:flutter/material.dart';
import 'package:simple_animations/simple_animations.dart';
Expand Down
45 changes: 39 additions & 6 deletions lib/stateless_animation/custom_animation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ enum CustomAnimationControl {
/// The [curve] parameter can be used to apply a non-linear animation
/// to your tween.
///
/// The callbacks [onStart] and [onComplete] can be used to track the
/// start and end of an animation.
///
/// If you want to start your animation at alternative position, you
/// can set a [startPosition] that takes values between `0.0` (start)
/// and `1.0` (end).
Expand Down Expand Up @@ -93,6 +96,8 @@ class CustomAnimation<T> extends StatefulWidget {
final double startPosition;
final int? fps;
final bool developerMode;
final VoidCallback? onStart;
final VoidCallback? onComplete;

/// Creates a new CustomAnimation widget.
/// See class documentation for more information.
Expand All @@ -108,6 +113,8 @@ class CustomAnimation<T> extends StatefulWidget {
this.animationStatusListener,
this.fps,
this.developerMode = false,
this.onStart,
this.onComplete,
Key? key,
}) : assert(startPosition >= 0 && startPosition <= 1,
'The property startPosition must have a value between 0.0 and 1.0.'),
Expand All @@ -121,9 +128,10 @@ class _CustomAnimationState<T> extends State<CustomAnimation<T>>
with AnimationMixin {
late AnimationController aniController;
late Animation<T> _animation;
bool _isDisposed = false;
bool _waitForDelay = true;
bool _isControlSetToMirror = false;
var _isDisposed = false;
var _waitForDelay = true;
var _isControlSetToMirror = false;
var _isPlaying = false;

@override
void initState() {
Expand All @@ -133,9 +141,7 @@ class _CustomAnimationState<T> extends State<CustomAnimation<T>>

_buildAnimation();

if (widget.animationStatusListener != null) {
aniController.addStatusListener(widget.animationStatusListener!);
}
aniController.addStatusListener(_onAnimationStatus);

asyncInitState();
super.initState();
Expand Down Expand Up @@ -177,6 +183,7 @@ class _CustomAnimationState<T> extends State<CustomAnimation<T>>
}

if (widget.control == CustomAnimationControl.stop) {
_trackPlaybackComplete();
aniController.stop();
}
if (widget.control == CustomAnimationControl.play) {
Expand Down Expand Up @@ -205,6 +212,32 @@ class _CustomAnimationState<T> extends State<CustomAnimation<T>>
}
}

void _onAnimationStatus(AnimationStatus status) {
widget.animationStatusListener?.call(status);

if (status == AnimationStatus.forward ||
status == AnimationStatus.reverse) {
_trackPlaybackStart();
} else if (status == AnimationStatus.dismissed ||
status == AnimationStatus.completed) {
_trackPlaybackComplete();
}
}

void _trackPlaybackStart() {
if (!_isPlaying) {
_isPlaying = true;
widget.onStart?.call();
}
}

void _trackPlaybackComplete() {
if (_isPlaying) {
_isPlaying = false;
widget.onComplete?.call();
}
}

@override
Widget build(BuildContext context) {
return widget.builder(context, widget.child, _animation.value);
Expand Down
9 changes: 9 additions & 0 deletions lib/stateless_animation/play_animation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import 'custom_animation.dart';
/// The [curve] parameter can be used to apply a non-linear animation
/// to your tween.
///
/// The callbacks [onStart] and [onComplete] can be used to track the
/// start and end of an animation.
///
/// You can optionally limit the framerate (fps) of the animation by
/// setting the [fps] value.
///
Expand All @@ -40,6 +43,8 @@ class PlayAnimation<T> extends StatelessWidget {
final Curve curve;
final int? fps;
final bool developerMode;
final VoidCallback? onStart;
final VoidCallback? onComplete;

/// Creates a new PlayAnimation widget.
/// See class documentation for more information.
Expand All @@ -52,6 +57,8 @@ class PlayAnimation<T> extends StatelessWidget {
this.child,
this.fps,
this.developerMode = false,
this.onStart,
this.onComplete,
Key? key,
}) : super(key: key);

Expand All @@ -66,6 +73,8 @@ class PlayAnimation<T> extends StatelessWidget {
fps: fps,
developerMode: developerMode,
child: child,
onStart: onStart,
onComplete: onComplete,
);
}
}
73 changes: 73 additions & 0 deletions test/stateless_animation/onstart_oncomplete_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:simple_animations/simple_animations.dart';

import './widget_tester_extension.dart';

void main() {
testWidgets('CustomAnimation forward', (WidgetTester tester) async {
var playing = false;
final animation = MaterialApp(
home: CustomAnimation<int>(
control: CustomAnimationControl.play,
duration: const Duration(days: 100),
tween: IntTween(begin: 0, end: 100),
builder: (context, child, value) => Container(),
onStart: () => playing = true,
onComplete: () => playing = false,
));

expect(playing, false);

await tester.addAnimationWidget(animation);
await tester.wait(const Duration(days: 1));
expect(playing, true);

await tester.wait(const Duration(days: 100));
expect(playing, false);
});

testWidgets('CustomAnimation backwards', (WidgetTester tester) async {
var playing = false;
final animation = MaterialApp(
home: CustomAnimation<int>(
control: CustomAnimationControl.playReverse,
startPosition: 1.0,
duration: const Duration(days: 100),
tween: IntTween(begin: 0, end: 100),
builder: (context, child, value) => Container(),
onStart: () => playing = true,
onComplete: () => playing = false,
));

expect(playing, false);

await tester.addAnimationWidget(animation);
await tester.wait(const Duration(days: 1));
expect(playing, true);

await tester.wait(const Duration(days: 100));
expect(playing, false);
});

testWidgets('PlayAnimation forward', (WidgetTester tester) async {
var playing = false;
final animation = MaterialApp(
home: PlayAnimation<int>(
duration: const Duration(days: 100),
tween: IntTween(begin: 0, end: 100),
builder: (context, child, value) => Container(),
onStart: () => playing = true,
onComplete: () => playing = false,
));

expect(playing, false);

await tester.addAnimationWidget(animation);
await tester.wait(const Duration(days: 1));
expect(playing, true);

await tester.wait(const Duration(days: 100));
expect(playing, false);
});
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// ignore_for_file: avoid_print

//@start
import 'package:flutter/material.dart';
import 'package:simple_animations/simple_animations.dart';

Expand All @@ -14,3 +14,4 @@ var widget = CustomAnimation<Color?>(
}
},
);
//@end
14 changes: 14 additions & 0 deletions tool/templates/code/stateless_animation/ca_lifecycle.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// ignore_for_file: avoid_print
//@start
import 'package:flutter/material.dart';
import 'package:simple_animations/simple_animations.dart';

var widget = CustomAnimation<Color?>(
onStart: () => print('Animation started'),
onComplete: () => print('Animation complete'),
tween: ColorTween(begin: Colors.red, end: Colors.blue),
builder: (context, child, value) {
return Container(color: value, width: 100, height: 100);
},
);
//@end
14 changes: 14 additions & 0 deletions tool/templates/code/stateless_animation/pa_lifecycle.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// ignore_for_file: avoid_print
//@start
import 'package:flutter/material.dart';
import 'package:simple_animations/simple_animations.dart';

var widget = PlayAnimation<Color?>(
// Track animation status
onStart: () => print('Animation started'),
onComplete: () => print('Animation complete'),
tween: ColorTween(begin: Colors.red, end: Colors.blue),
builder: (context, child, value) =>
Container(color: value, width: 100, height: 100),
);
//@end
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ var widget = CustomAnimation<Color?>(
// set start position at 50%
startPosition: 0.5,

// full duration is 10 seconds
// full duration is 5 seconds
duration: const Duration(seconds: 5),

tween: ColorTween(begin: Colors.red, end: Colors.blue),
Expand Down
18 changes: 13 additions & 5 deletions tool/templates/doc/stateless_animation.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ You can enrich your animation with non-linear behavior by supplying a `Curve` to

@code tool/templates/code/stateless_animation/pa_curve.dart

#### Track animation status

You can track the status of the animation by setting the `onStart` and `onComplete` callbacks.

@code tool/templates/code/stateless_animation/pa_lifecycle.dart

#### Working with child widgets

Animations are highly demanding because parts of your apps are recomputed many times per second. It's important to keep these computations as low as possible.
Expand Down Expand Up @@ -100,7 +106,7 @@ A `MirrorAnimation` repeatedly plays the specified `tween` from the start to the

Use `CustomAnimation` if the animation widgets discussed above aren't sufficient for you use case. Beside all parameters mentioned for `PlayAnimation` it allows you actively control the animation.

#### Take over control
#### Control the animation

The `control` parameter can be set to the following values:

Expand All @@ -126,12 +132,14 @@ You can modify the initial position by setting the `startPosition` parameter.

@code tool/templates/code/stateless_animation/start_position.dart

This animation will start playing right in the middle of the specified animation and only will animate for 5 seconds.
This animation will start playing right in the middle of the specified animation and only will animate for 2.5 seconds.

#### Track animation status

#### Listen to AnimationStatus
You can track the status of the animation by setting the `onStart` and `onComplete` callbacks.

Behind the scenes there is an `AnimationController` processing the animation. `CustomAnimation` exposes it's `AnimationStatusListener` to enable you to react to finished animations.
@code tool/templates/code/stateless_animation/ca_lifecycle.dart

You can specify your own listener at the `animationStatusListener` parameter.
Or you can access the [`AnimationStatusListener`](https://api.flutter.dev/flutter/animation/AnimationStatusListener.html) within the internal [`AnimationController`](https://api.flutter.dev/flutter/animation/AnimationController-class.html).

@code tool/templates/code/stateless_animation/animation_status.dart

0 comments on commit 4e73ff3

Please sign in to comment.