【Flutter 绘制技巧】Path 路径变换

本文探讨了Flutter中Path的变换技巧,包括Canvas和Path变换的区别,如何利用Path的变换中心进行复合操作,以及在路径变换与命中检测中的应用。重点讲解了路径旋转、缩放及变换中心的灵活设置。
摘要由CSDN通过智能技术生成

本文来探讨一下路径的变换,我们知道 Canvas 本身也支持变换,那 Path 的变换有什么必要性吗?和 Canvas 变换又有什么区别呢?如何在一次变换中叠加多种变换效果,如何修改变换中心?这些都是绘制的基本技能。本文将作为 《Flutter 绘制指南 - 妙笔生花》的补充内容,被同步到小册中。本文源码见 【idraw/extra03path】


1. 绘制路径测试

如下,通过 PathPainter 作为画板,绘制如下图案:左上角是一个三角形路径。坐标系以画布中心为原点,方为正方向,只起到辅助查看作用。通过之前封装的 Coordinate 类进行绘制,详见 coordinate_pro.dart

dart void main() { runApp(CustomPaint( painter: PathPainter(), )); }

可以看出默认情况下,以画布的左上角为原点。

```dart class PathPainter extends CustomPainter {

@override void paint(Canvas canvas, Size size) { Paint paint = Paint()..style = PaintingStyle.stroke; Path path = Path() ..lineTo(40, 40) ..relativeLineTo(0, -40) ..close(); canvas.drawPath(path, paint); }

@override bool shouldRepaint(covariant PathPainter oldDelegate) => true; } ```


2.画板的变换和路径的变换

现在,如果想让这个三角形绘制时以 画布中心 为原点,实现这个需求的方式有很多。总得来说有两个方向,一者是对 Canvas 进行处理,一者是对 Path 进行处理。

如下是对 canvas 进行变换,将画板的左上角平移到中心,如下浅蓝色区域:

dart ---->[extra_03_path/01]---- canvas.translate(size.width/2, size.height/2,);


如下,不改变画布,通过对 Path 处理,也可以完成同样的显示。 此时画布的原点仍在屏幕左上角,如下浅蓝色区域:

dart ---->[extra_03_path/02]---- Path path = Path() ..moveTo(size.width/2, size.height/2,) ..relativeLineTo(40, 40) ..relativeLineTo(0, -40) ..close();


优劣党开始发问,那这两种方式有什么区别,用哪种更好呢。还是那句话,抛开场景谈优劣,就是纸上谈兵,两者各有各的好处,各有各的不足。如果对 canvas 进行变换,那么接下来的所有绘制都会在该变换的基础上;如果是对 Path 进行处理,不会影响 canvas

另外有个非常重要的注意点,如果是对 Path 进行处理,它的真实位置是发生变化的,对 canvas 进行变换,Path 的真实位置不变。Path 中有个 contains 方法,用于校验点是否在路径内。比如下面的红点是 30,10 ,通过 canvas 平移实现的。

此时通过输出可以看出 30,10 点仍在 path 路径下,这就说明 path 只是在绘制时进行了视觉上的偏移,它本身还在红色虚线所示的区域。这样的话,如果路径需要校验触点,就需要额外的运算处理。可能有人会说,不就是加加减减,简单计算一下吗,也不麻烦。但这里只是平移,如果是缩放、旋转、斜切等变换,你还算得过来吗?

dart ---->[extra_03_path/03]---- print(path.contains(Offset(30,10))); //true


3. 路径变换

其实前面只是通过对路径进行移动处理而已,并没有真正用到变换。但有些场景通过计算会非常麻烦,这时路径的变换就会非常实用。比如需要旋转 10° ,如下通过 Matrix4 进行变换,rotationZ 表示沿 Z 轴旋转,也就是说在 XOY 水平面上旋转。可以看出,默认情况下,是以屏幕左上角为变换中心的。

dart ---->[extra_03_path/05]---- Matrix4 m4 = Matrix4.rotationZ(10*pi/180); path = path.transform(m4.storage);


 那如何指定某点为变换中心呢?在一次变换中,通过平移,可以改变变换中心。比如下面左上角的红色虚线路径,通过 平移变换 ,形成如下黑线路径。

