*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

Flutter中的自定义饼状图

  • 效果图
  • 控件功能
  • Flutter中的canvas和paint
    • 大致流程:
  • 画圆弧
    • 声明一些必要元素
    • 声明一只画笔painter
  • 画扇形
  • 在扇形上画文字
  • 系稳安全带,准备加速
  • 惯性转动
    • 雾里探花
    • 动画的运用

效果图

入门 时下最火的移动开发技术Flutter 也 快一年了 ,一直没遇到 入门级但又不失逼格的自定义控件,最近她来了,看似简单,实则蕴含flutter中的绘制基础、动画基础,以及融合了数学、物理学的高难度控件,按照国际性惯例先上图:

看着是不是很简单?那我们上车出发~

控件功能

1、饼状图,显示各元素的比例。
2、各元素上有文字说明
3、圆盘可跟着手势转动
4、快速滑动抬起手后,可惯性滚动直至停止

Flutter中的canvas和paint

搞安卓的 同学都知道 自定义View 中抛开组合控件,需要用到Canvas和Paint,那么在flutter中也一样,给你个画布canvas和画笔paint,可以画出属于你自己的世界。

大致流程:

1、新建类继承于CustomPainter实现paint()和shouldRepaint()方法
2、在paint方法中绘制你想要的内容
3、借助于 CustomPaint Widget来构建自己的Widget

具体细节不作阐述,不熟悉的同学可以移步官方教程Flutter中文网

画圆弧

声明一些必要元素

///比例集合List<double> angles;///文案集合List<String> contents;///颜色集合List<Color> colors;

声明一只画笔painter

    Paint paint = Paint()..color = Colors.red //颜色 红色..strokeWidth = 3.0 // 宽度3.0..isAntiAlias = true //是否抗锯齿 是..style = PaintingStyle.fill; // 模式 填充

画扇形

毕竟都是谷歌旗下产品,api名称和安卓都一模一样,对于安卓同学来说就是换个地方画东西而已。

 void drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint)

五个参数都是知名答意,现在是,startAngle(起始角度,参照点是以水平方向为0Ω dart中 就是0pi)、sweepAngle(扫过的区域角度)、useCenter(扇形区域是否实心)、paint(画笔) 我们都有了,就差一个rect,那我们就new一个出来。

     //声明一个包裹 以控件中心为中心,控件宽一半为半径的圆  的正方形。 Rect rect = Rect.fromCircle(center: Offset(size.width / 2, size.height / 2),radius: size.width / 2);

下面我们就可以遍历比例angles集合,把扇形区域画出来了。

  double startAngles=0,标记每个元素的其实角度//画扇形for (int i = 0; i < angles.length; i++) {paint..color = colors[i];//取到颜色canvas.drawArc(rect, 2 * pi * startAngles2 * pi * angles[i], true, paint);startAngles += angles[i];}

这样我们就把比例画出来了,到此只能说稳如狗。

在扇形上画文字

扇形画完就该在上面写 一些文字用以说明 某个区域代表什么意思了 ,此时暗暗回忆了一下,安卓里面的画文字就是 canvas.drawText…,不过可惜了,flutter中画文字和安卓里面不一样!!!先看一下画文字的方法:

drawParagraph(Paragraph paragraph, Offset offset) ;

看着很简单,由此可见,画文字不需要用到画笔,需要的是Paragraph以及Offset,一个是段落一个是偏移量的意思,切记偏移量在这里指的是文字左上角的 位置,不是安卓里面的左下角。那么重头戏就是paragraph这个变量了。大概也就是长这样:

      // 新建一个段落建造器,然后将文字基本信息填入;ParagraphBuilder pb = ParagraphBuilder(ParagraphStyle(textAlign: TextAlign.left,fontWeight: FontWeight.w300,fontStyle: FontStyle.normal,fontSize: 15.0,));pb.pushStyle(ui.TextStyle(color: Colors.white));pb.addText(text]);// 设置文本的宽度约束ParagraphConstraints pc = ParagraphConstraints(width: 400);// 这里需要先layout,将宽度约束填入,否则无法绘制Paragraph paragraph = pb.build()..layout(pc);

