May 19, 2020

Circular progress indicator in the flutter

By Mohit Agrawal

Basics of CustomPainter and build Circular progress indicator

flutter default

Overview

There are thousands of libraries available on the internet which can easily provide you the circular progress indicator without writing a single line of code. But if you are a beginner and want to learn the basics then you should try to build your own components first. In this article, we are going to cover the CustomPainter widget.

Introduction

CustomPainter is something that lets you draw custom graphics on the canvas. Flutter makes it very easy to draw any custom design and this is one of them. I will show you how to inherit the properties of CustomPainter class and create our own class CircularProgressIndicator.

Implementation

Create a new flutter project in the android studio and do the initial setup. Check this post How to setup flutter with the android studio? if required. Now open the main.dart and replace the default code with this.

import 'package:flutter/material.dart';
import 'package:fluttercustompaint/circularProgressIndicator.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Material(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: <Widget>[
              CircularProgressView(50),
            ],
          )
      ),
    );
  }
}

I have just replaced the default boilerplate UI with a custom widget CircluarProgressiew(50). This will display a circular progress indicator with 50% of progress. Let’s design this now. Create a new dart file and name it circularProgressIndicator. Now create a stateful Widget and name it CircularProgressView.

import 'package:flutter/material.dart';
import 'package:flutter/painting.dart';
import 'package:flutter/widgets.dart';

class CircularProgressView extends StatefulWidget {

  @override
  _CircularProgressViewState createState() => _CircularProgressViewState();
}

class _CircularProgressViewState extends State<CircularProgressView> {
  @override
  Widget build(BuildContext context) {
    return Center(
      //Will update with custom painter class.
    );
  }
}

Create a class in the same file and name it CircleProgressIndicator.

class CircleProgressIndicator extends CustomPainter {

  double progress = 0;

  CircleProgressIndicator(this.progress);

  @override
  void paint(Canvas canvas, Size size) {
    // TODO: implement paint
    Paint outerCircle = Paint()
        ..strokeWidth = 15
        ..color = Colors.grey
        ..style = PaintingStyle.stroke;

    Paint completeArc = Paint()
        ..strokeWidth = 15
        ..color = Colors.red
        ..style = PaintingStyle.stroke
        ..strokeCap = StrokeCap.round;
    
    Offset offset = Offset(size.width/2, size.height/2);
    double radius = min(size.width/2, size.height/2);

    double angle = 2*pi*(progress/100);

    canvas.drawCircle(offset, radius, outerCircle);
    canvas.drawArc(Rect.fromCircle(center: offset, radius: radius), 0, angle, false, completeArc);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    // TODO: implement shouldRepaint
    return false;
  }

}

Here we are inheriting the class CircleProgressIndicator from CustomPainter class. It provides two mandatory methods, the first one is paint(Canvas canvas, Size size) and the second one is shouldRepaint(CustomPainter oldDelegate). They help us to draw the custom drawings on the canvas. Now update the stateful widget with this.

class _CircularProgressViewState extends State<CircularProgressView> {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: CustomPaint(
        foregroundPainter: CircleProgressIndicator(widget.progress),
        child: Container(
          height: 200, width: 200,
          child: Center(
            child: Text( widget.progress.toInt().toString() + '%',
              style: TextStyle(
                  color: Colors.grey,
                  fontSize: 29
              ),
            ),
          ),
        ),
      ),
    );
  }
}

Run your code now you should be getting something like this.

circular progress
Flutter circular progress indicator

Complete source code

main.dart

import 'package:flutter/material.dart';
import 'package:fluttercustompaint/circleIndicator.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Material(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: <Widget>[
              CircularProgressView(50),
            ],
          )
      ),
    );
  }
}

circularProgressIndicator.dart



import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter/painting.dart';
import 'package:flutter/widgets.dart';

class CircularProgressView extends StatefulWidget {

  double progress = 0;
  CircularProgressView(this.progress);

  @override
  _CircularProgressViewState createState() => _CircularProgressViewState();
}

class _CircularProgressViewState extends State<CircularProgressView> {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: CustomPaint(
        foregroundPainter: CircleProgressIndicator(widget.progress),
        child: Container(
          height: 200, width: 200,
          child: Center(
            child: Text( widget.progress.toInt().toString() + '%',
              style: TextStyle(
                  color: Colors.grey,
                  fontSize: 29
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class CircleProgressIndicator extends CustomPainter {

  double progress = 0;

  CircleProgressIndicator(this.progress);

  @override
  void paint(Canvas canvas, Size size) {
    // TODO: implement paint
    Paint outerCircle = Paint()
        ..strokeWidth = 15
        ..color = Colors.grey
        ..style = PaintingStyle.stroke;

    Paint completeArc = Paint()
        ..strokeWidth = 15
        ..color = Colors.red
        ..style = PaintingStyle.stroke
        ..strokeCap = StrokeCap.round;
    
    Offset offset = Offset(size.width/2, size.height/2);
    double radius = min(size.width/2, size.height/2);

    double angle = 2*pi*(progress/100);

    canvas.drawCircle(offset, radius, outerCircle);
    canvas.drawArc(Rect.fromCircle(center: offset, radius: radius), 0, angle, false, completeArc);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    // TODO: implement shouldRepaint
    return false;
  }

}

Help me to grow our YouTube Channel: More tutorials like this

I hope this blog post is useful for you, do let me know your opinion in the comment section below.
I will be happy to see your comments down below 👏.
Thanks for reading!!!