组件分析
1、从上图可以看出,该控件由一个圆环和一个圆弧组成,做出圆环需要的就是画笔(Paint)和画板(Canvas),在Flutter中自定义画笔和画板使用CustomPaint组件来完成。
2、当点击按钮调整进度条进度时,进度条有一个开始到结束的动画过程,这里我们需要使用AnimationController,CurvedAnimation ,Tween组合来完成动画。
码文分析
1、CustomPaint
首先看下CustomPaint源码,如下图,有6个需要我们注意的参数,功能源码中都有注释,这里在代码中简单解释一下
class CustomPaint extends SingleChildRenderObjectWidget {
const CustomPaint({
Key key,
this.painter, //实现CustomPainter ,所画的图形在child下层
this.foregroundPainter, //实现CustomPainter ,所画的图形在child上层
this.size = Size.zero, //设置画板大小,child不为空的时候,设置无效
this.isComplex = false, //如果是绘制复杂图形,可以设置为true,会提高渲染速度,前提是painter和foregroundPainter都不能为空
this.willChange = false, //告诉缓存器下一帧是否会改变,同样前提是painter和foregroundPainter都不能为空时可以设置为true
Widget child, //子组件
}) : assert(size != null),
assert(isComplex != null),
assert(willChange != null),
assert(painter != null || foregroundPainter != null || (!isComplex && !willChange)),
super(key: key, child: child);
...省略
2、CustomPainter
(1)新建ProgressPaint继承CustomPainter,实现其中的paint 和 shouldRepaint方法 。paint 方法有两个参数,canvas参数,我们的图形就是通过canvas画板画出来的;Size 参数,是指我们画板的尺寸,尺寸的大小可由父级控件控制。
(2)开始画图 :首先新建两个画笔Paint,一个作为背景圆环画笔, 一个作为进度圆弧画笔 ; 然后就可以开始画图了,首先是背景圆环,确定圆心位置和半径大小 ,使用canvas.drawCircle()方法画出圆环;进度条使用canvas.drawArc()方法绘制,这里需要注意的是第三个参数sweepAngle,这里有个对应关系:180° = Π ≈ 3.14 ,所以这里如果画90°的弧线sweepAngle就可以写成3.14/2 ,
代码如下:
class ProgressPaint extends CustomPainter {
ProgressPaint(
this.progress, //进度
this.width, //画笔宽度
this.backgroundColor, //背景画笔颜色
this.progressColor) {
//背景画笔
paintBg = Paint()
..color = backgroundColor
..strokeWidth = width
..isAntiAlias = true //是否开启抗锯齿
..style = PaintingStyle.stroke; // 画笔风格,线
//进度画笔
paintProgress = Paint()
..color = progressColor
..strokeWidth = width
..isAntiAlias = true //是否开启抗锯齿
..strokeCap = StrokeCap.round // 笔触设置为圆角
..style = PaintingStyle.stroke;// 画笔风格,线
}
final Color backgroundColor;
final double progress;
final Color progressColor;
final double width;
var paintBg;
var paintProgress;
@override
void paint(Canvas canvas, Size size) {
//半径,这里为防止宽高不一致,取较小值的一半作为半径大小
double radius = size.width > size.height ? size.height / 2 : size.width / 2;
canvas.drawCircle(Offset(size.width / 2, size.height / 2), radius, paintBg);
Rect rect = Rect.fromCircle(
center: Offset(size.width / 2, size.height / 2), radius: radius);
canvas.drawArc(rect, 0, progress, false, paintProgress);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
3、AnimationController
图形已经准备好了,接下来就是让进度条动起来了,使用AnimationController + CurvedAnimation + Tween组合方式,使进度条在给定进度的同时,跳转到指定位置。
查看源码可以发现AnimationController和CurvedAnimation 其实都是继承至Animation
这里相关动画知识就不赘诉了,有需要的可以查看下官方文档。
代码如下:
class CircleProgressView extends StatefulWidget {
///背景圆形色值
final Color backgroundColor;
///当前进度 0-100
final double progress;
///进度条颜色
final Color progressColor;
///圆环宽度
final double progressWidth;
///宽度
final double width;
///高度
final double height;
CircleProgressView(
{Key key,
@required this.progress,
@required this.width,
@required this.height,
this.backgroundColor = Colors.grey,
this.progressColor = Colors.blue,
this.progressWidth = 10.0})
: super(key: key);
@override
_CircleProgressViewState createState() => _CircleProgressViewState();
}
class _CircleProgressViewState extends State
with TickerProviderStateMixin {
static const double _Pi = 3.14;
Animation animation;
AnimationController controller;
CurvedAnimation curvedAnimation;
Tween tween;
double oldProgress;
@override
void initState() {
super.initState();
oldProgress = widget.progress;
controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 300));
curvedAnimation =
CurvedAnimation(parent: controller, curve: Curves.decelerate);
tween = Tween();
tween.begin = 0.0;
tween.end = oldProgress;
animation = tween.animate(curvedAnimation);
animation.addListener(() {
setState(() {});
});
controller.forward();
}
//这里是在重新赋值进度时,再次刷新动画
void startAnimation() {
controller.reset();
tween.begin = oldProgress;
tween.end = widget.progress;
animation = tween.animate(curvedAnimation);
controller.forward();
oldProgress = widget.progress;
}
@override
void dispose() {
super.dispose();
controller.dispose();
}
@override
Widget build(BuildContext context) {
if (oldProgress != widget.progress) {
startAnimation();
}
return Container(
width: widget.width,
height: widget.height,
padding: EdgeInsets.all(10),
child: CustomPaint(
child: Center(child: Text("${animation.value.toInt()}%")),
//这里因为我们给的进度一般都是0-100区间
painter: ProgressPaint(animation.value / 50 * _Pi, widget.progressWidth,
widget.backgroundColor, widget.progressColor),
),
);
}
}
ok!代码基本就是这样了,需要的可以直接复制代码拿去用咯。当然控件的扩展还有很大的空间,如果特殊有什么需求的可以自己在定义一下,或者留言给我,我们共同研究下哦