Last month I participated to the Flutter clock challenge with a coworker. The goal was to build a clock with Flutter for an iot device. Our biggest challenge was to create an animation between digits. We wanted each digit to transform their shapes.
This type of shape animation is called path morphing.
In this tutorial, I will show you how to animate an image from one shape to another in your flutter app. At the end of this article, you will learn how to do this:
First if you're new to flutter, you can learn how to get started here.
You can find the source code for this app on this repo.
We found out that there are many ways to do path morphing with flutter.
In Flutter there is already a built-in widget that allows you to change the shape of a container programmatically.
It's a very powerful tool, but it doesn't fit our needs. We can't make a container look like a digit.
Rive is a web application that allows you to create beautiful animation, to export them and use them in your Flutter application.
Jeff Dalaney made a great tutorial on how to use flare with flutter.
Again it's a very powerful tool, but the drawback is that you have to edit the vertices manually. It means moving all the points of your path with your mouse like this:
It's an imprecise and repetitive process.
We are looking for a way to create a path morphing animation between any SVG images.
To understand how we created the animation, I need to talk about a few tools that we're going to use.
We first have to learn what an SVG image is.
SVG stands for Scalable vector graphics. It's used to define vector-based graphics for the Web. SVG defines the graphics in XML format. Every element and every attribute in SVG files can be animated.
SVG can be defined with many objects like rectangle, circle or path.
We need to use the path, it's defined with the following commands:
Figma is my favorite software to create an interface. We'll use it to export our SVG image. Let's create an animation between two digits.
To export an SVG, select a text and at the bottom right click on export.
We need two shapes to create the animation so let export a "1" and a "2".
Path_morph is a package that lets you smoothly morph one Flutter Path object into another.
++pre>SampledPathData data = PathMorph.samplePaths(path1, path2);++/pre>
With this SampledPathData we can create an animation using the function:
++pre>PathMorph.generateAnimations();++/pre>
But an SVG is not a Flutter path object. To use path_morph we need to create a path object from the SVG we exported.
Path_drawing is flutter package that allows us to parse SVG data to create a flutter path object.
++pre>++code> Path parseSvgPathData(String svg);++/pre>
The function takes the "path" part of an SVG. Open your SVG file with a text editor.
It should be like this:
++pre><svg>
<path d="THE_STRING_WERE_LOOKING_FOR" fill="#FFFFFF"/>
</svg>++/pre>
So, you create a flutter path object like this:
++pre>Path path1 parseSvgPathData("THE_STRING_WERE_LOOKING_FOR");++/pre>
We have our image, but we can't change his size with width and height. We need to transform the path with a Matrix4.
Without digging too much in the maths, if we transform our path with the identity matrix, it will stay the same. But with flutter, we can scale our identity matrix.
++pre>Const SCALE_RATION = 0.8;
final matrix4 = Matrix4.identity()..scale(SCALE_RATIO, SCALE_RATIO);
Path path1 = parseSvgData("THE_STRING_WERE_LOOKING_FOR").transform(matrix4.strorage);++/pre>
Now our image is 20% smaller.
An Animation Controller is a class that allows you to Play an animation forward, in reverse, or stop the animation. It produces values that range from 0.0 to 1.0, during a given duration. These values are generated whenever the device's screen is ready to display a new frame.
Animation Controller needs a TickerProvider. We can extend our class with a SingleTickerProviderStateMixin.
++pre>controller =
AnimationController(vsync: this, duration: Duration(seconds: 1));++/pre>
Now we have all the key to code our animation let's take a look at how we use these tools together:
++pre>class _MyApp extends State<MyApp> with SingleTickerProviderStateMixin {
SampledPathData data;
AnimationController controller;
@override
void initState() {
super.initState();
final matrix4 = Matrix4.identity()..scale(SCALE_RATIO, SCALE_RATIO);
Path path1 = parseSvgPathData(ONE_PATH).transform(matrix4.storage);
Path path2 = parseSvgPathData(TWO_PATH).transform(matrix4.storage);
data = PathMorph.samplePaths(path1, path2);
controller =
AnimationController(vsync: this, duration: Duration(seconds: 1));
PathMorph.generateAnimations(controller, data, func);
}
void func(int i, Offset z) {
setState(() {
data.shiftedPoints[i] = z;
});
}
}++/pre>
Customaint is a widget that allows us to draw a painting on a canvas. It takes a painter as argument.
A painter is a class with two methods to override: paint and shouldRepaint.
Paint is called when the object needs to be repainted.
shouldRepaint is called when a new instance of the class is provided.
We want to paint our digits, so let's create a class with a path as argument:
++pre>class MyPainter extends CustomPainter {
Path path;
var myPaint;
MyPainter(this.path) {
myPaint = Paint();
myPaint.color = Color(0xFFC4C4C4);
myPaint.strokeWidth = 3.0;
}
@override
void paint(Canvas canvas, Size size) => canvas.drawPath(path, myPaint);
@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}++/pre>
Now in our widget tree, we need to specify the size of our painter, using the SizedBox widget. It allows us to define the height and width of our painter.
To allow other widgets to constrain its dimension, we just need to wrap our SizedBox with a FittedBow. Like this:
++pre>FittedBox(
child: SizedBox(
height: HEIGHT,
width: WIDTH,
child: CustomPaint(
painter:
MyPainter(PathMorph.generatePath(data)))),
)++/pre>
To finish this tutorial we just need to control our animation. In the widget tree, create a Button, check the controller.status and do a reverse() or a forward().
++pre>onPressed: () {
if (controller.status == AnimationStatus.completed) {
controller.reverse();
}
if (controller.status == AnimationStatus.dismissed) {
controller.forward();
}
},++/pre>
Congratulation now you know how to do path morphing animation with flutter! Now you can experiment with this library and create the animation you want. In the repo, you will find all the resources to do this animation:
Participating to the flutter clock challenge was the perfect opportunity to learn flutter and to discover all the possibilities it offers. As a mobile developer, I'm excited to start new projects with this technology.
Thank you Antoine Françon for participating in this challenge with me !