Flutter开发者

自定义View案例【CircleProgressBar】

2018-10-29

上期回顾

在前面的文章中我们看了下如何通过自定义View简单实现了labelView的效果,其实实现起来非常的简单,就是根据用户传递来的参数来做不同的绘制而已。

但是我们实现的LabelView是不能动态更改的,一来是受制于这个Widget的功能,而来就是因为没有跟动画结合啊。

今天我们就通过自定义圆形进度条来讲下自定义View与动画结合的例子。

CircleProgressBar原型进度条

还是先来看下效果吧:

也可能是这个样子:

我们可以根据需要更改进度的背景颜色、进度条颜色以及进度圆环的宽细、文字的样式等等。

话不多说,还是来一步一步看看如何实现吧。

CustomPainter

我们还是先来想想使用canvas的哪个方法来完成绘制。

首先,我们需要绘制一个圆形的背景啊,所以肯定要使用canvas.drawCircle方法。

其次,我们需要绘制圆上面的圆弧,所以就是canvas.drawArc方法了啊。

所以,我们先来绘制一个圆来看效果哈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

class CircleProgressBarPainter extends CustomPainter {
var _paintBckGround;


CircleProgressBarPainter() {
_paintBckGround = new Paint()
..color = Colors.grey
..isAntiAlias = true
..strokeCap = StrokeCap.round
..strokeWidth = 10.0
..style = PaintingStyle.stroke;

}

@override
void paint(Canvas canvas, Size size) {

canvas.drawCircle(Offset(0, 0), 100, _paintBckGround);
}

@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}

然后,我们尝试在相同的位置再绘制一段圆弧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

class CircleProgressBarPainter extends CustomPainter {
var _paintBckGround;
var _paintFore;

CircleProgressBarPainter() {
_paintBckGround = new Paint()
..color = Colors.grey
..isAntiAlias = true
..strokeCap = StrokeCap.round
..strokeWidth = 10.0
..style = PaintingStyle.stroke;

_paintFore = new Paint()
..color = Colors.blueAccent
..isAntiAlias = true
..strokeWidth = 10.0
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke;
}

@override
void paint(Canvas canvas, Size size) {
canvas.drawCircle(Offset(0, 0), 100, _paintBckGround);

Rect rect = Rect.fromCircle(center: Offset(0, 0), radius: 100);
canvas.drawArc(rect, 0, 3.14, false, _paintFore);
}

@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}

这样一来我们就基本上完成了我们最基本的圆形进度条哈

构造方法抽取

还是需要回到第一个问题,要有哪些功能,那些参数需要暴露出去


属性 作用
_strokeWidth 圆弧宽度
_backgroundColor 进度条背景颜色
_foreColor 进度条前景颜色
_startAngle 进度开始的角度
_sweepAngle 扫过的角度
_endAngle 结束的角度

相信大家还能记得弧度和角度的换算方式,如果不记得话去找下初中数学老师叙叙旧哈。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

class CircleProgressBarPainter extends CustomPainter {
var _paintBckGround;
var _paintFore;

final _strokeWidth;
final _backgroundColor;
final _foreColor;
final _startAngle;
final _sweepAngle;
final _endAngle;

CircleProgressBarPainter(this._backgroundColor, this._foreColor,
this._startAngle, this._sweepAngle, this._endAngle, this._strokeWidth) {
_paintBckGround = new Paint()
..color = _backgroundColor
..isAntiAlias = true
..strokeCap = StrokeCap.round
..strokeWidth = _strokeWidth
..style = PaintingStyle.stroke;

_paintFore = new Paint()
..color = _foreColor
..isAntiAlias = true
..strokeWidth = _strokeWidth
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke;
}

@override
void paint(Canvas canvas, Size size) {
var radius = size.width > size.height ? size.width / 2 : size.height / 2;
Rect rect = Rect.fromCircle(center: Offset(radius, radius), radius: radius);

canvas.drawCircle(Offset(radius, radius), radius, _paintBckGround);
canvas.drawArc(rect, _startAngle / 180 * 3.14, _sweepAngle / 180 * 3.14,
false, _paintFore);
}

@override
bool shouldRepaint(CustomPainter oldDelegate) {
return _sweepAngle != _endAngle;
}
}

