Android Flutter绘制扇形图详解

目录
  • 简介
  • CustomPaint介绍
  • CustomPainter介绍
    • paint介绍
    • shouldRepaint介绍
  • 示例
    • 使用CustomPaint
    • 自定义Painter
    • 绘制
    • 触摸事件处理
    • 动画实现

简介

在开发过程中通常会遇到一些不规则的UI,比如不规则的线条,多边形,统计图表等等,用那些通用组件通过组合的方式无法进行实现,这就需要我们自己进行绘制。可以通过使用CuntomPaint组件并结合画笔CustomPainter去进行手动绘制各种图形。

CustomPaint介绍

CustomPaint是一个继承SingleChildRenderObjectWidget的Widget,这里主要介绍几个重要参数:

child:CustomPaint的子组件。

painter: 画笔,绘制的图形会显示在child后面。

foregroundPainter:前景画笔,绘制的图形会显示在child前面。

size:绘制区域大小。

CustomPainter介绍

CustomPainter是一个抽象类,通过自定义一个类继承自CustomPainter,重写paintshouldRepaint方法,具体绘制主要在paint方法里。

paint介绍

主要两个参数:

Canvas:画布,可以用于绘制各种图形。

Size:绘制区域的大小。

void paint(Canvas canvas, Size size)

shouldRepaint介绍

在Widget重绘前会调用该方法确定时候需要重绘,shouldRepaint返回ture表示需要重绘,返回false表示不需要重绘。

bool shouldRepaint(CustomPainter oldDelegate)

示例

这里我们通过绘制一个饼状图来演示绘制的整体流程。

使用CustomPaint

首先,使用CustomPaint,绘制大小为父组件最大值,传入自定义painter

@override
Widget build(BuildContext context) {
    return CustomPaint(
      size: Size.infinite,
      painter: PieChartPainter(),
    );
}

自定义Painter

自定义PieChartPainter继承CustomPainter

class PieChartPainters extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {

  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return oldDelegate != this;
  }
}

绘制

接着我们来实现paint方法进行绘制

@override
void paint(Canvas canvas, Size size) {
    //移动到中心点
    canvas.translate(size.width / 2, size.height / 2);
    //绘制饼状图
    _drawPie(canvas, size);
    //绘制扇形分割线
    _drawSpaceLine(canvas);
    //绘制中心圆
    _drawHole(canvas, size);
}

绘制饼状图

我们以整个画布的中点为圆点,然后计算出每个扇形的角度区域,通过canvas.drawArc绘制扇形。

void _drawPie(Canvas canvas, Size size) {
    var startAngle = 0.0;
    var sumValue = models.fold<double>(0.0, (sum, model) => sum + model.value);
    for (var model in models) {
      Paint paint = Paint()
        ..style = PaintingStyle.fill
        ..color = model.color;
      var sweepAngle = model.value / sumValue * 360;
      canvas.drawArc(Rect.fromCircle(radius: model.radius, center: Offset.zero),
          startAngle * pi / 180, sweepAngle * pi / 180, true, paint);

      //为每一个区域绘制延长线和文字
      _drawLineAndText(
          canvas, size, model.radius, startAngle, sweepAngle, model);

      startAngle += sweepAngle;
    }
}

绘制延长线以及文本

延长线的起点为扇形区域边缘中点位置,长度为一个固定的长度,转折点坐标通过半径加这个固定长度和三角函数进行计算,然后通过转折点的位置决定横线终点的方向,而横线的长度则根据文字的宽度决定,然后通过canvas.drawLine进行绘制直线。

文本绘制使用TextPainter.paint进行绘制,paint方法里面最终是通过canvas.drawParagraph进行绘制的。

