Flutter 浅析之 自定义view 六 CircleProgressBar

2023-05-16

技术无止境,只怕不学习啊,Flutter 我们开始吧

CircleProgressBar原型进度条
自定义view结合动画来完成进度条效果。

CustomPainter
先来想想使用canvas的哪个方法来完成绘制。
首先,需要绘制一个圆形的背景啊,所以肯定要使用canvas.drawCircle方法。
其次,需要绘制圆上面的圆弧,所以就是canvas.drawArc方法了啊。
所以,先来绘制一个圆来看效果哈

/// 绘制进度条
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(size.width/2, size.height/2), 100, _paintBckGround);
  }

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

调用:


void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.red, // 风格颜色
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: '测试项目'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  final String title;
  MyHomePage({Key key, this.title}) : super(key: key);
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Container(
          child: CustomPaint(
            painter: CircleProgressBarPainter(),
          ),
        ),
      ),
    );
  }
}

效果:
在这里插入图片描述
然后,尝试在相同的位置再绘制一段圆弧

/// 绘制进度条
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结束的角度
/// 绘制进度条
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;
    //圆
    canvas.drawCircle(Offset(radius, radius), radius, _paintBckGround);
    //半圆
    Rect rect = Rect.fromCircle(center: Offset(radius, radius), radius: radius);
    canvas.drawArc(rect, _startAngle / 180 * 3.14, _sweepAngle / 180 * 3.14,
        false, _paintFore);
  }

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

调用:

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Container(
          child: CustomPaint(
            painter: CircleProgressBarPainter(
                Colors.grey, Colors.redAccent, 0, 270.0, 360.0, 10.0),
            size: Size(100, 200),
          ),
        ),
      ),
    );
  }
}

效果:
在这里插入图片描述
只要更改这里的参数,这个圆弧的显示就会改变。但是却不能动态改变,要想要动态改变还是需要借助于动画的。

结合动画 文字显示
首先建立CurvedAnimation使用减速的插值器来模拟减速效果。然后结合Animation实现数值的变化

在这里插入图片描述

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: widget.duration));

    curve = new CurvedAnimation(
        parent: _animationController, curve: Curves.decelerate);
    _doubleAnimation =
        new Tween(begin: widget.startNumber, end: widget.maxNumber)
            .animate(curve);

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

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

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

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

  @override
  Widget build(BuildContext context) {
    var percent = (_doubleAnimation.value / widget.maxNumber * 100).round();
    return Container(
        width: widget.size,
        height: widget.size,
        child: CustomPaint(
          painter: CircleProgressBarPainter(
              widget.backgroundColor,
              widget.foreColor,
              widget.startNumber / widget.maxNumber * 360,
              _doubleAnimation.value / widget.maxNumber * 360,
              widget.maxNumber / widget.maxNumber * 360,
              widget.strokeWidth),
          size: Size(widget.size, widget.size),
          child: Center(
            child: Text(
                "${_doubleAnimation.value.round() == widget.maxNumber ? "完成" : "${widget.textPercent ? "$percent%" : "${_doubleAnimation.value.round()}/${widget.maxNumber.round()}"}"}",
                style: widget.textStyle == null
                    ? TextStyle(color: Colors.black, fontSize: 20)
                    : widget.textStyle),
          ),
        ));
  }
}

整体代码:

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.red, // 风格颜色
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: '进度条'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  final String title;
  MyHomePage({Key key, this.title}) : super(key: key);
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Container(
//          child: CustomPaint(
//            painter: CircleProgressBarPainter(
//                Colors.grey, Colors.redAccent, 0, 270.0, 360.0, 10.0),
//            size: Size(100, 200),
//          ),
          child: CircleProgressBar(
            200.0,
            backgroundColor: Colors.grey,
            foreColor: Colors.red,
            startNumber: 0,
            maxNumber: 100,
            duration: 3000,
            textPercent: true,
          ),
        ),
      ),
    );
  }
}

class CircleProgressBar extends StatefulWidget {
  final Color backgroundColor;
  final Color foreColor;
  final int duration;
  final double size;
  final bool textPercent;
  final double strokeWidth;
  final double startNumber;
  final double maxNumber;
  final TextStyle textStyle;

  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,
      });

  @override
  State<StatefulWidget> createState() {
    return CircleProgressBarState();
  }
}

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: widget.duration));

    curve = new CurvedAnimation(
        parent: _animationController, curve: Curves.decelerate);
    _doubleAnimation =
        new Tween(begin: widget.startNumber, end: widget.maxNumber)
            .animate(curve);

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

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

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

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

  @override
  Widget build(BuildContext context) {
    var percent = (_doubleAnimation.value / widget.maxNumber * 100).round();
    return Container(
        width: widget.size,
        height: widget.size,
        child: CustomPaint(
          painter: CircleProgressBarPainter(
              widget.backgroundColor,
              widget.foreColor,
              widget.startNumber / widget.maxNumber * 360,
              _doubleAnimation.value / widget.maxNumber * 360,
              widget.maxNumber / widget.maxNumber * 360,
              widget.strokeWidth),
          size: Size(widget.size, widget.size),
          child: Center(
            child: Text(
                "${_doubleAnimation.value.round() == widget.maxNumber ? "完成" : "${widget.textPercent ? "$percent%" : "${_doubleAnimation.value.round()}/${widget.maxNumber.round()}"}"}",
                style: widget.textStyle == null
                    ? TextStyle(color: Colors.black, fontSize: 20)
                    : widget.textStyle),
          ),
        ));
  }
}

代码可以直接执行看效果,本节就到这里了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Flutter 浅析之 自定义view 六 CircleProgressBar 的相关文章

