Animating in Flutter Makes the World Go Round
Samantha H.
2020-11-25
5min
Development
There are so many animations to be made and Flutter has a big support system for doing so. Here’s how to make your components fly, sail, float, race.
As someone who had only just started developing in Flutter, animating components really was a challenge. I am definitely not claiming to be a pro in this and I believe there is so much more about the complexity of animating in Flutter that I could learn, but I’ll try and write a summary of everything I’ve learnt through researching this and through my experience with animating. Hopefully it can help you understand the topic a bit more too!
Flutter has a really rich support when it comes to animating components. This is no surprise considering Flutter is primarily a UI framework. There are many widgets you can choose from when animating in Flutter, from a simple AnimatedWidget that changes when its ‘Listenable’ changes its value, to Hero, used to animate a repeating component when changing between screens.
While these different animations can be topics within themselves and possible topics for another blog, here, the main ‘animating feature’ I will be focusing on is moving a component across the screen, while also providing insight on how to manipulate this component by speeding it up or making it disappear.
The widget that I used for this specific animation that I made was an AnimatedContainer.
Duration is required. The duration is the amount of time you want it to take for the animation to happen.
Curves make the animation much smoother, if that’s the kind of animation that you’re going for. There are loads of different curve properties that you can choose from. I didn’t use any as I didn't want the animation to change its speed throughout the animation cycle.
AnimatedContainer is a good choice of widget if you want to define what you want to happen at the start and what you want to happen at the end. The AnimatedContainer then worries about how to get it from start to finish, meaning you don’t have to.
When starting out in animating my component to ‘sail’ from one side of the screen to the other, the first thing that I made was a model, and in it I defined the points (coordinates), the angle and whether the boat was flipped or not. I defined the angle because I ran into an issue where my boat component was facing the wrong direction. I defined the property flip to see if I needed to flip my boat image the other way. If the component you’re animating is a shape like a circle that isn't facing a certain direction, there is no need for these two properties. After that, I created an array of these models, so that it would be easier to keep track of all of the different animation paths that I need.
class BoatAnimationModel{ final double startPointX; final double startPointY; final double endPointX; final double endPointY; final double angle; final bool flip; const BoatAnimationModel(this.startPointX, this.startPointY, this.endPointX, this.endPointY, this.angle, this.flip);
} // paths
// index - startPointX - startPointY - endPointX - endPointY - angle
const boatAnimations=[ BoatAnimationModel(1.4, 0.5, -1.8, -0.1, -0.2, true), BoatAnimationModel(-1.4, -0.8, 1.6, -0.1, 0.1, false), BoatAnimationModel(-1.6, -0.7, 1.6, 0.1, 0.2, false), BoatAnimationModel(0.5, -1.2, -1.6, 0, -0.2, true), BoatAnimationModel(1.6, -0.1, -1.6, -0.1, 0, true),
];
The next thing I worked on is implementing the actual animation itself. I started off by creating a StatefulWidget in which I defined:
currentAnimation (which BoatAnimationModel should be used from the array)
flip (this is a boolean to see whether I need to flip the image or not)
alignment
duration
angle
I made a function called setNewAnimation in which I’m setting a path that it will take. The function first says that for a duration of 1 millisecond it should move to the starting point (the duration is set this short so it doesn’t really animate, but instead it just moves there). Then I set a new duration and alignment to say where it should go and how long it should take to get there. To accomplish this I defined a callback WidgetsBinding.instance.addPostFrameCallback (this is a function that is executed after building) in which I set the state of my boats to make the alignment equal to the variable alignmentEnd. At the very end of the function, I implemented a delay (giving it the same duration as the duration of the animation), and I call this same function again, so as to create a recursion loop. I call this function in my initState.
import 'dart:async';
import 'dart:math' as math; import 'package:flutter/material.dart';
import 'package:flutter_app_boat/BoatAnimationModel.dart';
import 'package:flutter_svg/svg.dart'; void main()=>runApp(MaterialApp(title:'Flutter Demo', home:HomeScreen())); class HomeScreen extends StatefulWidget{ @override _HomeScreenState createState()=>_HomeScreenState();
} const _animationDuration=18000; class _HomeScreenState extends State{ int currentAnimation=0; double angle; bool flip=false; Duration duration; Alignment alignment; @override void initState(){ super.initState(); setNewAnimation(); } // Call this, whenever you want a new animation to start
void setNewAnimation(){ if (++currentAnimation >=boatAnimations.length) currentAnimation=0; // Get the current animation path that we want BoatAnimationModel animation=boatAnimations[currentAnimation]; // Set starting points setState((){ duration=Duration(milliseconds:1); alignment=Alignment(animation.startPointX, animation.startPointY); angle=animation.angle; flip=animation.flip; }); WidgetsBinding.instance.addPostFrameCallback((timeStamp){ setState((){ duration=Duration(milliseconds:_animationDuration); alignment=Alignment(animation.endPointX, animation.endPointY); }); }); // Set starting points // Make the next animation play after the first one is complete Future.delayed(const Duration(milliseconds:_animationDuration), ()=>setNewAnimation());
}
The only thing that was left to do is add the actual AnimatedContainer. I added a Container with a background gradient. The child of the Container was the actual AnimatedContainer. In here I simply provided the duration that it should take to finish the animation and the alignment. I also added the actual component that I wanted to animate (Note:for this project I used the flutter_svg package, so as to add my .svg file as my object to animate). I wrapped my SvgPicture with a Transform component and provided a transform property to either flip the image or not, and then within that I added another Transform component and extended it with the rotate class, in order to provide it an angle.
@override
Widget build(BuildContext context){ return Container( decoration:const BoxDecoration( gradient:const LinearGradient( begin:Alignment.topCenter, end:Alignment.bottomCenter, colors:[ Colors.lightBlueAccent, Colors.blueAccent, ], ), ), child:AnimatedContainer( duration:duration, alignment:alignment, child:Transform( transform:Matrix4.rotationY(flip ? math.pi :0), child:Transform.rotate( angle:angle, child:SvgPicture.asset( 'images/boat2.svg', width:32, height:32, ), ), ), ), );
}
And the final result looks like this:
One thing to note is another way I could’ve made this animation would have been to use an AnimatedController. However, for my specific task that I had to make, using an AnimatedContainer worked great for me because it was such a simple animation. I’m hoping this blog helped it become slightly more clear on how to animate in Flutter!
Subscribe to our newsletter
We send bi-weekly blogs on design, technology and business topics.