最后再在文字的前面通过canvas.drawCircle绘制一个小圆点。

 void _drawLineAndText(Canvas canvas, Size size, double radius,
      double startAngle, double sweepAngle, PieChartModel model) {
    var ratio = (sweepAngle / 360.0 * 100).toStringAsFixed(2);

    var top = Text(model.name);
    var topTextPainter = getTextPainter(top);

    var bottom = Text("$ratio%");
    var bottomTextPainter = getTextPainter(bottom);

    // 绘制横线
    // 计算开始坐标以及转折点的坐标
    var startX = radius * (cos((startAngle + (sweepAngle / 2)) * (pi / 180)));
    var startY = radius * (sin((startAngle + (sweepAngle / 2)) * (pi / 180)));

    var firstLine = radius / 5;
    var secondLine =
        max(bottomTextPainter.width, topTextPainter.width) + radius / 4;
    var pointX = (radius + firstLine) *
        (cos((startAngle + (sweepAngle / 2)) * (pi / 180)));
    var pointY = (radius + firstLine) *
        (sin((startAngle + (sweepAngle / 2)) * (pi / 180)));

    // 计算坐标在左边还是在右边
    // 并计算横线结束坐标
    // 如果结束坐标超过了绘制区域,则改变结束坐标的值
    var marginOffset = 20.0; // 距离绘制边界的偏移量
    var endX = 0.0;
    if (pointX - startX > 0) {
      endX = min(pointX + secondLine, size.width / 2 - marginOffset);
      secondLine = endX - pointX;
    } else {
      endX = max(pointX - secondLine, -size.width / 2 + marginOffset);
      secondLine = pointX - endX;
    }

    Paint paint = Paint()
      ..style = PaintingStyle.fill
      ..strokeWidth = 1
      ..color = Colors.grey;

    // 绘制延长线
    canvas.drawLine(Offset(startX, startY), Offset(pointX, pointY), paint);
    canvas.drawLine(Offset(pointX, pointY), Offset(endX, pointY), paint);

    // 文字距离中间横线上下间距偏移量
    var offset = 4;
    var textWidth = bottomTextPainter.width;
    var textStartX = 0.0;
    textStartX =
        _calculateTextStartX(pointX, startX, textWidth, secondLine, textStartX, offset);
    bottomTextPainter.paint(canvas, Offset(textStartX, pointY + offset));

    textWidth = topTextPainter.width;
    var textHeight = topTextPainter.height;
    textStartX =
        _calculateTextStartX(pointX, startX, textWidth, secondLine, textStartX, offset);
    topTextPainter.paint(canvas, Offset(textStartX, pointY - offset - textHeight));

    // 绘制文字前面的小圆点
    paint.color = model.color;
    canvas.drawCircle(
        Offset(textStartX - 8, pointY - 4 - topTextPainter.height / 2),
        4,
        paint);
}

绘制扇形分割线

在绘制完扇形之后,然后在扇形的开始的那条边上绘制一条直线,起点为圆点,长度为扇形半径,终点的位置根据半径和扇形开始的那条边的角度用三角函数进行计算,然后通过canvas.drawLine进行绘制。

void _drawSpaceLine(Canvas canvas) {
    var sumValue = models.fold<double>(0.0, (sum, model) => sum + model.value);
    var startAngle = 0.0;
    for (var model in models) {
      _drawLine(canvas, startAngle, model.radius);
      startAngle += model.value / sumValue * 360;
    }
}

void _drawLine(Canvas canvas, double angle, double radius) {
    var endX = cos(angle * pi / 180) * radius;
    var endY = sin(angle * pi / 180) * radius;
    Paint paint = Paint()
      ..style = PaintingStyle.fill
      ..color = Colors.white
      ..strokeWidth = spaceWidth;
    canvas.drawLine(Offset.zero, Offset(endX, endY), paint);
  }

绘制内部中心圆

这里可以通过传入的参数判断是否需要绘制这个圆,使用canvas.drawCircle进行绘制一个与背景色一致的圆。

void _drawHole(Canvas canvas, Size size) {
    if (isShowHole) {
      holePath.reset();
      Paint paint = Paint()
        ..style = PaintingStyle.fill
        ..color = Colors.white;
      canvas.drawCircle(Offset.zero, holeRadius, paint);
    }
}

触摸事件处理

接下来我们来处理点击事件,当我们点击某一个扇形区域时,此扇形需要突出显示,如下图:

重写hitTest方法

注意这个方法的返回值决定是否响应事件。

默认情况下返回null,事件不会向下传递,也不会进行处理; 如果返回true则当前组件进行处理事件; 如果返回false则当前组件不会响应点击事件,会向下一层传递;

我直接在这里处理点击事件,通过该方法传入的offset确定点击的位置,如果点击位置是在圆形区域内并且不在中心圆内则处理事件同时判断所点击的具体是哪个扇形,反之则恢复默认状态。

@override
bool? hitTest(Offset offset) {
    if (oldTapOffset.dx==offset.dx && oldTapOffset.dy==offset.dy) {
      return false;
    }
    oldTapOffset = offset;
    for (int i = 0; i < paths.length; i++) {
      if (paths[i].contains(offset) &&
          !holePath.contains(offset)) {
        onTap?.call(i);
        oldTapOffset = offset;
        return true;
      }
    }
    onTap?.call(-1);
    return false;
}