这里主要说一点懵逼的地方,ParagraphConstraints实例的时候,必须要声明width作为 宽,要不后面build的时候无效,不知道是我没搞明白还是另有其他方法,实际上我们也不知道 文案具体的宽高,也没找到 安卓中 测量 一段文本宽高的方法,知道的同学 务必请留言说一下啊,感谢!

下面我们就可以画文本了,画之前要想清楚一个问题,drawParagraph方法里面没有涉及角度的东西,所以画的时候我们只能通过把canvas 移到圆环中心,并且 旋转的方法 让文案进行旋转,这一点 和安卓里面是一样的,下面 上主要代码:

 double startAngles = 0;for (int i = 0; i < contents.length; i++) {canvas.save();//这里先把canvas保存个副本,待会移动旋转完 好还原// 新建一个段落建造器,然后将文字基本信息填入;ParagraphBuilder pb = ParagraphBuilder...上面那一段,拿到paragraph.// 文字左上角起始点Offset offset = Offset(60, 0 - paragraph.height / 2);//由于没找到算文本宽度的方法,这里文本x轴的七点模拟写成 了60。canvas.translate(size.width / 2, size.height / 2);//先把canvas移动到中心点double roaAngle =    2 * pi * (startAngles + angles[i] / 2) ;canvas.rotate(roaAngle);  //再将画布旋转一定角度,具体这个角度怎么来的, 稍微琢磨一下就明白了canvas.drawParagraph(paragraph, offset);canvas.restore();//画完后重置 画布startAngles += angles[i]; //把当前元素角度累加}

这样我们就把文案画在 比例 圆环的内部了,是不是很简单:

系稳安全带,准备加速

到此基础绘制完成了,暂时只涉及到来了 一些简单的绘制,也是给各位老铁温习一下而已,当然对于新手 , 应该还是有些难度的。下面我们要加速了,光有个图不是我们的目的,我们还得让他跟着手指动起来,这里就涉及到了手势处理。

玩安卓自定义View时,里面的onTouchEvent,以及稍微长一点的onInterruptTouchEvent 是我们经常光顾的地方,一些列的 手势传递,诸如down、move、up、flying 这些多少次把我们折磨的体无完肤,那么在flutter中手势处理用什么呢?那就是GestureDetector这个Widget,涉及到拖动的方法回调是这三个:

1、onPanDown(DragDownDetails details)         --------- 手指按下时触发 2、onPanUpdate(DragUpdateDetails details)     ---------手指移动时触发3、onPanEnd (DragEndDetails details)          --------- 手指抬起时触发

可以看见一些列的 down、move、up 操作都能拿到了,下面开始看看怎么用。

脑海中冥想一下滑在圆盘上面,让圆盘跟着动,当手放下的时候 我们从圆心到手指画一条圆半径长的线段,当手指移动的时候,手指到圆心的距离肯定是会变化的,我们只要能做到手指滑动的时候让手指一直在刚才那条线段上就大工搞成了,想想是不是很简单!!!那么问题来了,如何做到这样呢?要是垂直或者水平滑动 就很简单了,计算偏移量,然后 累加,更新偏移量。。。。。。问题是这里不是水平或者垂直运动,而是绕着圆心滑动,那么这里改用什么计算偏移量呢?没错,就是角度,假设我们从A点滑到B点,只要计算出A、B两点以圆心O为顶点形成的角不就可以了 ?如下图:

每次 onPanUpdate 回调的时候,我们 根据此时的 x、y坐标计算出 角AOB的大小,然后累加,绘制的时候 加上这个角度就可以了。那么这个角AOB 怎么算呢?我相信很多人都忘记了三角函数了,这里有很多方法,需要注意的是这里涉及到 正负问题,由于高中的三角函数也忘得差不多了 我就化繁为简,计算角BOC的大小,然后计算角AOC的大小,然后做减法就得到带方向的 角BOA 了。
角BOC和角AOC 的算法就很多了,首先三角形内角公式:

再者就是通过sin和cos求得,具体细节就不阐述了,想不明白的同学可以留言或者看代码细节。伪代码如下


double getTurns() {/// o点(offset.dx+radius,offset.dy+radius)./// C点 (offset.dx+2*radius,offset.dy+radius)./// oc距离  radius/// A点 (pAx,pAy),/// B点  (pBx,pBy)./// AC距离double acDistance = ChartUtils.distanceForTwoPoint(offset.dx + 2 * widget.radius, offset.dy + widget.radius, pAx, pAy);/// AO距离double aoDistance = ChartUtils.distanceForTwoPoint(offset.dx + widget.radius, offset.dy + widget.radius, pAx, pAy);double ocdistance = widget.radius;///根据手在上半圆还是下半圆, 决定角是正还是负int c = 1;if (pAy < (offset.dy + widget.radius)) {c = -1;}///计算 cos aoc 的值 ,然后拿到 角 aocdouble cosAOC = (aoDistance * aoDistance + ocdistance * ocdistance -  acDistance * acDistance) / (2 * aoDistance * ocdistance);double AOC = c * acos(cosAOC);/// BC距离double bcDistance = ChartUtils.distanceForTwoPoint(offset.dx + 2 * widget.radius, offset.dy + widget.radius, pBx, pBy);/// BO距离double boDistance = ChartUtils.distanceForTwoPoint(offset.dx + widget.radius, offset.dy + widget.radius, pBx, pBy);c = 1;if (pBy < (offset.dy + widget.radius)) {c = -1;}///计算 cos boc 的值,然后拿到角 boc;double cosBOC = (boDistance * boDistance +ocdistance * ocdistance -  bcDistance * bcDistance) /   (2 * boDistance * ocdistance);double BOC = c * acos(cosBOC);return BOC - AOC;}

这样圆盘就跟着手动起来啦,是不是还是可以接受,这里主要就是用到了数学的三角形角度计算以及带一点极限思想而已,极限就是A移动到B的过程。

惯性转动

主要功能实现了,还有最后一步,当然也是最复杂的一步,上一步我们实现了圆盘跟着手滑动,那么手指抬起来的时候 肯定存在一个 滑动速度,按照正常思维,这个时候,圆盘应该在此速度之下继续滚动,而不是手指一抬就停止,那么这里又改如何是好呢?

雾里探花

再次闭上我们的双眼,开始冥想,开阔点的想一想地球绕着太阳旋转的过程, 狭隘点想着一头驴在拉磨,拉着拉着绳子断了,这个时候磨在惯性驱动下,如果没有摩擦力的影响,肯定也会一直转下去,由于存在摩擦力,滚两下就停下来了,这里有三个因素:
1、 绳子断了的时候,线速度v,
2、摩擦力 f,
3、绳子系在磨上的点到磨中心点的距离 r。
怎么样,满满的物理感。那这几个因素该怎么用呢?
其实是一个道理,这个时候我们同样要拿到线速度相对于圆心对应的角速度jv,然后给一个每秒减少的角速度量,也就是加速度:ja,通过动画或者拿到移动特定时间转过的角度 累加上,刷新UI,就完成了,想想这样是能行得通。那么问题核心就是求角速度了,这是最难最复杂的地方了,由于手指抬起时我们可以拿到此时x、y方向上的速度velocityX、velocitY,下面再借用大神的一张图细说线速度与角速度的关系:


1、手势up时,拿到当时的velocityX和velocityY,就能得到矢量线速度lineSpeed,并且计算出其与水平线的夹角vectorAngle;
2、然后根据up点的坐标值和中心点坐标值,计算up点到中心点的连线与水平线的夹角levelAngle;
3、通过vectorAngle与levelAngle获取circleLineAngle;
4、勾股定理lineSpeed与circleLineAngle获取circleSpeed;
5、角速度 w 与线速度 v 的关系:wr = v,得到角速度,其中r是触摸点到中心点的距离;
6、减速度绘制刷新UI。

由于计算量过于庞大,就不上代码了,总之就为了拿到此时的 角速度jiaoV。

动画的运用

这样假设我们通过各种骚气操作拿到了角速度jiaoV,然后回到flutter 的动画上来,由于flutter 没提供安卓中的减速度过程的回调方法,这里我提供两种方法,

  1. 我们假设从时间 tA到tB ,分别计算这两个时间点的角速度vA和vB,这样就拿到了 tA到tB 这个时间段转过的角度jAB,做累加,这是一种方法。
  2. jiaoV除以角速度ja,得到一共惯性运动的时间t,这样我们就拿到了动画运行时间,同样假设时间点tC,可以计算0-tC之间滑动的过得角度jC,这样直接把这个角度在绘制的时候加上就可以了,主要涉及的就是路程、时间、加速度公式得到转过的角度,伪代码如下:
/// 按照 va的加速度 拿到 滚动的时间 。 也就是 结束后 惯性动画的 执行 时间, 高中物理double t = ChartUtils.abs(inertiaInitAngle) , // vA;毫秒级double animalValue = turns;//由于不是累加,定义一个变量作为当前旋转的角度var time = new DateTime.now();//动画开启时 的 时间戳int direction = 1; ///方向控制参数,用以最后的角度是加还是减if (inertiaInitAngle < 0) {direction = -1;}//flutter中的动画,不熟悉的同学可以看下官网autoAnimationController = AnimationController(duration: Duration(milliseconds: (t * 1000).toInt()), vsync: this)..addListener(() {var animalTime = new DateTime.now();double t1 = animalTime.millisecondsSinceEpoch - time.millisecondsSinceEpoch;//动画执行中间的某个时间点setState(() {double s1 = (2 * inertiaInitAngle - direction * vA * (t1 / 1000)) *t1 / (2 * 1000);//路程、时间、加速度公式得到转过的角度。turns = animalValue + s1;});});autoAnimationController.forward();

这样我们的圆盘就有了惯性运动啦,快哉快哉,作为入门级自定义控件还是值得一练,当然可以根据需求 让他自动旋转、实现抽奖转盘的特定功能,这里不做多阐述,抛砖引玉即可。

感想:感觉自定义控件终极思想都是数学问题、空间思维偏多,加以API辅助。

时间蹉跎,记得按时撸码。

Flutter架构系列之BaseWidget封装

代码已全部上传github,欢迎star、fork,如果有问题 , 欢迎加QQ群 661614986。

Flutter自定义控件之饼状图、大转盘相关推荐

  1. Flutter 饼状图、柱状图、拆线图、Flutter动态饼图、Flutter图表 flutter_echart 开发文档

    在码农的世界里,优美的应用体验,来源于程序员对细节的处理以及自我要求的境界,年轻人也是忙忙碌碌的码农中一员,每天.每周,都会留下一些脚印,就是这些创作的内容,有一种执着,就是不知为什么,如果你迷茫,不 ...

  2. Axure高保真移动端智能数据监控+用户画像+饼状图+条形图+折线图数据统计+抖音直播app用户数据统计+智慧移动端主播粉丝、评论、播放量大数据统计+套餐购买、续费套餐prd流程

    作品介绍:Axure高保真移动端智能数据监控+用户画像+饼状图+条形图+折线图数据统计+直播app用户数据统计+智慧移动端主播粉丝.评论.播放量大数据统计+套餐购买.续费套餐prd流程 原型演示及下载 ...

  3. 饼状图改变数据显示位置_Tableau--饼图大作战

    作为tableau初学者,自己采用边学边输出的形式,与大家分享,如有好的建议,欢迎交流~ 元数据:罗永浩直播湖北专场商品明细[1] 目录 1.饼状图 2.空心饼图-圆环图 3.双重内嵌饼图 一.饼状图 ...

  4. Flutter 自定义View之 饼状图

    版权声明:本文为博主原创文章,转载请注明出处! 今天跟大家分享的是用Flutter来实现的自定义饼状图,下面来看看效果! 通过点击左右两侧的按钮,可以实现扇形切换,被选中的扇形有个放大的效果,中间的百 ...

  5. android饼状图简书,Charts-饼状图

    上篇文章已经讲述了折线图的用法这边文章主要来谈饼状图. 其实Charts难的部分主要在于配置,所以同样主要说说他的配置. pieGraphView.setExtraOffsets(left: 10, ...

  6. echart饼状图没有数据的时候显示暂无数据_Python数据结构可视化 day 5

    Python 数据结构可视化 (Day5) 01年度工作总结 有时候画布太大,影响到图表的展示,这个时候输入: "init_opts=opts.InitOpts(width='',heigh ...

  7. iNeuOS工业互联平台,发布消息管理、子用户权限管理、元件移动事件、联动控制、油表饼状图和建筑类设备驱动,v3.4版本...

    目       录 1.      概述... 2 2.      平台演示... 2 3.      消息管理... 2 4.      子用户权限管理... 3 5.      元件移动事件... ...

  8. 怎么用python画饼状图_Python入门进阶:Python绘制饼图到Microsoft Excel

    原标题:Python入门进阶:Python绘制饼图到Microsoft Excel 来自:Linux迷https://www.linuxmi.com/python-pie-chart-microsof ...

  9. python画饼图程序_python使用matplotlib画饼状图

    本文实例为大家分享了python使用matplotlib画饼状图的具体代码,供大家参考,具体内容如下 代码与详细注释 from matplotlib import pyplot as plt #调节图 ...

最新文章

  1. webpack Plugins列表
  2. ATF(TF-A)的编译方法
  3. Android高级模糊技术RenderScript和FastBlur
  4. Android开发之品牌机型不同setMargins属性无效的bug
  5. ProtocolBuffer for Objective-C 运行环境配置(真正测试过的)
  6. OpenCV中IplImage与Qt中的QImage转化
  7. Python学习笔记_Day4_集合
  8. 社会达尔文主义 盛行时间_新达尔文主义的心理理论
  9. Eclipse环境变量配置
  10. 出租车计价器设计VHDL
  11. WEB服务器安全设置,有效防护网站攻击70%
  12. python批量ppt转图片,pdf转图片,word转图片脚本
  13. Ubuntu下安装显卡和cuda
  14. linux查找文件或文件夹
  15. python中uppercase是什么意思_Python string.ascii_uppercase方法代码示例
  16. antd-vue的date-picker限制不能选择今天之前的时间
  17. 网络模型——TCP/IP模型
  18. VOC和COCO数据集的介绍和转换
  19. 百度抢先翻开春节红包大战B面
  20. 对比ubuntu与centos系统 ​​​​

热门文章

  1. 【博主推荐】HTML制作一个美观的个人简介网页(附源码)
  2. 关于java代码中执行js脚本
  3. java截取字符串可用于截取文件后缀名
  4. NLP自然语言处理——文本分类(CNN卷积神经网络)
  5. oci8 php,PHP增加OCI8模块
  6. Android输入法挤乱布局问题
  7. BIOS实战之PCI设备枚举一
  8. leica[.m00]如何转成rinex[2.0x,3.0x]格式
  9. 一键获取速卖通商品详情
  10. 搭建eth开发环境_1_centos 环境搭建笔记