```dart ---->[extra03path/06]---- Path path = Path() ..moveTo(0, 0) ..relativeLineTo(40, 40) ..relativeLineTo(0, -40) ..close();

Matrix4 m4 = Matrix4.translationValues(size.width/2, size.height/2, 0); path = path.transform(m4.storage); ```


这时只要在 m4 的基础上 叠加 旋转变换,这样对于 旋转变换 来说,变换中心就是上面红点所示,如下图所示。变换效果的叠加,本质上就是两个 4*4 矩阵的乘法,通过 multiply 方法实现。注意这个方法无返回值,会改变 m4 的值。

dart ---->[extra_03_path/07]---- Matrix4 m4 = Matrix4.translationValues(size.width/2, size.height/2, 0); Matrix4 rotateM4 = Matrix4.rotationZ(10*pi/180); m4.multiply(rotateM4); path = path.transform(m4.storage);


在一次变换中,我们可以叠加多个变换,比如下面在旋转的基础上,再叠加缩放变换。这个变换中心依然是红点,也就是说,在一次变换中,通过平移变换可以用来修改变中心。

dart ---->[extra_03_path/08]---- Matrix4 m4 = Matrix4.translationValues(size.width/2, size.height/2, 0); Matrix4 rotateM4 = Matrix4.rotationZ(10*pi/180); Matrix4 scaleM4 = Matrix4.diagonal3Values(2,2,1); m4.multiply(rotateM4); m4.multiply(scaleM4); path = path.transform(m4.storage);


那接下来思考一个问题,如何以任意点为变换中心呢,比如以 20,20 点为变换中心,进行旋转和缩放操作。实现思路也非常简单,定义两个偏移的矩阵,在旋转和缩放前,先叠加 center ,让变换中心变为 20,20 。在最后为了不影响结果,在通过 back 矩阵,平移会取即可。

```dart ---->[extra03path/09]---- Matrix4 m4 = Matrix4.translationValues(size.width/2, size.height/2, 0); Matrix4 center = Matrix4.translationValues(20, 20, 0); Matrix4 back = Matrix4.translationValues(-20, -20, 0);

Matrix4 rotateM4 = Matrix4.rotationZ(10*pi/180); Matrix4 scaleM4 = Matrix4.diagonal3Values(2,2,1);

m4.multiply(center);// tag1 m4.multiply(rotateM4); m4.multiply(scaleM4); m4.multiply(back); // tag2 path = path.transform(m4.storage); canvas.drawPath(path, paint); ```


4. 路径变换与命中

路径的变换操作是对 路径 本身的真实操作,通过 contains 方法,判断点是否在路径之内。这个点是相对于组件左上角的,也就是说通过手势事件,可以很方便地校验触点是否在路径之内。比如下面的效果,当在区域内时,路径加粗且为橙色,实现代码详见 : 【extra03path/10】

dart ---->[extra_03_path/10]---- bool contains = path.contains(pos.value); double strokeWidth = contains ? 2 : 1; Color color = contains ? Colors.orange : Colors.black; canvas.drawPath(path, paint..strokeWidth = strokeWidth..color = color);

通过不对路径变换,而是通过 canvas 变换,那么在校验时,就需要进行复杂的点位计算。这就是两者之间最大的区别,另外 canvas 变换本质上也是通过 Matrix4 实现的,上面所说的叠加特性对 canvas 也使用。


最后简单说一下 Matrix4#multiplyMatrix4#multiplied 两个方法的区别。从源码中可以看出 multiplied 本质上是通过 multiply 实现功能的,只不过它会克隆对象,对新对象进行 multiply 操作。也就是说这个方法会返回一个新的 Matrix4 对象,不会影响调用者的内部数据。

multiply 方法,如下所示:是根据矩阵的乘法,来修改自身的数据。

所以如果调用者需要在后续被使用,可以通过 Matrix4#multiplied 返回个新的。如果不需要被使用,通过 Matrix4#multiply 方法直接修改自身数据即可。了解其原理之后,就不会用起来稀里糊涂的。
那本文就到这里,谢谢观看 ~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值