Flutter学习:使用CustomPaint绘制路径
Flutter学习:认识CustomPaint组件和Paint对象
Flutter学习:使用CustomPaint绘制路径
Flutter学习:使用CustomPaint绘制图形
Flutter学习:使用CustomPaint绘制文字
Flutter学习:使用CustomPaint绘制图片
drawPath 绘制路径
drawPath需要传递2个参数:
- Path path 路径对象
- Paint paint 绘制对象
dart
复制代码
Path path = Path()..moveTo(100, 100); path.lineTo(250, 250); path.lineTo(350, 180); path.lineTo(200, 500); // 控制路径是否闭合,可不写 path.close(); canvas.drawPath(path, paint);

path.moveTo和path.lineTo
path.moveTo和path.lineTo一样,都需要传递2个参数:
- double x用来设置点的横坐标(x轴)
- double y用来设置点的纵坐标(y轴)
dart
复制代码
path.moveTo(80, 200); path.lineTo(320, 400); canvas.drawPath(path, paint);
其中p0是path.moveTo设置的坐标,p1是path.lineTo设置的坐标

path.relativeMoveTo和path.relativeLineTo
path.relativeMoveTo和path.relativeLineTofiType的使用方法和path.moveTo、path.lineTo一样,只是起始点不再是左上角,而是前一个点的坐标
path.close
用来设置路径是否自己闭合:
dart
复制代码
path.close();

path.reset
清除之前所有的Path对象。并重置原点为左上角。
dart
复制代码
path.reset();
path.fillType
关于fillType的资料可以查看以下文章:
- Quartz2D的渲染模式
fillType有两个枚举值:
- PathFillType.nonZero:内部由有符号边缘交叉的非零和定义。对于给定点,如果从该点到无穷远的线与绕该点顺时针方向的线交叉的次数与与绕该点逆时针方向的线交叉的次数不同,则认为该点位于路径的内侧。
- PathFillType.evenOdd:内部由奇数个边缘交叉定义。对于给定点,如果从该点到无穷远的线穿过奇数条线,则认为该点位于路径的内侧
dart
复制代码
Path path = Path()..moveTo(size.width / 2, 200); path.lineTo(size.width / 4, 500); path.lineTo(size.width / 7 * 6, 320); path.lineTo(size.width / 7, 320); path.lineTo(size.width / 4 * 3, 500); path.close(); // 默认值 path.fillType = PathFillType.nonZero; path.fillType = PathFillType.evenOdd; canvas.drawPath(path, paint);

path.addArc
通过路径绘制圆弧
addArc(Rect oval, double startAngle, double sweepAngle)需要传递3个参数:
- oval绘制一个矩形
- startAngle圆弧开始处
- sweepAngle圆弧开始到结束的角度大小
dart
复制代码
// 只有path.addArc方法,path.moveTo方法将会无效 Path path = Path(); // path.moveTo(size.width / 2, 200); 无效 Rect oval = Rect.fromPoints(const Offset(80, 80), const Offset(300, 180)); path.addArc(oval, 0, 4); canvas.drawPath(path, paint);
dart
复制代码
// 有path.moveTo、path.lineTo、path.addArc这3个方法 // path.lineTo在谁的后面就跟谁连接,默认path.moveTo为(0,0) Path path = Path(); path.moveTo(size.width / 2, 200); Rect oval = Rect.fromPoints(const Offset(80, 80), const Offset(300, 180)); path.addArc(oval, 0, 4); path.lineTo(250, 400); canvas.drawPath(path, paint);

path.addOval、path.addRect、path.addRRect用法和path.addArc一样
path.addPath
该方法将复制一遍已绘制的路径,并进行偏移
addPath(Path path, Offset offset, {Float64List? matrix4})可以传递3个参数,2个必要的,一个可选的:
- path已绘制的路径对象
- offset对path对象进行的偏移量
- matrix4将矩阵平移给定偏移量
没有使用matrix4属性:
dart
复制代码
Path path = Path()..moveTo(100, 100); path.lineTo(210, 240); path.lineTo(80, 380); path.addPath(path, const Offset(120, 120)); canvas.drawPath(path, paint);
使用了matrix4属性:
dart
复制代码
Path path = Path()..moveTo(100, 100); path.lineTo(210, 240); path.lineTo(80, 380); // 变形(倾斜)路径 path.addPath(path, const Offset(120, 120), matrix4: Matrix4.skew(.1, .1).storage); canvas.drawPath(path, paint);

