Flutter自定义组件—圆形进度条

CircleProgressView.gif

组件分析

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 ,只不过AnimationController和CurvedAnimation又分别继承了其他类,又加进了一些其他功能,AnimationController作为动画控制器,管理动画的开始(forward)、停止(stop)、倒放(reverse)等功能,也能监听动画的状态。CurvedAnimation是对动画变化的曲线处理,随着时间的推移调整动画的变化率来实现不同的变化效果。
这里相关动画知识就不赘诉了,有需要的可以查看下官方文档。
代码如下:


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!代码基本就是这样了,需要的可以直接复制代码拿去用咯。当然控件的扩展还有很大的空间,如果特殊有什么需求的可以自己在定义一下,或者留言给我,我们共同研究下哦

你可能感兴趣的:(Flutter自定义组件—圆形进度条)