我的编程空间,编程开发者的网络收藏夹
学习永远不晚

详解利用Flutter中的Canvas绘制有趣的图形

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

详解利用Flutter中的Canvas绘制有趣的图形

简介

上一篇我们介绍了使用 Flutter 的 Canvas 绘制基本图形的示例,简单的示例没什么好玩的,今天这一篇我们来点有趣的,我们会完成如下图形的绘制:

  • 发现数学重复之美:使用等边三角形组合成彩虹伞面。
  • 绘制彩虹。
  • 绘制评分用的五角星。

通过这一篇,我们可以知道自定义形状绘制的基本原理,然后可以在这个基础上绘制你自己想要绘制的图形。

等边三角形构建重复之美

首先我们来绘制等边三角形,其实上一篇我们也有绘制等边三角形,只是那是将三个顶点手动计算出来的,这一篇我们封装一个绘制等边三角形的通用方法。老规矩,先定义方法的输入参数,如下所示:

  • canvasCanvas 画布
  • color:绘制颜色
  • startVertex:三角形的第一个顶点位置,这里我们其他边都是相对这个点旋转的
  • length:边长
  • startAngle:第一条边相对水平方向旋转的夹角,这样我们可以改变夹角来更改三角形的绘制位置。
  • clockwise:顺时针绘制,如果是顺时针,则绘制的偏移夹角往顺时针方向开始,否则逆时针。
  • filled:是否填充图形。
void drawEquilateralTriangle(
    Canvas canvas, {
    required Color color,
    required Offset startVertex,
    required double length,
    double startAngle = 0,
    clockwise = true,
    filled = true,
  })

等边三角形基于一个顶点,一条边和起始角度后就可以计算其他两个顶点的位置,具体推到通过三角函数就可以了。

具体计算三角形的三个顶点的方法如下,这里逆时针方向和顺时针方向的计算方式有点不同,需要区分一下。

static List<Offset> getEquilateralTriangleVertexes(
      Offset startVertex, double length,
      {double startAngle = 0, bool clockwise = true}) {
  double point2X, point2Y, point3X, point3Y;
  point2X = startVertex.dx + length * cos(startAngle);
  point2Y = startVertex.dy - length * sin(startAngle);
  if (clockwise) {
    point3X = startVertex.dx + length * cos(pi / 3 + startAngle);
    point3Y = startVertex.dy - length * sin(pi / 3 + startAngle);
  } else {
    point3X = startVertex.dx + length * cos(pi / 3 - startAngle);
    point3Y = startVertex.dy + length * sin(pi / 3 - startAngle);
  }

  return [startVertex, Offset(point2X, point2Y), Offset(point3X, point3Y)];
}

有了顶点我们就可以使用 Path 将顶点连起来就完成等边三角形的绘制了,绘制三角形的实现方法如下:

void drawEquilateralTriangle(
    Canvas canvas, {
    required Color color,
    required Offset startVertex,
    required double length,
    double startAngle = 0,
    clockwise = true,
    filled = true,
  }) {
    assert(length > 0);
    Path trianglePath = Path();
    List<Offset> vertexes = ShapesUtil.getEquilateralTriangleVertexes(
      startVertex,
      length,
      clockwise: clockwise,
      startAngle: startAngle,
    );
    trianglePath.moveTo(vertexes[0].dx, vertexes[0].dy);
    for (int i = 1; i < vertexes.length; i++) {
      trianglePath.lineTo(vertexes[i].dx, vertexes[i].dy);
    }
    trianglePath.close();
    Paint paint = Paint();
    paint.color = color;
    if (!filled) {
      paint.style = PaintingStyle.stroke;
    }
    canvas.drawPath(trianglePath, paint);
  }
}

单独一个三角形没啥意思,我们通过画6个等边三角形,每个三角形旋转60度,空心绘制看看怎么样?

一个 完美的六边形出来了,再试试12个怎么样。

形状越多,会越接近圆形,你会充分发现对称之美。下面是我们用24个三角形,填充不同颜色后的效果。有点像一把彩虹伞的伞面了,感觉是不是很美?

上面图形的实现代码如下,其中颜色是通过一个颜色数组完成的。

int number = 24;
for (int i = 0; i < number; ++i) {
  drawEquilateralTriangle(
    canvas,
    color: colors[i],
    startVertex: Offset(center.width, center.height),
    length: 120,
    startAngle: i * 2 * pi / number,
    clockwise: true,
    filled: true,
  );
}

绘制彩虹

有了上面的彩虹伞一样的启发,我们决定来绘制彩虹。彩虹其实比较简单,绘制7条不同颜色的弧线即可。这里讲一下弧线的绘制约束。如下图所示,实际上弧线是通过矩形的内接椭圆限制的(这里用正方形,内接为圆形示例)。外面的矩形限制了椭圆位置和尺寸,而通过 startAngle (起始角度)和 sweepAngle (弧线覆盖的角度范围)就能够确定弧线的起点和终点,从而得到一段弧线。注意的是,数学里我们是逆时针角度为正,但是在 Flutter 默认是顺时针为正,因此如果你要从逆时针方向开始角度就要设置为负数。

下面是弧线绘制的示例代码:

Path path1 = Path();
Rect rect1 = Rect.fromLTWH(startPoint.dx + (width - innerWidth) / 2,
    startPoint.dy + (width - innerWidth) / 2, innerWidth, innerWidth);
path1.arcTo(rect1, -pi / 6, -2 * pi / 3, true);
paint.color = colors[i];
canvas.drawPath(path1, paint);

