Better Programming

Advice for programmers.

Follow publication

Build a Custom Paint Flutter App Widget

Gajendra Pandeya
Better Programming
Published in
5 min readApr 30, 2022

--

In this article, we will be covering both explicit animation as well as how to work with CustomPaint widget in a flutter.

You can check out the code sample at from my GitHub Repository.

Custom Paint : A widget that provides a canvas on which to draw during the paint phase.

Simply add CustomPaint widget to your widget tree and provide painter to it which is a subclass of CustomPainter abstract class.

Since CustomPainter is an abstract class, it forces us to implement two important methods i.e. paint() and shouldRepaint(). The size spcifies the size of the drawn widget. Let’s get our hands dirty:

The paint() method provides us with two important parameters:

  1. Canvas: Canvas is the area on the screen where we draw our widget. It has various method to draw custom widget. Some of them are: canvas.drawLine(), canvas.drawCircle(), canvas.drawArc(), canvas.drawOval() etc.
  2. Size: How big should be the drawing. By default, it takes the size of the wrapper widget i.e the widget wrapping our CustomPaint widget. Also, we can provide size parameters inside our CustomPaint widget. Since we want to draw a circle so we passed the same value for both the width and height.

The shouldRepaint() method is called whenever CustomPainter needs to be rebuilt. This would get clear when we implement our project.

Now that we understand CustomPainter class, let’s draw something on the canvas. Our goal is to draw a circle with some stroke width.

Let’s see how we do this:

Since we wanna draw a circle, so we used the prebuilt canvas.drawCircle() the method that expects three parameters i.e. center of the circle, radius of the circle and a paint object. We calculated the circle center and radius from size the parameter. The Paint() class provides us various properties like the color of the paint, storkeWidth, style of the paint i.e whether it should be stroked or filled. The current implementation will result in this:

Our next goal is to draw an arc over the same circle that would animate around the stroke. In order to achieve this:

Since we wanna draw an arc, so we used canvas.drawArc() method that expects five parameters i.e. Rect object , startAngle in radian, sweepAngle in radian, useCenter boolean and paint object.

Also, we want to draw arc around the circle, so we used Rect.fromCircle() the method as the first parameter. The second parameter is the start angle which we have applied to -pi/2 as we want to start the angle from top of the circle.

The sweep angle specifies where should the arc end and we have applied 40% of the total angle i.e our arc will draw from -pi/2 to 40% of the total 360 radian.

If useCenter boolean is true, the arc is closed back to the center, forming a circle sector. Otherwise, the arc is not closed, forming a circle segment. And the final argument is our paint object which we are already familiar with.

The current implementation with useCenter set to true will result in this:

But we don’t want that circle sector, so setting it false would result in this:

You can tweak the startAngle, endAngle to see the various effects.

Now we are almost close to what we want to achieve. The only part remaining is to animate the progress. In order to do this, we have to use explicit animation i.e. we have to create AnimationController and pass its value down the RingPainter class:

The various things we have done now are:

  1. Converted our RingPainter StatelessWidget to Stateful widget in order to rebuild the UI whenever the value of animation changes.
  2. Created AnimationController _controller by mixing our _RingPainterState class with SingleTickerProviderStateMixin and initilized it within initState() method.
  3. Started the animation with _controller.forward()
  4. Added listener to the controller and a setState((){}) is called so that our entire UI rebuilds whenever value of our _controller changes.
  5. Now our MyPainter class expects three required arguments: progress, defaultColor of the circle stroke and fill color. It is done so in order to pass the various values from outside of this class and to make this component reusable. Note that shouldRepaint() method is updated such that our Circle should only be drawn whenever the progress value differs from the previous value.
  6. Finally passed these three arguments to the MyPainter class with progress value being the current value of the animation.

With the current implementation, we are successful to achieve this:

Woah, we are almost to the end! The only thing remaining is to add an Icon at the middle of the circle and change its color depending upon whether the animation is completed or not.

In order to achieve this update your build method like this:

The following implementation would result in this:

So now successfully achieved what we desired.

I hope you have learned something about the CustomPaint widget. Now you can explore on your own to achieve various other effects.

Thanks for reading.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Write a response