至此,我们通过onTap向上传递出点击的是第几个扇形,然后进行处理,更新UI就可以了。

动画实现

这里通过Widget继承ImplicitlyAnimatedWidget来实现,ImplicitlyAnimatedWidget是一个抽象类,继承自StatefulWidget,既然是StatefulWidget那肯定还有一个StateState继承AnimatedWidgetBaseState(此类继承自ImplicitlyAnimatedWidgetState),感兴趣的小伙伴可以直接去看源码

实现AnimatedWidgetBaseState里面的forEachTween方法,主要是用于来更新Tween的初始值。

@override
void forEachTween(TweenVisitor<dynamic>visitor) {
   customPieTween = visitor(customPieTween, end, (dynamic value) {
      return CustomPieTween(begin: value, end: end);
    }) as CustomPieTween;
}

自定义CustomPieTween继承自Tween,重写lerp方法,对需要做动画的参数进行处理

class CustomPieTween extends Tween<List<PieChartModel>> {
  CustomPieTween({List<PieChartModel>? begin, List<PieChartModel>? end})
      : super(begin: begin, end: end);

  @override
  List<PieChartModel> lerp(double t) {
    List<PieChartModel> list = [];
    begin?.asMap().forEach((index, model) {
      list.add(model
        ..radius = lerpDouble(model.radius, end?[index].radius ?? 100.0, t));
    });
    return list;
  }

  double lerpDouble(double radius, double radius2, double t) {
    if (radius == radius2) {
      return radius;
    }
    var d = (radius2 - radius) * t;
    var value = radius + d;
    return value;
  }
}