有了这个基础,我们通过循环 ,绘制7条弧线,保证每条弧线挨着就行。而弧线的线条粗细可以用画笔的宽度来搞定,代码如下。我们这里每条弧线的中心、起始角度和覆盖角度是一样的,通过改变不同弧线的正方形边长实现彩虹弧线的位置不同,然后画笔粗细保持为每条彩虹的高度的一半就可以保证每条彩虹是挨着的了。

void drawRainbow(
    Canvas canvas, {
    required Offset startPoint,
    required double width,
  }) {
  assert(width > 0);
  var paint = Paint();
  double rowHeight = 12;
  paint.strokeWidth = rowHeight / 2;
  List<Color> colors = [
    Color(0xFFE05100),
    Color(0xFFF0A060),
    Color(0xFFE0E000),
    Color(0xFF10F020),
    Color(0xFF2080F5),
    Color(0xFF104FF0),
    Color(0xFFA040E5),
  ];
  paint.style = PaintingStyle.stroke;
  for (var i = 0; i < 7; i++) {
    double innerWidth = width - i * rowHeight;
    Path path1 = Path();
    Rect rect1 = Rect.fromLTWH(startPoint.dx + (width - innerWidth) / 2,
        startPoint.dy + (width - innerWidth) / 2, innerWidth, innerWidth);
    path1.arcTo(rect1, -pi / 6, -2 * pi / 3, true);
    paint.color = colors[i];
    canvas.drawPath(path1, paint);
  }
}

最终效果如下图所示。

绘制五角星

五角星相对来说会复杂一些,主要是要知道通过中心点确定10个顶点的坐标,这里就需要利用二维坐标的旋转公式了,具体可以查阅相关资料,结论是一个点(x2, y2)围绕另一个点(x1, y1)旋转某个角度(α)后得到的新坐标(x, y)计算方式如下:

x=x1+(x2-x1)*cos(α)-(y2-y1)*sin(α)

y=y1+(y2-y1)*cos(α)+(x2-x1)*sin(α)

有了这个基础,我们就可以基于五角星的中心点,第一个顶点,边长(间隔一个点连线的线段长度)来通过旋转计算其他顶点了。其中外面5顶点一组计算,内部5个顶点一组计算。最终获取5个顶点的代码如下:

static List<Offset> getStarVertexes(Offset center, double length) {
  assert(length > 0);
  // 外接圆半径计算(五角星锐角为36度)
  double radius = length / 2 / cos(18 / 180 * pi);
  // 内部顶点的半径
  double innerRadius =
      radius / (cos(36 / 180 * pi) + sin(36 / 180 * pi) / sin(18 / 180 * pi));
  List<Offset> vertexes = [];
  Offset outerStartVertex = Offset(center.dx, center.dy - radius);
  Offset innerStartVertex = Offset(
    center.dx - innerRadius * sin(36 / 180 * pi),
    center.dy - innerRadius * cos(36 / 180 * pi),
  );
  vertexes.add(outerStartVertex);
  vertexes.add(innerStartVertex);
  // 计算方式为以第一个顶点围绕五角星中心点坐标旋转得到
  const double rotateAngle = 72 / 180 * pi;
  for (int i = 1; i < 5; ++i) {
    vertexes.add(Offset(
      center.dx +
          (outerStartVertex.dx - center.dx) * cos(-i * rotateAngle) -
          (outerStartVertex.dy - center.dy) * sin(-i * rotateAngle),
      center.dy +
          (outerStartVertex.dy - center.dy) * cos(-i * rotateAngle) +
          (outerStartVertex.dx - center.dx) * sin(-i * rotateAngle),
    ));
    vertexes.add(Offset(
      center.dx +
          (innerStartVertex.dx - center.dx) * cos(-i * rotateAngle) -
          (innerStartVertex.dy - center.dy) * sin(-i * rotateAngle),
      center.dy +
          (innerStartVertex.dy - center.dy) * cos(-i * rotateAngle) +
          (innerStartVertex.dx - center.dx) * sin(-i * rotateAngle),
    ));
  }

  return vertexes;
}

有了顶点,绘制方式就和三角形一样了,将顶点连起来就好了。下面是我们绘制了一个常见的五星评分的图形。

总结

本篇介绍了基于 Flutter 的 CustomPaint 绘制定制化图形的示例,可以看到,其实只要 UI 小姐姐给出的图形能够用数学表达式表示出来,都可以用 CustomPaintCanvas 来实现。

到此这篇关于详解利用Flutter中的Canvas绘制有趣的图形的文章就介绍到这了,更多相关Flutter Canvas图形内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

详解利用Flutter中的Canvas绘制有趣的图形

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android Flutter利用CustomPaint绘制基本图形的方法

今天小编给大家分享一下Android Flutter利用CustomPaint绘制基本图形的方法的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们
2023-07-02

利用Matlab绘制甘特图的方法详解

这篇文章主要为大家详细介绍了如何利用Matlab实现甘特图(gantt chart)的绘制,文中的示例代码讲解详细,对我们学习Matlab有一定帮助,需要的可以参考一下
2022-11-13

Python利用cv2动态绘制圆和矩形的示例详解

这篇文章主要为大家详细介绍了Python如何利用cv2实现动态绘制圆和矩形的功能,文中的示例代码讲解详细,具有一定的参考价值,需要的可以参考一下
2023-03-23

利用Python-iGraph如何绘制贴吧/微博的好友关系图详解

前言 最近工作中遇到了一些需求,想通过图形化的方式显示社交网络特定用户的好友关系,上网找了一下这方面的图形库有networkx、graphviz等,找了好久我选择了iGraph这个图形库。下面话不多说了,来一起看看详细的介绍吧。 安装igr
2022-06-04

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录