How to create a simple progress gauge using Flutter CustomPaint: Part 2

In part 1 of this article, we discussed how to create a simple progress gauge using Flutter. Now, let's create a simple animation on that progress gauge. Before we get started, make sure you have read the part 1.

The first thing we going to do to create the animation is to change the CustomProgressGauge class from extending the StatelessWidget into StatefulWidget .

import 'package:my_simple_gauge/custom_progress_painter.dart';
import 'package:flutter/material.dart';

class CustomProgressGauge extends StatefulWidget {
  const CustomProgressGauge({super.key, this.progress = 0});
  final num progress;

  @override
  State<StatefulWidget> createState() => CustomProgressGaugeState();
}

class CustomProgressGaugeState extends State<CustomProgressGauge>
    with SingleTickerProviderStateMixin {
  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: CustomProgressPainter(widget.progress),
      child: SizedBox(
        height: 450,
        child: Center(
          child: Text("${widget.progress}%",
              style: const TextStyle(
                  color: Colors.black,
                  fontSize: 36,
                  fontWeight: FontWeight.bold)),
        ),
      ),
    );
  }
}

As you can see, now the class is extending the StatefulWidget . In order to provide the ticker provider to the vsync parameter of the animation controller, we also use SingleTickerProviderStateMixin with the CustomProgressGaugeState class.

Let's create the animation controller in the build function:

class CustomProgressGaugeState extends State<CustomProgressGauge>
    with SingleTickerProviderStateMixin {
    
  late AnimationController _controller;
  late Animation<num> _animation;
  
  @override
  void initState() {
    super.initState();

    _controller =
        AnimationController(duration: const Duration(seconds: 2), vsync: this);
    _animation = Tween<num>(begin: 0, end: widget.progress).animate(_controller);
    _controller.forward();
  }

  @override
  void dispose() {
    super.dispose();
    _controller.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
  
  ......

In the code above we create two variables _controller and _animation. The _controller variable is an instance of AnimationController. In Flutter, an animation controller is a class that manages an animation over a specific duration. It allows you to control the progress of the animation, define its duration, and handle various animation-related tasks.

The _animation variable in the code above is used to define the animation's begin and end state. In this case, it represents our progress percentage. To start the animation when the widget is initiated, we call the _controller.forward() method inside the initState.

Implementing the AnimatedBuilder widget.

Now, after we initialized that stuff, let's implement the AnimatedBuilder widget. To do that, we can wrap the CustomPaint widget using the AnimatedBuilder.

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
        animation: _animation,
        builder: (BuildContext context, Widget? child) {
          return CustomPaint(
            painter: CustomProgressPainter(_animation.value),
            child: SizedBox(
              height: 450,
              child: Center(
                child: Text("${_animation.value.toInt()}%",
                    style: const TextStyle(
                        color: Colors.black,
                        fontSize: 36,
                        fontWeight: FontWeight.bold)),
              ),
            ),
          );
        });
  }

As you can see, now we wrapped the CustomPaint with the AnimatedBuilder. The AnimatedBuilder requires a named parameter, animation. We can just pass the _animation variable we just created.

Instead of directly passing the value of the progress variable, we now use _animation.value as the parameter of CustomProgressPainter and the value will change while the animation running.

We also updated the text inside the child parameter of the CustomPaint to:

${_animation.value.toInt()}%

So, the text will change while the animation is running. This is how the end result looks:

Conclusion

In this article, we modify our CustomProgressGauge to extend the StatefulWidget instead of StatelessWidget. We also, define our animation and implement the animation using the AnimatedBuilder widget. Hopefully, this article will be beneficial for you.

In the repository that I share in the previous part, there is a branch called animated if you want to see the full code.

Thanks!