随机推荐

  • Error: Invalid character in header content [“Authorization“]

    GET https xxxxxx com api getToken Body 34 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 eyJleHAiOjE2NzkzMTM5MDB9 VgZnHxBUqR3I PZD
  • 标准C++库有哪些

    C 43 43 标准库的内容基本可以分以下为10类 xff1a C1 标准库中与语言支持功能相关的头文件 C2 支持流输入 输出的头文件 C3 与诊断功能相关的头文件 C4 定义工具函数的头文件 C5 支持字符串处理的头文件 C6 定义容器
  • 示波器解析串口数据

    文章目录 前言一 示波器准备二 硬件介绍三 软件四 串口TTL数据协议1 波特率2 数据起始和停止信号3 数据有效位4 数据校验位 五 示波器探头连接五 串口数据波形捕获 方式1 xff1a 示波器自带的decode进行解码方式2 xff1
  • UART波形分析

    1 逻辑分析仪解码配置 波特率 xff1a 9600 2 逻辑分析仪结果 3 波特率计算 1除以9600 xff0c 结果如下 xff08 e 4表示10的负4次方 xff09 表示 0 00010416秒 61 104 16 微秒 找到发
  • snprintf()函数使用方法

    众所周知 sprintf不能检查目标字符串的长度 xff0c 可能造成众多安全问题 所以都会推荐使用snprintf 自从snprintf代替了sprintf xff0c 相信大家对snprintf的使用都不会少 xff0c 函数定义如下
  • vlc通过udp读取h264码流

    vlc通过udp读取h264码流 在使用gstreamer过程中 xff0c 遇到需要在服务端推送码流 xff0c 客户端接受的情况 xff0c 而有些主机并未安装gstreamer xff0c 但是可以采用vlc读取视频 xff0c 方法
  • ubuntu20.04回退系统内核

    ubuntu20 04回退系统内核 有时候开机之后突然发现nvidia smi检查不到驱动了 分辨率不对 xff0c 第二个屏幕检测不到等等 xff0c 一般来说是因为内核自动更新导致的 xff0c 这里介绍一下内核回退的方法 第一步 查看
  • Unity 重置项目资源的guid

    有时需要将多个项目的资源合并到一个项目中 xff0c 但有可能有些资源是共用的 xff0c 它的guid是一样的 xff0c 这样合并到一个项目时 xff0c 可能会超成冲突 如果要让每个资源的guid都不相同 xff0c 就需要重新生成g
  • zotero+logseq联合阅读文献具体设置

    title zotero 43 logseq联合阅读文献具体设置 date 2022 05 16 16 10 41 tags literature research logseq zotero zotero 43 logseq联合阅读文献具
  • hexo博客同时发布到github和gitee, 并使用gitee page action更新gitee page

    hexo博客同时发布到github和gitee xff0c 并使用gitee page action更新gitee page 发布博客到github page和gitee page 首先在github和gitee中各自建立一个公开仓库 xf
  • 在git bash中使用oh my zsh

    在git bash中使用oh my zsh 安装zsh 首先从官网下载git bash 进入MSYS2 Packages点击File下载 zst压缩包 xff0c 如图所示 xff1a 直接解压至Git根目录下 可通过git bash的快捷
  • 如何编写CMakeLists.txt,并且使用pkg-config

    这里记录一下如何编写CMakeLists txt 本篇blog记录如何在CMakeLists txt中使用pkg config 首先确定cmake版本 cmake minimum required VERSION 3 16 项目名称 pro
  • 解决WSL上不了网以及不能通过wsl网络ping通主机

    解决WSL上不了网以及不能通过wsl网络ping通主机 WSL上不了网 在更换电脑无线网络之后 xff0c 发现wsl上不了网 xff0c ssh显示no route xff0c ping baidu com ping不通 xff0c 但是
  • WIndows下cmd报错退出进程,代码为1

    WIndows下cmd报错退出进程 xff0c 代码为1 不知道什么原因出现了这种情况 参考微软官方回答 xff08 https answers microsoft com zh hans windows forum all cmd E6
  • docker使用load加载tar镜像时报错no such file or directory

    docker使用load加载tar镜像时报错no such file or directory 解决docker在使用load加载tar镜像时报错open var lib docker tmp docker import xxxxxxxxx
  • sudo启动的程序找不到动态库文件

    sudo启动的程序找不到动态库文件 在 bashrc中添加的LD LIBRARY PATH xff0c 并sudo ldconfig后 xff0c sudo启动的程序还是找不到依赖库 原因分析 sudo启动的程序不会用到bashrc中的配置
  • Pycharm显示cannot find declaration to go to,设置子目录为根目录

    Pycharm显示cannot find declartion to go to xff0c 设置子目录为根目录 使用Pycharm用ctrl跳转函数时显示cannot find declaration to go to 原因可能有很多 x
  • pycharm 2021.2.2 版本之前试用期过了怎么办

    pycharm 2021 2 2 版本之前试用期过了怎么办 虽然 jetbrains 的产品是商业收费 xff0c 而且价格不菲 xff0c 但官方还是为免费使用留下的空间 xff0c 实在良心 收费版可以免费试用30天 xff0c 问题是
  • layabox Native 自己下载资源并缓存

    我们在开发中 xff0c 不管是打的网络版还是本地版 xff0c 或多或少都有可能加载一些网络上的资源 xff0c 并且这些资源不想用dcc方式 xff0c 毕竟现在苹果对热更新管得比较严 xff0c 那如果不用dcc方式 xff0c 我们
  • Flutter 浅析之 自定义view 六 CircleProgressBar

    技术无止境 xff0c 只怕不学习啊 xff0c Flutter 我们开始吧 CircleProgressBar原型进度条 自定义view结合动画来完成进度条效果 CustomPainter 先来想想使用canvas的哪个方法来完成绘制 首