可以看到,我们的CircleProgressBarPainter可以根据用户传入的参数来完成不同颜色、大小弧度的计算,圆以及圆弧的半径由Size宽和高最小值的二分之一决定。

这个一来我们就可以在调用的地方这样写

1
2
3
4
5
6

CustomPaint(
painter: CircleProgressBarPainter(
Colors.grey, Colors.redAccent, 0, 270, 360, 10),
size: Size(100, 200),
)

这样一来,只要我们更改这里的参数,这个圆弧的显示就会改变。但是却不能动态改变,要想要动态改变还是需要借助于动画的。

结合动画

首先我们建立CurvedAnimation使用减速的插值器来模拟减速效果。然后结合Animation实现数值的变化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

class CircleProgressBarState extends State<CircleProgressBar>
with SingleTickerProviderStateMixin {
Animation<double> _doubleAnimation;
AnimationController _animationController;
CurvedAnimation curve;

@override
void initState() {
super.initState();
_animationController = new AnimationController(
vsync: this, duration: Duration(milliseconds: 3000));

curve = new CurvedAnimation(
parent: _animationController, curve: Curves.decelerate);
_doubleAnimation =
new Tween(begin: 0.0, end: 360)
.animate(curve);

_animationController.addListener(() {
setState(() {});
});
onAnimationStart();
}

@override
void reassemble() {
onAnimationStart();
}

onAnimationStart() {
_animationController.forward(from: widget.startNumber);
}

@override
void dispose() {
super.dispose();
_animationController.dispose();
}

然后把变化的值处理后赋给CircleProgressBarPainter的_sweepAngle即可。

文字显示

当然,在平时的使用中有时候我们是需要在中间显示文字提示的,比如百分比或者数值完成情况,
这个时候我们就可以利用CustomPaint的child来实现。

当然,为了文字居中我们可以在外层包裹一个Center。

为了对动画的时长控制,同样的在构造方法可以选择传入动画的时长,为了控制文字是显示百分比或者数值比我们也可以选择传入传入参数,也可以选择性传入参数控制文字的style。

CircleProgressBar的构造方法:

1
2
3
4
5
6
7
8
9
10
11
12

const CircleProgressBar(
this.size, {
this.backgroundColor = Colors.grey,
this.foreColor = Colors.blueAccent,
this.duration = 3000,
this.strokeWidth = 10.0,
this.textStyle,
this.startNumber = 0.0,
this.maxNumber = 360,
this.textPercent = true,
});

可以看到只有SIze属性是必填参数,其他的参数都是可选的哦。

属性 作用
size 尺寸
backgroundColor 进度条背景颜色
foreColor 进度条前景颜色
duration 动画时长
strokeWidth 圆弧宽度
textStyle 文字风格
startNumber 开始的数字
maxNumber 结束的数字
textPercent 是否使用百分比

在调用的地方就非常的简单了

1
2
3
4
5
6
7
8
9
10

CircleProgressBar(
200.0,
backgroundColor: Colors.grey,
foreColor: Colors.blueAccent,
startNumber: 0,
maxNumber: 100,
duration: 3000,
textPercent: true,
)

代码稍微有点才,阅读不便,就把完整代码放在github了。

完整代码:https://github.com/flyou/circle_progress_bar

到这里基本上就完成了这个圆形进度条效果,但是却也没有结束,因为确实还有许多很多继续改进的,比如进度条颜色随着动画改变,圆形内水波纹上升效果等等。

那么为什么不做啊?没有必要,主要还是(我懒!!!)让大家掌握如何使用。

小结

  • 熟悉并掌握绘制圆形、圆弧的方法
  • 熟悉动画与Widget结合的使用
使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

打赏请备注姓名或者昵称,方便我后期统计哦

关注公众号,及时查阅最新文章