以上就是Android Flutter绘制扇形图详解的详细内容,更多关于Android Flutter扇形图的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android绘制双折线图的方法

    本文实例为大家分享了Android绘制双折线图的具体代码,供大家参考,具体内容如下 自定义View实现双折线图,可点击,点击后带标签描述,暂未实现拖动的功能,实现效果如下: 代码如下: 首先,自定义布局属性: <declare-styleable name="LineChart">     <!--type2.LineChart(双折线图)-->     <attr name="maxYValue" format="integ

  • Android图表库HelloChart绘制多折线图

    本文实例为大家分享了Android图表库HelloChart绘制多折线图的具体代码,供大家参考,具体内容如下 一.效果图 二.实现步骤 1.添加依赖库 compile 'com.github.lecho:hellocharts-android:v1.5.8' 2.布局文件 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.a

  • Android自定义圆角柱状图

    本文实例为大家分享了Android自定义圆角柱状图的具体代码,供大家参考,具体内容如下 需求: 画一个圆角柱状图,显示12个月的数据,Y轴数据动态分割,如果是当前月,就画出当前月图片:点击柱状图变色,并显示虚线弹出当前月信息,滑动时弹框和虚线消失,柱状图刷新到最初. 1.HistogramRound package com.broker.liming.widget;   import android.annotation.TargetApi; import android.content.Con

  • Android自定义View——扇形统计图的实现代码

    Android 扇形统计图 先看看效果: 看上去如果觉得还行就继续往下看吧! 自定义View 定义成员变量 private int mHeight, mWidth;//宽高 private Paint mPaint;//扇形的画笔 private Paint mTextPaint;//画文字的画笔 private int centerX, centerY;//中心坐标 //"其他"的value //扇形图分成太多快 所以要合并一部分为其他 即图中灰色部分 private double

  • Android图像处理之绘制圆形、三角形及扇形的头像

    前言 相信大家在Android日常开发中,绘制圆形和绘制图片都是很容易的事情,但是绘制圆形图片就有点难倒人了.以前为了偷懒就直接去github上找一个开源项目,后来才发现绘制圆形图片其实也是很简单的事. 绘制圆形图片也需要两个步骤: 绘制圆形和绘制图片,只不过要让它们取并集,得到的结果就是一张圆形图片了. 直接上代码: public class CircleImageView extends View { private Paint mPaint; private Paint mTargetPa

  • Android Flutter绘制扇形图详解

    目录 简介 CustomPaint介绍 CustomPainter介绍 paint介绍 shouldRepaint介绍 示例 使用CustomPaint 自定义Painter 绘制 触摸事件处理 动画实现 简介 在开发过程中通常会遇到一些不规则的UI,比如不规则的线条,多边形,统计图表等等,用那些通用组件通过组合的方式无法进行实现,这就需要我们自己进行绘制.可以通过使用CuntomPaint组件并结合画笔CustomPainter去进行手动绘制各种图形. CustomPaint介绍 Custom

  • Python可视化学习之seaborn绘制矩阵图详解

    目录 本文内容速览 1.绘图数据准备 2.seaborn.pairplot 加上分类变量 修改调色盘 x,y轴方向选取相同子集 x,y轴方向选取不同子集 非对角线散点图加趋势线 对角线上的四个图绘制方式 只显示网格下三角图形 图形外观设置 3.seaborn.PairGrid(更灵活的绘制矩阵图) 每个子图绘制同类型的图 对角线和非对角线分别绘制不同类型图 对角线上方.对角线.对角线下方分别绘制不同类型图 其它一些参数修改 本文内容速览 1.绘图数据准备 还是使用鸢尾花iris数据集 #导入本帖

  • Python pyecharts绘制折线图详解

    一.绘制折线图 import seaborn as sns import numpy as np import pandas as pd import matplotlib as mpl import matplotlib.pyplot as plt %matplotlib inline plt.rcParams['font.sans-serif']=['Microsoft YaHei'] # 用来正常显示中文标签 plt.rcParams['axes.unicode_minus']=False

  • Android listView 绘制表格实例详解

    Android  listView 绘制表格 效果图: 二,创建步骤: 1,创建布局: activity_main中的布局: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical" android:

  • Android如何绘制发光效果详解

    前言 之前在看别人写自定义view作绘制的时候,看到别人家的view自带发光效果,看起来也是蛮炫酷的,于是自己也抽出时间来试用一下,这里做了一个模仿太阳的各种状态样式. 先上效果先上效果: 实现方式: public BlurMaskFilter(float radius, Blur style) { 实现是使用的Paint类的setMaskFilter()方法,传入BlurMaskFilter对象实现高斯模糊发光. float radius 设置模糊半径 Blur style 设置发光样式,包括

  • Android  listView 绘制表格实例详解

    Android  listView 绘制表格 效果图: 二,创建步骤: 1,创建布局: activity_main中的布局: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical" android:

  • Android View 绘制机制的详解

    View 绘制机制一. View 树的绘图流程 当 Activity 接收到焦点的时候,它会被请求绘制布局,该请求由 Android framework 处理.绘制是从根节点开始,对布局树进行 measure 和 draw.整个 View 树的绘图流程在ViewRoot.java类的performTraversals()函数展开,该函数所做 的工作可简单概况为是否需要重新计算视图大小(measure).是否需要重新安置视图的位置(layout).以及是否需要重绘(draw),流程图如下: Vie

  • Android Flutter利用CustomPaint绘制基本图形详解

    目录 绘制矩形 绘制圆形 绘制椭圆 绘制任意形状 绘制弧形 总结 上一篇我们介绍了 CustomPaint 的基本概念和使用,可以看到 CustomPaint 其实和 前端的 Canvas基本上是一样的,实际上前端 Canvas 支持的绘制方法 CustomPaint 都支持,毕竟 CustomPaint 其实也是基于 Canvas 实现的.本篇我们来介绍 CustomPaint 基本图形的绘制. 绘制矩形 绘制矩形比较简单,方法定义如下: void drawRect(Rect rect, Pa

  • Android Flutter实现精灵图的使用详解

    目录 前言 如何使用精灵图 自定义实现加载 Flame加载精灵图 前言 在日常开发中遇到的图片展示一般是静态图和Gif图两种形式(静态和动态的不同).与此同时当需要对图片做效果时让其动起来,常用方案是Gif图播放或者是帧动画(多种静态图轮询播放).但在游戏开发中还有一种动图表现形式叫做Sprite图(雪碧图),其在前端开发中也是很常见.为什么需要使用精灵图,因为每张图片显示都需要去发起请求获取,若页面图片数量较多(一个页面有几十个小图)并发请求将是一个大数量级,可能会造成页面加载速度降低,精灵图

  • Android实现动态添加数据与堆叠折线图详解流程

    目录 效果视频 引用 描述 导包 代码分析 初始化 动态添加数据 温度数据 湿度数据 光照数据 动态添加X轴时间值 初始化 自动刷新时间实现 尾言 效果视频 引用 描述 本示例采用的是非常.非常.非常好用的一款第三方SDK--helloCharts 传送门 导包 第一步 :导入maven maven { url 'https://jitpack.io' } 第二步:导入依赖 implementation 'com.github.lecho:hellocharts-library:1.5.8@aa

随机推荐