前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[-Flutter 自组篇-] 圆形进度条

[-Flutter 自组篇-] 圆形进度条

作者头像
张风捷特烈
发布2020-04-30 15:19:49
1.7K0
发布2020-04-30 15:19:49
举报

今天写个简单的,自定义一个圆形进度条,并且加上小箭头指向内圈进度。 进度条已上传到公网,使用circle_progress: ^0.0.1,使用如下

代码语言:javascript
复制
void main() => runApp(MaterialApp(
    title: 'Flutter Demo',
    theme: ThemeData(
      primarySwatch: Colors.blue,
    ),
    home: Scaffold(
        appBar: AppBar(
          title: Text("Flutter 之旅"),
        ),
        body: TestStateful() //内置案例
    )
));


1.准备阶段
1.1:定义描述对象类Progress

将需要变化的属性抽离出一个描述类,传参方便些

代码语言:javascript
复制
///信息描述类 [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:自定义组件类CircleProgressWidget

这便是我们的组件

代码语言:javascript
复制
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();
  }
}

1.3:自定义ProgressPainter

我们的绘制逻辑在这里进行

代码语言:javascript
复制
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); //裁剪区域
  }

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

2.绘制
2.1:绘制进度条

如果直接用给定的半径,你会发现是这样的。原因很简单,因为Canvas画圆半径是内圆加一半线粗。 于是我们需要校正一下半径:通过平移一半线粗再缩小一半线粗的半径。

代码语言:javascript
复制
_radius = _progress.radius - _progress.strokeWidth / 2;

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

背景直接画圆,进度使用drawArc方法,要注意的是Flutter中使用的是弧度!!。

代码语言:javascript
复制
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结合使用,可能会更方便。

代码语言:javascript
复制
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:绘制点

绘制点的时候要注意颜色的把控,判断进度条是否到达,然后更改颜色

代码语言:javascript
复制
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();
}

2.4:拼装

也许你会问Text呢?在Canvas里画Text好麻烦,这里用一个Stack包一下挺方便的

代码语言:javascript
复制
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],
    );
  }
}
复制代码

OK,这样就可以了。


3.使用
3.1:简单使用
代码语言:javascript
复制
CircleProgressWidget(
        progress: Progress(
            backgroundColor: Colors.grey,
            value: 0.8,
            radius: 100,
            completeText: "完成",
            color: Color(0xff46bcf6),
            strokeWidth: 4))

3.2:组件代码全览

拿去用吧,谢谢,不送。

代码语言:javascript
复制
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();
}

///信息描述类 [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 _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); //裁剪区域
    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();
  }
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年08月02日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.准备阶段
    • 1.1:定义描述对象类Progress
      • 1.2:自定义组件类CircleProgressWidget
        • 1.3:自定义ProgressPainter
        • 2.绘制
          • 2.1:绘制进度条
            • 2.2:绘制箭头
              • 2.3:绘制点
                • 2.4:拼装
                • 3.使用
                  • 3.1:简单使用
                    • 3.2:组件代码全览
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档