[Flutter Animation] newbie to advanced ~2. multiple effects and TweenSequence~

Shohei Ogawa(@heyhey1028)
5 min readNov 12, 2022

This article is the second in a series of articles on animation.

In vol. 1, we’ve gone through on some of the basics of animation.

For next step, let us look into more complex animations.

Complex animations can include the following

  • Multiple animation effects on a single widget at the same time
  • Sequenced animation on a single widget
  • A single widget with different animations at different times
  • Staggered animation for multiple widgets
  • Animate multiple Widgets separately

Let’s look at them one by one

Applying multiple animations on a single widget

In previous article, I have applied only an alignment animation on a single widget, but what if I want to change the alignment while rotating at the same time?

Conclusion: Generate multiple animations from a single AnimationController, and bind them to a Widget

I know I’m jumping to conclusions, but this use case is simple!

Prepare a Tween for each animation effect (position, rotation, color, size, etc.) you want to add, and generate an Animation using that Tween and the AnimationController.

In the following example, a widget has animating position (alignment) and rotation (rotation).

Sample code

class _MultipleEffectState extends State<MultipleEffect>
with SingleTickerProviderStateMixin {
late AnimationController controller;
late Tween<Alignment> alignmentTween; // <<< Tween for first animation
late Tween<double> rotateTween; // <<< Tween for second animation
late Animation<Alignment> alignmentAnimation; // <<< first animation
late Animation<double> rotateAnimation; // <<< second animation

@override
void initState() {
controller =
AnimationController(duration: const Duration(seconds: 3), vsync: this);
alignmentTween = Tween(
begin: Alignment.topCenter,
end: Alignment
.bottomCenter); // <<< define start and end value of alignment animation
rotateTween = Tween(
begin: 0,
end: 8 * pi); // <<< define start and end value of rotation animation
alignmentAnimation =
controller.drive(alignmentTween); // <<< create align animation
rotateAnimation =
controller.drive(rotateTween); // <<< create rotation animation
super.initState();
}

@override
void dispose() {
controller.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.orange[300],
title: const Text('Multiple Effect'),
),
drawer: const MainDrawer(),
body: AnimatedBuilder(
animation: controller,
builder: (context, _) {
return Align(
alignment: alignmentAnimation.value, // <<< bind align animation
child: Transform.rotate(
angle: rotateAnimation.value, // <<< bind rotation animation
child: const Text('Hello world!'),
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
controller.forward();
},
backgroundColor: Colors.yellow[700],
child: const Icon(
Icons.bolt,
color: Colors.black,
),
),
);
}
}

Sequenced animation on a single widget

In the following use case, the same animation effect is applied to a Widget multiple times while changing its position

For example, if we want a Widget to move in the shape of a rectangle, we need to apply the animation to change position multiple times, first to the right, then to the bottom, then to the left, then to the top, and so on.

We can use TweenSequence class in such cases

TweenSequence class

TweenSequence is a Tween that defines a sequence of changes.

TweenSequence allows you to specify multiple changes by passing an array of TweenSequenceItem, a class that defines how long and what kind of changes you want to make.

tweenSequnece = TweenSequence<T>([
TweenSequenceItem(tween: Tween(),weight:1),
TweenSequenceITem(tween: Tween(),weight:1),
TweenSequenceITem(tween: Tween(),weight:1),
]);

TweenSequence inherits Animatable class, which is the same super class as Tween, meaning it can be handled in the same way as Tween.

So you can generate an Animation using the defined TweenSequence and AnimationController. By binding this Animation to a widget, you can make a widget have an animation that changes multiple times.

animation = controller.drive(TweenSequence([]))

TweenSequenceItem class

TweenSequenceItem is a class that can be passed to TweenSequences. Each classes are defined with what kind of change you want to tween parameter and how long to weight parameter.

TweenSequenceItem(
tween: Tween(
begin: const Alignment(-1, 3),
end: Alignment.topLeft,
),
weight: 2,
)

weight is a ratio of time. It determines how much of the time (Duration) defined in the AnimationController will be allocated for that specific tween.

Like flex in the Flexible class, the Duration divided by the total of the weight is the amount of time that each TweenSequenceItem has.

Sample code

class _SequenceAnimationState extends State<SequenceAnimation>
with SingleTickerProviderStateMixin {
late AnimationController controller;
late TweenSequence<Alignment>
tweenSequence; // <<< define as TweenSequence, not Tween
late Animation<Alignment> animation;

@override
void initState() {
controller =
AnimationController(duration: const Duration(seconds: 4), vsync: this);
tweenSequence = TweenSequence<Alignment>([
// <<< can take in multiple TweenSequenceItem classes
TweenSequenceItem(
tween: Tween(begin: Alignment.topLeft, end: Alignment.topRight),
weight:
1 // <<< in this example, duration is 4 seconds, so weight:1 means this Tween will be applied for 1 second
),
TweenSequenceItem(
tween: Tween(begin: Alignment.topRight, end: Alignment.bottomRight),
weight: 1),
TweenSequenceItem(
tween: Tween(begin: Alignment.bottomRight, end: Alignment.bottomLeft),
weight: 1),
TweenSequenceItem(
tween: Tween(begin: Alignment.bottomLeft, end: Alignment.topLeft),
weight: 1),
]);
animation = controller
.drive(tweenSequence); // <<< create Animation just like using a Tween
super.initState();
}

@override
void dispose() {
controller.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.green[300],
title: const Text('Sequence Animation'),
),
drawer: const MainDrawer(),
body: AnimatedBuilder(
animation: controller,
builder: (context, _) {
return Align(
alignment: animation.value, // <<< simply bind the animation
child: const Text('Hello world!'));
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
controller.forward();
},
backgroundColor: Colors.yellow[700],
child: const Icon(
Icons.bolt,
color: Colors.black,
),
),
);
}
}

Sample Repository

what’s next?

The remaining three use cases will be described in the following article

  • A single widget with different animations at different times
  • Staggered animation for multiple widgets
  • Animate multiple Widgets separately

Reference

--

--

Shohei Ogawa(@heyhey1028)

Flutter dev based in Tokyo, Japan. Passionate about flutter, dart, firebase and shaping the future with own hands. #flutterdev #dart #firebase #web3 #devOps