path.extendWithPath
效果和path.addPath一样,属性也一样,只是该方法会将这两条线段连接起来:
dart
复制代码
Path path = Path()..moveTo(100, 100); path.lineTo(210, 240); path.lineTo(80, 380); path.extendWithPath(path, const Offset(120, 120), matrix4: Matrix4.skew(.2, .4).storage); canvas.drawPath(path, paint);

path.addPolygon
添加一条新路径
addPolygon(List points, bool close)需要传递2个参数:
- points一系列点的坐标
- close是否首位相连
dart
复制代码
path.moveTo(size.width / 2, 200); path.lineTo(200, 380); path.lineTo(80, 460); List<Offset> points = const [ Offset(100, 40), Offset(350, 240), Offset(200, 500), ]; path.addPolygon(points, true); canvas.drawPath(path, paint);

path.arcTo
绘制圆弧路径
arcTo( Rect rect, double startAngle, double sweepAngle, bool forceMoveTo, )需要传递4个参数:
- rect绘制一个矩形用来确定圆弧的位置
- startAngle圆弧开始的角度
- sweepAngle圆弧开始到结束的角度大小
- forceMoveTo圆弧路径为新路径还是与原路径相连
dart
复制代码
path.moveTo(size.width / 2, 200); path.lineTo(80, 460); Rect rect = Rect.fromPoints(const Offset(80, 340), const Offset(280, 420)); // forceMoveTo为true path.arcTo(rect, 0, 5, true); // forceMoveTo为false path.arcTo(rect, 0, 5, false); canvas.drawPath(path, paint);

path.arcToPoint
绘制一个两点之间线段距离的直径圆弧
arcToPoint(Offset arcEnd, {Radius radius = Radius.zero, double rotation = 0.0, bool largeArc = false, bool clockwise = true})可以传递5个参数,1个必要的,4个可选的:
- arcEnd确定圆弧的结束点坐标
- radius为0时将绘制一条直线
- rotation感觉没啥用
- largeArc是否是大圆弧
- clockwise决定圆弧的绘制是从左边还是右边
dart
复制代码
path.moveTo(100, 200); // p0 path.arcToPoint( const Offset(320, 500), radius: const Radius.circular(.5), rotation: 2, argeArc: true, clockwise: false, ); // p1 path.arcToPoint( onst Offset(160, 250), adius: const Radius.circular(1), otation: 0, argeArc: true, lockwise: false, ); // p2 path.arcToPoint( const Offset(240, 375), radius: const Radius.circular(0), rotation: 5, largeArc: false, clockwise: true, ); canvas.drawPath(path, paint);

path.relativeArcToPoint
效果和path.arcToPoint一样,只是起始点为前一个点
path.conicTo
绘制圆锥路径
conicTo(double x1, double y1, double x2, double y2, double w)需要传递5个参数:
- x1是第一个点x轴的坐标
- y1是第一个点y轴的坐标
- x2是第二个点x轴的坐标
- y2是第二个点y轴的坐标
- w是权重值。如果权重大于1,则曲线为双曲线;如果权重等于 1,则为抛物线;如果小于 1,则为椭圆
dart
复制代码
path.moveTo(size.width / 2, 200); // 权重=0 path.conicTo(80, 280, 300, 380, 0); // 权重=1 path.conicTo(80, 280, 300, 380, 1); // 权重=2 path.conicTo(80, 280, 300, 380, 2); canvas.drawPath(path, paint);

