Write a simple, custom circular progress bar today with a small arrow pointing to the inner circle. The progress bar has been uploaded to the public network. Use circle_progress: ^0.0.1

void main() => runApp(MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold(appBar (title: Text("Flutter "),), body: TestStateful() // built-in case)));Copy the code

1. Preparation

1.1: Define the description object class Progress

The attributes that need to be changed are separated into a description class to facilitate parameter passing

/// Information description class [value] is the progress, between 0 and 1, progress bar color [color], /// Unfinished color [backgroundColor], radius of the circle [radius], line width [strokeWidth] /// number of small dots [dotCount] style [style] Display the text [completeText] class Progress { double value; Color color; Color backgroundColor; double radius; double strokeWidth; int dotCount; TextStyle style; String completeText; Progress({this.value, this.color, this.backgroundColor, this.radius, this.strokeWidth, this.completeText="OK", this.style, this.dotCount = 40 }); }Copy the code

1.2: Custom component class CircleProgressWidget

That’s our component

class CircleProgressWidget extends StatefulWidget { final Progress progress; CircleProgressWidget({Key key, this.progress}) : super(key: key); @override _CircleProgressWidgetState createState() => _CircleProgressWidgetState(); } class _CircleProgressWidgetState extends State<CircleProgressWidget> { @override Widget build(BuildContext context) { return Container(); }}Copy the code

1.3: Customizing ProgressPainter

This is where our drawing logic goes

class ProgressPainter extends CustomPainter { Progress _progress; Paint _paint; Paint _arrowPaint; // Arrow's brush Path _arrowPath; // Arrow path double _radius; // Radius ProgressPainter(this._progress,) {_arrowPath=Path(); _arrowPaint=Paint(); _paint = Paint(); _radius = _progress.radius - _progress.strokeWidth / 2; } @override void paint(Canvas canvas, Size size) { Rect rect = Offset.zero & size; canvas.clipRect(rect); } @override bool shouldRepaint(CustomPainter oldDelegate) {return true; }}Copy the code

2. Draw

2.1: Draw a progress bar

If you just take a given radius, you’ll find something like this. The reason is very simple, because the radius of Canvas circle is the inner circle plus half of the line thickness.

So we need to correct the radius by shifting it by half and then reducing it by half.

_radius = _progress.radius - _progress.strokeWidth / 2;

 @override
  void paint(Canvas canvas, Size size) {
    canvas.translate(_progress.strokeWidth / 2, _progress.strokeWidth / 2);
Copy the code

Draw circles for the background and drawArc method for progress. Note that the Flutter uses radians!! .

drawProgress(Canvas canvas) { canvas.save(); _paint / / background.. style = PaintingStyle.stroke .. color = _progress.backgroundColor .. strokeWidth = _progress.strokeWidth; canvas.drawCircle(Offset(_radius, _radius), _radius, _paint); _paint / / schedule.. color = _progress.color .. StrokeWidth = _progress.strokeWidth * 1.2.. strokeCap = StrokeCap.round; double sweepAngle = _progress.value * 360; Canvas. DrawArc (rect. fromLTRB(0, 0, _radius * 2, _radius * 2), -90/180 * PI, sweepAngle / 180 * PI, false, _paint); canvas.restore(); }Copy the code

2.2: Draw arrows

The arrows are actually pretty easy to draw, so use a relativeLineTo with a lineTo to make it easier.

drawArrow(Canvas canvas) { canvas.save(); canvas.translate(_radius, _radius); canvas.rotate((180 + _progress.value * 360) / 180 * pi); var half = _radius / 2; var eg = _radius / 50; // Unit length _arrowpath. moveTo(0, -half -eg * 2); //1 _arrowPath.relativeLineTo(eg * 2, eg * 6); //2 _arrowPath.lineTo(0, -half + eg * 2); //3 _arrowPath.lineTo(0, -half - eg * 2); //1 _arrowPath.relativeLineTo(-eg * 2, eg * 6); _arrowPath.lineTo(0, -half + eg * 2); _arrowPath.lineTo(0, -half - eg * 2); canvas.drawPath(_arrowPath, _arrowPaint); canvas.restore(); }Copy the code

2.3: plot points

When drawing points, pay attention to the color control, judge whether the progress bar has arrived, and then change the color

void drawDot(Canvas canvas) { canvas.save(); int num = _progress.dotCount; canvas.translate(_radius, _radius); for (double i = 0; i < num; i++) { canvas.save(); double deg = 360 / num * i; canvas.rotate(deg / 180 * pi); _paint .. strokeWidth = _progress.strokeWidth / 2 .. color = _progress.backgroundColor .. strokeCap = StrokeCap.round; if (i * (360 / num) <= _progress.value * 360) { _paint.. color = _progress.color; } canvas.drawLine( Offset(0, _radius * 3 / 4), Offset(0, _radius * 4 / 5), _paint); canvas.restore(); } canvas.restore(); }Copy the code

2.4: assemble

You might ask what about Text? It’s a hassle to draw Text in a Canvas, but it’s easier to use a Stack

class _CircleProgressWidgetState extends State<CircleProgressWidget> { @override Widget build(BuildContext context) { var progress = Container( width: widget.progress.radius * 2, height: widget.progress.radius * 2, child: CustomPaint( painter: ProgressPainter(widget.progress), ), ); String txt = "${(100 * widget.progress.value).toStringAsFixed(1)} %"; Var text = text (widget.progress.value == 1.0? widget.progress.completeText : txt, style: widget.progress.style ?? TextStyle(fontSize: widget.progress.radius / 6), ); return Stack( alignment: Alignment.center, children: <Widget>[progress,text], ); }}Copy the code

OK, that will do.


Use 3.

3.1: Simple use
CircleProgressWidget(progress: progress (backgroundColor: colors. grey, value: 0.8, radius: 100, completeText: "Finish ", color: color (0xff46bcf6), strokeWidth: 4)Copy the code

3.2: Component code overview

Take it. Thank you. No.

import 'dart:math'; import 'package:flutter/material.dart'; class CircleProgressWidget extends StatefulWidget { final Progress progress; CircleProgressWidget({Key key, this.progress}) : super(key: key); @override _CircleProgressWidgetState createState() => _CircleProgressWidgetState(); } /// Information description class [value] is the progress, between 0 and 1, progress bar color [color], /// Unfinished color [backgroundColor], radius of the circle [radius], line width [strokeWidth] /// number of small dots [dotCount] style [style] Display the text [completeText] class Progress { double value; Color color; Color backgroundColor; double radius; double strokeWidth; int dotCount; TextStyle style; String completeText; Progress( {this.value, this.color, this.backgroundColor, this.radius, this.strokeWidth, this.completeText = "OK", this.style, this.dotCount = 40}); } class _CircleProgressWidgetState extends State<CircleProgressWidget> { @override Widget build(BuildContext context) { var progress = Container( width: widget.progress.radius * 2, height: widget.progress.radius * 2, child: CustomPaint( painter: ProgressPainter(widget.progress), ), ); String txt = "${(100 * widget.progress.value).toStringAsFixed(1)} %"; Var text = text (widget.progress.value == 1.0? widget.progress.completeText : txt, style: widget.progress.style ?? TextStyle(fontSize: widget.progress.radius / 6), ); return Stack( alignment: Alignment.center, children: <Widget>[progress,text], ); } } class ProgressPainter extends CustomPainter { Progress _progress; Paint _paint; Paint _arrowPaint; Path _arrowPath; double _radius; ProgressPainter( this._progress, ) { _arrowPath = Path(); _arrowPaint = Paint(); _paint = Paint(); _radius = _progress.radius - _progress.strokeWidth / 2; } @override void paint(Canvas canvas, Size size) { Rect rect = Offset.zero & size; canvas.clipRect(rect); // Clipping area Canvas.translate (_progress.strokeWidth / 2, _progress.strokewidth / 2); drawProgress(canvas); drawArrow(canvas); drawDot(canvas); } @override bool shouldRepaint(CustomPainter oldDelegate) { return true; } drawProgress(Canvas canvas) { canvas.save(); _paint / / background.. style = PaintingStyle.stroke .. color = _progress.backgroundColor .. strokeWidth = _progress.strokeWidth; canvas.drawCircle(Offset(_radius, _radius), _radius, _paint); _paint / / schedule.. color = _progress.color .. StrokeWidth = _progress.strokeWidth * 1.2.. strokeCap = StrokeCap.round; double sweepAngle = _progress.value * 360; Print (sweepAngle); canvas.drawArc(Rect.fromLTRB(0, 0, _radius * 2, _radius * 2), -90 / 180 * pi, sweepAngle / 180 * pi, false, _paint); canvas.restore(); } drawArrow(Canvas canvas) { canvas.save(); canvas.translate(_radius, _radius); Rotate ((180 + _progress.value * 360) / 180 * PI); rotate((180 + _progress.value * 360) / 180 * PI); Var half = _radius / 2; Var eg = _radius / 50; // Unit length _arrowpath. moveTo(0, -half -eg * 2); _arrowPath.relativeLineTo(eg * 2, eg * 6); _arrowPath.lineTo(0, -half + eg * 2); _arrowPath.lineTo(0, -half - eg * 2); _arrowPath.relativeLineTo(-eg * 2, eg * 6); _arrowPath.lineTo(0, -half + eg * 2); _arrowPath.lineTo(0, -half - eg * 2); canvas.drawPath(_arrowPath, _arrowPaint); canvas.restore(); } void drawDot(Canvas canvas) { canvas.save(); int num = _progress.dotCount; canvas.translate(_radius, _radius); for (double i = 0; i < num; i++) { canvas.save(); double deg = 360 / num * i; canvas.rotate(deg / 180 * pi); _paint .. strokeWidth = _progress.strokeWidth / 2 .. color = _progress.backgroundColor .. strokeCap = StrokeCap.round; if (i * (360 / num) <= _progress.value * 360) { _paint.. color = _progress.color; } canvas.drawLine( Offset(0, _radius * 3 / 4), Offset(0, _radius * 4 / 5), _paint); canvas.restore(); } canvas.restore(); }}Copy the code

conclusion

This is the end of this article. If you want to taste Flutter quickly, Flutter For Seven days is a must-have. If you want to explore it, follow in my footsteps and complete a Flutter tour. In addition, I have a Flutter wechat communication group. You are welcome to join and discuss Flutter issues together. My wechat account is ZDL1994328.