Bezier Animations — UI elements to Flutter Widgets (Part 1)

Kamalesh Basu
TheBrand.app
Published in
8 min readJan 29, 2020

UX Softwares such as Sketch, Adobe Xd and Figma have empowered designers to imagine, experiment and provide great finesse to User Interfaces and experiences. Just take a look at design websites such as Dribbble or Behance, and you will be awestruck at the quality of designs being published.

One common feature of UI/UX elements today are animations — not flashing neon light animations — but subtle, tasteful, more organic animations. Bezier curves are a great way of creating smooth curves and provide a simple, elegant yet powerful way of animating UI elements in an App. This two-part article will be an attempt to simplify the process of converting a designer’s UI elements into Flutter Widgets for drawing Bezier Curves and animating them.

1. The Design Process

A simple Bezier curved based animation in the orange page header

Almost all UI/UX software has a Path or Pen tool for creating smooth shapes or paths. Paths are composed of anchors or points which are joined by segments or arcs. In most cases, these arcs are defined by a quadratic or cubic equation. These arcs are collectively called Bezier curves.

To create an animation of such paths or shapes, a designer needs to define a new path/shape (usually containing the same number of points and segments) in a new Artboard or View, and make use of the auto-animate feature of the UX software to create the animation.

In this article, we will focus on the Bezier curve animation (the orange page header) shown in the image above and take a look at how to export the designer’s path animation into a Flutter widget. The above UI was created using Adobe XD and the file is available in the repository for this article.

For the article, we will assume the number of anchors/points and segments for the two paths we are animating between is the same. If the two paths have a different number of points, you can simply add additional points along the path with fewer points and it will work just the same.

2. Path to Bezier curve

Before we can programmatically draw the shape or path created by the designer, we need to translate the Path into Points and Segments (Bezier curve). Each segment in a Bezier curve is defined by the starting point, the endpoint, and zero or more control points. The control points are responsible for the curvature of the curve.

If there are no control points, it is a straight line. If there is only one control point, then it is a quadratic Bezier curve and if it has two control points, it is a cubic Bezier curve. We seldom need to consider segments that require more than two control points.

Whenever we are trying to export a path or a shape from a Design software and draw it using Bezier curve functions in our app, our strategy will be:

  1. Export the path/shape from UX software as an SVG
  2. Convert the SVG path (“d” string) to Bezier curves
  3. Programatically draw the Bezier curves (in Flutter for our case)

3. Export Path to SVG

Some plugins exist which allows Paths to be exported directly into Bezier curves but in case you don’t have access to such a plugin, our strategy will always work.

We start by exporting our path in the UX Software to an SVG. Any scalable vector graphic (SVG), can be represented by a String (often called a “d” String) which is a series of instructions on how to draw the path that represents the SVG. For example, a simple SVG string can be:

M0,0L0,100Q100,120,100,100L100,0L0,0

This reads (For all possible instructions you can find in an SVG path string, check out this link.):

1. Move to point (0,0)
2. Draw straight Line to (0,100)
3. Draw Quadratic Curve to (100,100) with control point (100,120)
4. Draw straight Line to (100,0)
5. Draw straight Line to (0,0)

For the article, I will use Adobe XD (it’s free to use) but similar export capabilities exist for all UX software.

If you open the Adobe XD file provided in the repository for this article, you will see two Artboards with each Artboard containing the two states of the Path we are going to animate.

The two states of the same path in the Artboard

Select the path in Artboard 1, and then select the following options from the toolbar:

File > Export > Selected

Save the file as an svg and give it a name such as, curve.svg . Follow the same steps with the path in Artboard 2, and save the path as curve_final.svg . You can watch the steps in the following video:

4. SVG path to Bezier instructions

Once you have created the SVG files, open up the first file curve.svg in your browser, right-click on the image and select Inspect Element (for non-Chrome browsers the option might be different). You will see something as follows:

Screenshot of the SVG image in Chrome Element inspector

You can immediately get hold of the path string from pathattribute:

M-27.4,335.344S-3.935,356.71,44.157,356.71s70.664-62.965,131.742-55.941,79.371,26.263,135.113,26.263S386.6,247.758,386.6,247.758V-6.441h-414Z

There is one other important information we need to take note of and that is the transformattribute:

translate(27.396 6.441)

I personally prefer to convert the SVG path to cubic-bezier segments and there are plenty of ways you can do that. Some of the libraries that can help are Raphael or SnapSVG. Both libraries have functions that will allow you to convert an SVG path String into an array of Bezier instructions. If you are unfamiliar with either of those, you can make use of the tool I have put together here using SnapSVG.

A tool to convert SVG Path string into Cubic Bezier Segments

Enter the path String and the translation coordinates in the appropriate fields. To get the Path dimensions, go back to the Adobe XD file and select the path. And you will be able to get the Width and Height of the Path. Once all the values are in, hit Convert! You can follow all the steps in this video:

It generates the Cubic Bezier instructions:

