原文是谁写的我也不知道,但是搜了这么多,没一个能用的,大多数的都没实时刷新,在自己的debug和学习下,进行了改进,这次可以直接用了xdm。
学习的笔记也记录在这里了
绘制需要的要素
1.纸: Canvas 画布对象
2.笔: Paint 画笔对象
3.形: Path 路径对象
4.色: Color 颜色对象
使用StreamController来进行实时的局部刷新
效果图:
程序主入口
void main() => runApp(MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text("Flutter 之旅"),
),
body: CircleProgressWidget(
progress: Progress(
backgroundColor: Colors.grey,
value: 0.0,
radius: 100,
completeText: "完成",
color: Color(0xff46bcf6),
strokeWidth: 4)
),
)
));
1.定义描述对象类Progress
1.1:定义描述对象类Progress
将需要变化的属性抽离出一个描述类
///信息描述类 [value]为进度,在0~1之间,进度条颜色[color],
///未完成的颜色[backgroundColor],圆的半径[radius],线宽[strokeWidth]
///小点的个数[dotCount] 样式[style] 完成后的显示文字[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
});
}
1.2自定义ProgressPainter
绘制逻辑
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) {
//这个是我们定义painter时必须实现的方法,其中canvas就是提供出我们绘制的核心,
//size是告诉我们画板的大小(通过CustomPaint的size或者child确定)
Rect rect = Offset.zero & size;
canvas.clipRect(rect); //裁剪区域
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
2.绘制
2.1:绘制进度条
如果直接用给定的半径,你会发现是这样的。原因很简单,因为Canvas画圆半径是内圆加一半线粗。
于是我们需要校正一下半径:通过平移一半线粗再缩小一半线粗的半径。
_radius = _progress.radius - _progress.strokeWidth / 2;
@override
void paint(Canvas canvas, Size size) {
canvas.translate(_progress.strokeWidth / 2, _progress.strokeWidth / 2);
背景直接画圆,进度使用drawArc方法,要注意的是Flutter中使用的是弧度!!。
save操作会保存此前的所有绘制内容和canvas状态。在调用该函数之后的绘制操作和变换操作,会重新记录。当你调用restore()之后,会把save到restore之间所进行的操作与之前的内容进行合并,之后再使用局部更新的StreamController来更新状态
drawProgress(Canvas canvas) {
canvas.save();
_paint//背景
..style = PaintingStyle.stroke
..color = _progress.backgroundColor
..strokeWidth = _progress.strokeWidth;
canvas.drawCircle(Offset(_radius, _radius), _radius, _paint);
_paint//进度
..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();
}
2.2:绘制箭头
其实箭头还是蛮好画的,注意relativeLineTo和lineTo结合使用,可能会更方便。
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; //单位长
_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();
}
2.3:绘制点
绘制点的时候要注意颜色的把控,判断进度条是否到达,然后更改颜色
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();
}
3.组装使用
使用_streamController来更新进度条状态
class CircleProgressWidget extends StatefulWidget {
final Progress progress;
CircleProgressWidget({Key key, this.progress}) : super(key: key);
@override
_CircleProgressWidgetState createState() => _CircleProgressWidgetState(this.progress);
}
class _CircleProgressWidgetState extends State<CircleProgressWidget> {
Progress progress;
_CircleProgressWidgetState(this.progress);
///计时器
Timer _timer;
///倒计时6秒
double totalTimeNumber = 10000;
///当前的时间
double currentTimeNumber = 10000;
StreamController<double> _streamController = StreamController();
@override
void initState(){
startTimer();
}
@override
void dispose(){
_streamController.close();
_timer.cancel();
}
void startTimer() {
///间隔100毫秒执行时间
_timer = Timer.periodic(Duration(milliseconds: 100), (timer) {
///间隔100毫秒执行一次 每次减100
currentTimeNumber -= 100;
///如果计完成取消定时
if (currentTimeNumber <= 0) {
_timer.cancel();
currentTimeNumber = 0;
}
///流数据更新
progress.value = (totalTimeNumber-currentTimeNumber)/totalTimeNumber;
_streamController.add((totalTimeNumber-currentTimeNumber)/totalTimeNumber);
});
}
@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 Scaffold(
body: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height, //容器填充满整个屏幕
child: StreamBuilder<double>(
stream: _streamController.stream,
initialData: 0,
builder: (BuildContext context, AsyncSnapshot<double> snapshot) {
return Stack(
alignment: Alignment.center,
children: [
Text(
widget.progress.value == 1.0 ? widget.progress.completeText : "${(100 * widget.progress.value).toStringAsFixed(1)} %",
style: widget.progress.style ??
TextStyle(fontSize: widget.progress.radius / 6),
),
Container(
width: widget.progress.radius * 2,
height: widget.progress.radius * 2,
child: CustomPaint(
painter: ProgressPainter(widget.progress),
),
),
],
);
},
),
)
);
}
}
4.全部代码
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
///信息描述类 [value]为进度,在0~1之间,进度条颜色[color],
///未完成的颜色[backgroundColor],圆的半径[radius],线宽[strokeWidth]
///小点的个数[dotCount] 样式[style] 完成后的显示文字[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 CircleProgressWidget extends StatefulWidget {
final Progress progress;
CircleProgressWidget({Key key, this.progress}) : super(key: key);
@override
_CircleProgressWidgetState createState() => _CircleProgressWidgetState(this.progress);
}
class _CircleProgressWidgetState extends State<CircleProgressWidget> {
Progress progress;
_CircleProgressWidgetState(this.progress);
///计时器
Timer _timer;
///倒计时6秒
double totalTimeNumber = 10000;
///当前的时间
double currentTimeNumber = 10000;
StreamController<double> _streamController = StreamController();
@override
void initState(){
startTimer();
}
@override
void dispose(){
_streamController.close();
_timer.cancel();
}
void startTimer() {
///间隔100毫秒执行时间
_timer = Timer.periodic(Duration(milliseconds: 100), (timer) {
///间隔100毫秒执行一次 每次减100
currentTimeNumber -= 100;
///如果计完成取消定时
if (currentTimeNumber <= 0) {
_timer.cancel();
currentTimeNumber = 0;
}
///流数据更新
progress.value = (totalTimeNumber-currentTimeNumber)/totalTimeNumber;
_streamController.add((totalTimeNumber-currentTimeNumber)/totalTimeNumber);
});
}
@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 Scaffold(
body: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height, //容器填充满整个屏幕
child: StreamBuilder<double>(
stream: _streamController.stream,
initialData: 0,
builder: (BuildContext context, AsyncSnapshot<double> snapshot) {
return Stack(
alignment: Alignment.center,
children: [
Text(
widget.progress.value == 1.0 ? widget.progress.completeText : "${(100 * widget.progress.value).toStringAsFixed(1)} %",
style: widget.progress.style ??
TextStyle(fontSize: widget.progress.radius / 6),
),
Container(
width: widget.progress.radius * 2,
height: widget.progress.radius * 2,
child: CustomPaint(
painter: ProgressPainter(widget.progress),
),
),
],
);
},
),
)
);
}
}
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); //裁剪区域
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//背景
..style = PaintingStyle.stroke
..color = _progress.backgroundColor
..strokeWidth = _progress.strokeWidth;
canvas.drawCircle(Offset(_radius, _radius), _radius, _paint);
_paint//进度
..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);// 将画板移到中心
canvas.rotate((180 + _progress.value * 360) / 180 * pi);//旋转相应角度
var half = _radius / 2;//基点
var eg = _radius / 50; //单位长
_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();
}
}