path.relativeConicTo
效果和path.conicTo一样,只是起始点为前一个点
path.quadraticBezierTo
使用控制点 (x1,y1) 添加一个从当前点弯曲到给定点 (x2,y2) 的二次贝塞尔曲线段
quadraticBezierTo( double x1, double y1, double x2, double y2)需要传递4个参数:
- x1是第一个点x轴的坐标
- y1是第一个点y轴的坐标
- x2是第二个点x轴的坐标
- y2是第二个点y轴的坐标
dart
复制代码
path.moveTo(20, 200); path.quadraticBezierTo(200, 80, size.width - 20, size.width); canvas.drawPath(path, paint);
其中p0代表起始点位置(path.moveTo(20, 200)),p1的坐标是**(x1, y1),p2的坐标是(x2, y2)**

path.relativeQuadraticBezierTo
效果和path.quadraticBezierTo一样,只是起始点为前一个点
path.cubicTo
使用控制点 (x1,y1) 和 (x2,y2) 添加从当前点弯曲到给定点 (x3,y3) 的三次贝塞尔曲线段
cubicTo( double x1, double y1, double x2, double y2, double x3, double y3)需要传递6个参数:
- x1是第一个点x轴的坐标
- y1是第一个点y轴的坐标
- x2是第二个点x轴的坐标
- y2是第二个点y轴的坐标
- x3是第三个点x轴的坐标
- y3是第三个点y轴的坐标
dart
复制代码
path.moveTo(100, 200); path.cubicTo(120, 120, 240, 360, 310, 360); canvas.drawPath(path, paint);
其中p1代表p0点控制线的右边坐标,p2代表p3点控制线的左边坐标

path.relativeCubicTo
效果和path.cubicTo一样,只是起始点为前一个点
path.transform
复制线段,并对其路径进行变形。transform(Float64List matrix4)需要传递一个Float64List对象:
绘制第一条线段:
dart
复制代码
Paint paint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 8 ..color = Colors.red; Path path = Path(); path.moveTo(80, 100); path.lineTo(140, 200); path.lineTo(320, 280); path.lineTo(100, 400); canvas.drawPath(path, paint);
绘制第二条线段:
dart
复制代码
Paint paint1 = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 8 ..color = Colors.blue; Path path1 = path.transform(Matrix4.skew(.1, .4).storage); canvas.drawPath(path1, paint1);

path.getBounds
获取路径的边界矩形
dart
复制代码
path.moveTo(50, 200); path.lineTo(300, 280); // path.getBounds()的结果为Rect.fromLTRB(50.0, 200.0, 300.0, 280.0) print(path.getBounds()); canvas.drawPath(path, paint);

path.shift
功能和path.add一样,只不过不会自动绘制一份路径,会把路径复制一份然后赋值给其他路径。
- Offset offset,复制一份路径后设置偏移量
第一条路径:
dart
复制代码
Paint paint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 8 ..color = Colors.red; Path path = Path(); path.moveTo(size.width / 2, 200); path.lineTo(80, 450); path.lineTo(size.width - 80, 450); path.close(); canvas.drawPath(path, paint);
复制第一条路径,并偏移:
dart
复制代码
Paint paint1 = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 8 ..color = Colors.blue; Path path1 = path.shift(const Offset(50, 50)); canvas.drawPath(path1, paint1);

path.contains
测试给定点是否在路径内。也就是说,如果路径与Canvas.clipPath一起使用,该点是否位于路径的可见部分。
- Offset point,设置要检测的点的位置
dart
复制代码
Offset point1 = const Offset(200, 320); print(path.contains(point1)); Offset point2 = const Offset(200, 520); print(path.contains(point2));
如果该点在路径内,就返回 true,否则返回 false。