M, -0.003999999999997783, 341.78499999999997C, -0.003999999999997783, 341.78499999999997, 23.461000000000002, 363.15099999999995, 71.553, 363.15099999999995C, 119.645, 363.15099999999995, 142.21699999999998, 300.186, 203.29500000000002, 307.21C, 264.373, 314.234, 282.666, 333.47299999999996, 338.408, 333.47299999999996C, 394.15000000000003, 333.47299999999996, 413.99600000000004, 254.199, 413.99600000000004, 254.199C, 413.99600000000004, 254.199, 413.99600000000004, 0, 413.99600000000004, 0C, 413.99600000000004, 0, -0.003999999999976467, 0, -0.003999999999976467, 0C, -0.003999999999976467, 0, -0.003999999999997783, 341.78499999999997, -0.003999999999997783, 341.78499999999997

It also generates the necessary Dart code for our Flutter App but we will come back to it later. (I could have done a better job at formatting the Dart code, but I was trying to get it out ASAP and I may go back and improve on it.)

5. Setting up the Flutter App

Start a new Flutter app:

flutter create --org com.itchylabs --description "Bezier Curve Animations" bezier_animation

(Tip: You can run the entire code without creating a Flutter app by going to https://dartpad.dartlang.org/flutter and by running the code there.)

Change the main.dart file as follows:

If you run the app on an emulator, you should see something like this:

We have created an orange container and now we are going to Clip this container to get our desired path.

6. Clipping the Container

You can clip any widget in Flutter, by wrapping it with a ClipPath widget and then by defining the clipper property. The clipper property is an object extended from the CustomClipper and will define the Path. In our case, we will Clip the container we created but you can use this technique to clip images, text fields or any other widget in Flutter.

We modify our code by wrapping our container as follows:

ClipPath(
clipper: BezierClipper(),
child:
Container(
color : Color.fromRGBO(255, 91, 53, 1),
height: height,
),
),

We will now define our BezierClipper class by extending a CustomClipper as follows:

class BezierClipper extends CustomClipper<Path>{
@override
Path
getClip(Size size) => null;
@override
bool
shouldReclip(CustomClipper<Path> oldClipper) => true;
}

6.1 A Test Clipper — A quadratic clip

We are exclusively going to work with getClip method and whatever path we return from that method will be used to clip the container. Change the getClip method as follows:

@override
Path
getClip(Size size){
Path path = new Path();
path.lineTo(0, size.height*0.85); //vertical line
path.quadraticBezierTo(size.width/2, size.height, size.width, size.height*0.85); //quadratic curve
path.lineTo(size.width, 0); //vertical line
return path;
}

This will give us the following rounded path:

This is what we are doing:

  1. Draw a vertical line from the top-left corner of the Container to 85% way down the height of the container.
  2. Draw a quadratic curve to the right edge of the container with a control point which is in the “center bottom” of the container.
  3. Draw a vertical line to the top-right corner of the Container.

Since all our measures are based on thesize variable of the getClipmethod, all the dimensions of the path are relative to the container (or widget) it is clipping.

6.2 A Test Clipper — A cubic clip

Update the getClip method as follows:

@override
Path
getClip(Size size){
Path path = new Path();
path.lineTo(0, size.height*0.85); //vertical line
path.cubicTo(size.width/3, size.height, 2*size.width/3, size.height*0.7, size.width, size.height*0.85); //cubic curve
path.lineTo(size.width, 0); //vertical line
return path;
}

You have created a nice wavy bottom border for the container:

6.3 Our Path Clipper

Head over to the online tool and copy the Flutter Clipper the tool generates.

Auto-generated Flutter Clipper

Use that as the getClip method and you will get our path:

6.4 Our 2nd Path Clipper and Toggling Between the two paths

  1. Now follow the same steps for the 2nd path with the file curve_final.svg and grab the getClip method but save it in your BezierClipper class as _getFinalClip method.
  2. Rename the getClip method as _getInitialClip .
  3. Define a new intclass variable and a constructor for our BezierClipper class as follows:
final int state;
BezierClipper(this.state);

4. Define a new getClip method as follows:

@override
Path
getClip(Size size) => state == 1 ? _getInitialClip(size) : _getFinalClip(size);

5. In the _AnimationPageState class add a new variable and method:

int state = 1;_toggle(){
setState(() {
state = state == 1? 2 : 1;
});
}

6. In the Stack widget of the buildmethod of _AnimationPageState class add a new child Container with a button which will call the _toggle method:

Container(
padding: EdgeInsets.only(top: height * 1.2),
child : Center(
child: RaisedButton(
child: Text("Toggle"),
onPressed: _toggle,
),
)
),

7. Update our clipper property from BezierClipper() to BezierClipper(state) .

Now if you press the button, the path will toggle (not animate) between the two states.

And here is the final main.dart file:

7. Wrapping Up

In this article, we explored how we can export paths/shapes from UX design software into our Flutter App while maintaining the design integrity and ensuring we get an accurate representation of the paths created by the designer. In Part 2, we will explore how we can animate these paths smoothly. Thanks for reading!

--

--