path.computeMetrics
path.computeMetrics可以获取路径的详细信息。
computeMetrics({bool forceClosed = false})有一个可选参数:
- forceClosed,如果没有使用
path.close();,该方法获取的结果isClose将会为false。设置该值为true,isClose将会为true
我们先绘制一条普通路径:
dart
复制代码
Paint paint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 8 ..color = Colors.red; Path path = Path(); path.moveTo(size.width / 2, 200); path.lineTo(80, 450); path.lineTo(size.width - 80, 450); path.close();
如果直接对以上绘制的路径使用path.computeMetrics,会返回一个PathMetrics对象:
dart
复制代码
PathMetrics pathMetrics = path.computeMetrics();
一般我们需要用到的是PathMetric对象,获取该对象一共有以下几种方法:
dart
复制代码
// 如果只有一个PathMetric对象推荐使用该方法 PathMetric pathMetric = pathMetrics.single; // 如果有很多个PathMetric对象使用以下任意方法 PathMetric pathMetric = pathMetrics.elementAt(0); PathMetric pathMetric = pathMetrics.first; PathMetric pathMetric = pathMetrics.last;
获取到PathMetric对象后,我们就可以沿着这条路径绘制一条新的路径,使用extractPath方法。
该方法可以传递3个参数:
double start:给定线段开始的距离double end:给定线段结束的距离bool startWithMoveTo:是否以 moveTo 点开始绘制线段
dart
复制代码
Path path1 = pathMetric.extractPath(50, 400, startWithMoveTo: true);
dart
复制代码
Path path1 = pathMetric.extractPath(50, 400, startWithMoveTo: false);

关于computeMetrics的方法和属性还有很多没讲到,等以后有时间了再慢慢研究。
作者:菠萝橙子丶
链接:https://juejin.cn/post/7083304661645541390
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
相关文章:
Flutter学习:使用CustomPaint绘制路径
Flutter学习:认识CustomPaint组件和Paint对象 Flutter学习:使用CustomPaint绘制路径 Flutter学习:使用CustomPaint绘制图形 Flutter学习:使用CustomPaint绘制文字 Flutter学习:使用CustomPaint绘制图片 drawPath 绘制路…...
软件模拟SPI协议的理解和使用编写W25Q64
SPI软件模拟的时序 SPI协议中,NSS、SCK、MOSI由主机产生,MISO由从机产生,在SCK每个时钟周期MOSI、MISO传输一位数据,数据的输入输出是同时进行的,所以读写数据也可以视作交换数据。所以读写时对数据位的控制都是用同一…...
SQLI手动注入和python sqlmap代码注入
sql教程: https://www.w3school.com.cn/sql/index.asp数据库: mysql oracle mssql常用方法 system_user() 系统用户名 user() 用户名 current_user() 当前用户名 session_user() 连接数据库的用户名 d…...
MemcachedRedis构建缓存服务器 (数据持久化,主从同步,哨兵模式)
Memcached/redis是高性能的分布式内存缓存服务器,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web等应用的速度、 提高可扩展性。降低数据库读的压力 Nsql的优点:高可扩展性,分布式计算,低成本,…...
Python语法基础(变量 注释 数据类型 输入与输出 运算符 缩进)
目录 变量变量命名规则变量的类型变量的创建变量的作用域 注释的方法数据类型对象和引用的概念Number(数字)数据转换 输入与输出输入函数输出函数输出函数的end参数输出格式多行语句 运算符算术运算符赋值运算符三目运算符运算符的优先级 缩进缩进格式注意事项层级嵌套 变量 标…...
linux espeak语音tts;pyttsx3 ubuntu使用
整体使用espeak声音很机械不太自然 1、linux espeak语音tts 安装: sudo apt install espeak使用: #中文男声 espeak -v zh 你好 #中文女声 espeak -v zhf3 你好 #粤语男声 espeak -v zhy 你好注意:espeak -v zh 你好 (Full d…...
小白该如何学习Linux操作系统?
💂 个人网站:【工具大全】【游戏大全】【神级源码资源网】🤟 前端学习课程:👉【28个案例趣学前端】【400个JS面试题】💅 寻找学习交流、摸鱼划水的小伙伴,请点击【摸鱼学习交流群】 Linux作为一种开源操作系…...
2023双十一:实体门店闯入,第二战场全面开战
“闺女,吃饺子了吗?”11月8日,立冬,忙碌一天的陈曦回家路上接到母亲电话,才想起来家里冷冻水饺没了,又不想再去超市,直接打开美团买菜买了两袋,回家就煮了吃。当然,最终她…...
操作系统·处理机调度死锁
3.1 处理机调度概述 3.1.1 处理机调度概述 高级调度 (High level Scheduling)决定把外存上哪些作业调入内存、创建进程、分配资源。高级调度又称作业调度、长程调度或宏观调度。只在批处理系统中有高级调度。 中级调度 (Middle level Scheduling)完成进程的部分或全部在内、…...
SQL第四次上机实验
1.查询借阅了计算机类或者文学类图书的读者的借书证号 USE TSGL GO SELECT DISTINCT Reader.Lno FROM Book,Lend,Reader WHERE Book.ISBNLend.ISBN AND Lend.LnoReader.Lno AND Class 计算机类 OR Class 文学类2.查询同时借阅了计算机类和文学类图书的读者的借书证号 USE T…...
读书笔记:彼得·德鲁克《认识管理》第11章 若干例外及经验教训
一、章节内容概述 例外的服务机构不仅表明服务机构实现卓越绩效不是天方夜谭,而 且指明了实现的方法。这一课,是美国电话电报公司给“自然垄断行业”上的;是19世纪后期处于创建阶段的美国现代大学给学校或医院类机构上的;是20世纪30年代的田纳西河流域管…...
JVM-虚拟机的故障处理与调优案例分析
案例1:大内存硬件上的程序部署策略 一个15万PV/日左右的在线文档类型网站最近更换了硬件系统,服务器的硬件为四路志强处理器、16GB物理内存,操作系统为64位CentOS 5.4,Resin作为Web服务器。整个服务器暂时没有部署别的应用&#…...
JMeter 相关的面试题
📢专注于分享软件测试干货内容,欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!📢交流讨论:加入1000人软件测试技术学习交流群📢资源分享:进了字节跳动之后,才…...
你在React项目中是如何使用Redux的? 项目结构是如何划分的?
一、背景 在前面文章了解中,我们了解到redux是用于数据状态管理,而react是一个视图层面的库 如果将两者连接在一起,可以使用官方推荐react-redux库,其具有高效且灵活的特性 react-redux将组件分成: 容器组件&#…...
[每周一更]-(第71期):DevOps 是什么?
Wiki的解释: DevOps(Development和Operations的混成词)是一种重视“软件开发人员(Dev)”和“IT运维技术人员(Ops)”之间沟通合作的文化、运动或惯例。 通过自动化“软件交付”和“架构变更”的…...
k8s的安装部署,详细过程展示(保姆级安装教程)
k8s应用部署方式演变 在部署应用程序的方式上,主要经历了三个时代: 传统部署:互联网早期,会直接将应用程序部署在物理机上 优点:简单,不需要其它技术的参与 缺点:不能为应用程序定义资源使用…...
基于windows、GDAL2.2.3版本和Java集成安装和使用GDAL库的方法
基于windows、GDAL2.2.3版本和Java集成安装和使用GDAL库的方法 一、下载gdal windows版本64位2.2.3版本 下载地址: https://www.gisinternals.com/archive.php 找到gdal-202-1911-x64-core.msi下载并安装 安装后默认目录为:C:\Program Files\GDAL 二、…...
AlphaControls控件TsRadioGroup的使用
通常使用AlphaControls控件中的TsRadioGroup时,往往使用默认值,会造成TsRadioGroup标题被TsRadioGroup的ITEMs占用,严重影响美观: 解决方案,通过对TsRadioGroup的ContentVOffset属性,设置为10。即可立即改善…...
安卓常见设计模式8------享元模式(Kotlin版)
1. W1 是什么,什么是享元模式? 享元模式(Flyweight Pattern)是一种结构型设计模式,用于有效地支持大量细粒度的对象共享。在 Android 中,享元模式可以用于减少内存使用和提高性能,特别是在需…...
day54 django中orm数据库增删改查
昨日内容回顾 三板斧问题 HttpResponse # 返回的是字符串 render # 渲染一个HTML静态文件,模板文件 redirect # 重定向的 """在视图文件中得视图函数必须要接收一个形参request,并且,视图…...
若依框架集成ShardingSphere-JDBC 5.2.0踩坑实录:从配置到动态数据源切换的完整流程
若依框架深度整合ShardingSphere-JDBC 5.2.0实战:动态数据源切换与分表策略全解析 当企业级应用面临数据量激增时,传统单库单表的架构往往成为性能瓶颈。最近在重构一个用户量突破千万级的SaaS系统时,我们选择了若依框架作为基础架构…...
手把手教你用SystemVerilog断言(SVA)给仲裁器模块写“活”的Spec文档
用SystemVerilog断言为仲裁器构建可执行规格的实战指南 在数字芯片验证领域,断言(Assertion)已经成为连接设计意图与验证实施的关键桥梁。本文将从一个四端口仲裁器的实际案例出发,演示如何将自然语言描述的设计规格转化为精确的S…...
用国密SM4实现FPE格式保留加密:一个保护手机号、银行卡号的Python实战案例
用国密SM4实现FPE格式保留加密:保护敏感数据的Python实战指南 想象一下这样的场景:你的数据库里存储着用户的手机号和银行卡号,这些数据需要被严格保护,但又不能影响业务系统的正常运行。传统的加密方式会将这些信息变成一串乱码…...
别再傻傻转存了!5分钟搞懂Base64图片体积计算与优化技巧(附Python/JS代码)
Base64图片体积计算的科学原理与高效优化策略 在当今数字化时代,Base64编码图片作为数据嵌入方案被广泛应用于网页开发、移动应用和数据传输场景。然而,许多开发者对Base64编码后体积膨胀的机制存在误解,导致资源浪费和性能瓶颈。本文将深入解…...
从一次‘Permission denied’错误讲起:手把手教你用chmod搞定Linux下各种文件的权限问题
从"Permission denied"到权限掌控:Linux文件权限实战指南 引子:一个常见错误的背后 那天下午,服务器监控突然报警——核心数据同步脚本停止运行了。我SSH登录到机器,尝试手动执行脚本,终端却冷冰冰地返回&am…...
从开发到上架:手把手教你用Inno Setup为Qt应用制作专业安装包(附脚本自定义技巧)
从开发到上架:用Inno Setup打造专业级Qt应用安装包的完整指南 当你完成了一个Qt应用的开发,看着调试通过的绿色对勾,那种成就感无与伦比。但接下来呢?如何让你的作品从开发环境走向用户桌面?这就是我们今天要探讨的核心…...
别再只会用--help了!Python argparse的nargs和action参数实战避坑指南
深度解析Python argparse:掌握nargs与action参数的高阶用法 在Python开发中,命令行参数处理是构建可复用脚本的关键环节。虽然大多数开发者都能使用基础的add_argument方法,但当遇到需要处理复杂参数场景时——比如动态长度的输入列表、互斥的…...
STM8S003F3P6串口通信避坑指南:为什么你的9600波特率总丢数据?
STM8S003F3P6串口通信避坑指南:为什么你的9600波特率总丢数据? 在嵌入式开发中,串口通信是最基础也最常用的功能之一。对于STM8S003F3P6这样的低成本MCU来说,串口通信看似简单,却隐藏着不少"坑"。很多开发者…...
告别网盘限速!八大网盘直链下载助手完整使用指南
告别网盘限速!八大网盘直链下载助手完整使用指南 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 ,支持 百度网盘 / 阿里云盘 / 中国移动云盘 / 天翼云盘 …...
Sunshine终极指南:构建家庭游戏串流服务器的完整教程
Sunshine终极指南:构建家庭游戏串流服务器的完整教程 【免费下载链接】Sunshine Self-hosted game stream host for Moonlight. 项目地址: https://gitcode.com/GitHub_Trending/su/Sunshine Sunshine是一款强大的自托管游戏串流服务器,专为Moonl…...
