发布网友 发布时间:2024-10-03 16:33
共1个回答
热心网友 时间:2024-10-03 23:23
本文来探讨一下路径的变换,我们知道Canvas本身也支持变换,那Path的变换有什么必要性吗?和Canvas变换又有什么区别呢?如何在一次变换中叠加多种变换效果,如何修改变换中心?这些都是绘制的基本技能。本文将作为《Flutter绘制指南-妙笔生花》的补充内容,被同步到小册中。本文源码见【idraw/extra_03_path】
1.绘制路径测试如下,通过PathPainter作为画板,绘制如下图案:左上角是一个三角形路径。坐标系以画布中心为原点,右和下方为正方向,只起到辅助查看作用。通过之前封装的Coordinate类进行绘制,详见coordinate_pro.dart。
voidmain(){runApp(CustomPaint(painter:PathPainter(),));}可以看出默认情况下,以画布的左上角为原点。
classPathPainterextendsCustomPainter{@overridevoidpaint(Canvascanvas,Sizesize){Paintpaint=Paint()..style=PaintingStyle.stroke;Pathpath=Path()..lineTo(40,40)..relativeLineTo(0,-40)..close();canvas.drawPath(path,paint);}@overrideboolshouldRepaint(covariantPathPainteroldDelegate)=>true;}2.画板的变换和路径的变换现在,如果想让这个三角形绘制时以画布中心为原点,实现这个需求的方式有很多。总得来说有两个方向,一者是对Canvas进行处理,一者是对Path进行处理。
如下是对Canvas进行变换,将画板的左上角平移到中心,如下浅蓝色区域:
---->[extra_03_path/01]----canvas.translate(size.width/2,size.height/2,);如下,不改变画布,通过对Path处理,也可以完成同样的显示。此时画布的原点仍在屏幕左上角,如下浅蓝色区域:
---->[extra_03_path/02]----Pathpath=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只是在绘制时进行了视觉上的偏移,它本身还在红色虚线所示的区域。这样的话,如果路径需要校验触点,就需要额外的运算处理。可能有人会说,不就是加加减减,简单计算一下吗,也不麻烦。但这里只是平移,如果是缩放、旋转、斜切等变换,你还算得过来吗?
---->[extra_03_path/03]----print(path.contains(Offset(30,10)));//true3.路径变换其实前面只是通过对路径进行移动处理而已,并没有真正用到变换。但有些场景通过计算会非常麻烦,这时路径的变换就会非常实用。比如需要旋转10°?,如下通过Matrix4进行变换,rotationZ表示沿Z轴旋转,也就是说在XOY水平面上旋转。可以看出,默认情况下,是以屏幕左上角为变换中心的。
---->[extra_03_path/05]----Matrix4m4=Matrix4.rotationZ(10*pi/180);path=path.transform(m4.storage);?那如何指定某点为变换中心呢?在一次变换中,通过平移,可以改变变换中心。比如下面左上角的红色虚线路径,通过平移变换,形成如下黑线路径。
---->[extra_03_path/06]----Pathpath=Path()..moveTo(0,0)..relativeLineTo(40,40)..relativeLineTo(0,-40)..close();Matrix4m4=Matrix4.translationValues(size.width/2,size.height/2,0);path=path.transform(m4.storage);这时只要在m4的基础上叠加旋转变换,这样对于旋转变换来说,变换中心就是上面红点所示,如下图所示。变换效果的叠加,本质上就是两个4*4矩阵的乘法,通过multiply方法实现。注意这个方法无返回值,会改变?m4的值。
---->[extra_03_path/07]----Matrix4m4=Matrix4.translationValues(size.width/2,size.height/2,0);Matrix4rotateM4=Matrix4.rotationZ(10*pi/180);m4.multiply(rotateM4);path=path.transform(m4.storage);在一次变换中,我们可以叠加多个变换,比如下面在旋转的基础上,再叠加缩放变换。这个变换中心依然是红点,也就是说,在一次变换中,通过平移变换可以用来修改变中心。
---->[extra_03_path/08]----Matrix4m4=Matrix4.translationValues(size.width/2,size.height/2,0);Matrix4rotateM4=Matrix4.rotationZ(10*pi/180);Matrix4scaleM4=Matrix4.diagonal3Values(2,2,1);m4.multiply(rotateM4);m4.multiply(scaleM4);path=path.transform(m4.storage);那接下来思考一个问题,如何以任意点为变换中心呢,比如以20,20点为变换中心,进行旋转和缩放操作。实现思路也非常简单,定义两个偏移的矩阵,在旋转和缩放前,先叠加center,让变换中心变为20,20。在最后为了不影响结果,在通过back矩阵,平移会取即可。
---->[extra_03_path/09]----Matrix4m4=Matrix4.translationValues(size.width/2,size.height/2,0);Matrix4center=Matrix4.translationValues(20,20,0);Matrix4back=Matrix4.translationValues(-20,-20,0);Matrix4rotateM4=Matrix4.rotationZ(10*pi/180);Matrix4scaleM4=Matrix4.diagonal3Values(2,2,1);m4.multiply(center);//tag1m4.multiply(rotateM4);m4.multiply(scaleM4);m4.multiply(back);//tag2path=path.transform(m4.storage);canvas.drawPath(path,paint);4.路径变换与命中路径的变换操作是对路径本身的真实操作,通过contains方法,判断点是否在路径之内。这个点是相对于组件左上角的,也就是说通过手势事件,可以很方便地校验触点是否在路径之内。比如下面的效果,当在区域内时,路径加粗且为橙色,实现代码详见:【extra_03_path/10】
classPathPainterextendsCustomPainter{@overridevoidpaint(Canvascanvas,Sizesize){Paintpaint=Paint()..style=PaintingStyle.stroke;Pathpath=Path()..lineTo(40,40)..relativeLineTo(0,-40)..close();canvas.drawPath(path,paint);}@overrideboolshouldRepaint(covariantPathPainteroldDelegate)=>true;}0通过不对路径变换,而是通过canvas变换,那么在校验时,就需要进行复杂的点位计算。这就是两者之间最大的区别,另外canvas变换本质上也是通过Matrix4实现的,上面所说的叠加特性对canvas也使用。
最后简单说一下Matrix4#multiply和Matrix4#multiplied两个方法的区别。从源码中可以看出multiplied本质上是通过multiply实现功能的,只不过它会克隆对象,对新对象进行multiply操作。也就是说这个方法会返回一个新的Matrix4对象,不会影响调用者的内部数据。
而multiply方法,如下所示:是根据矩阵的乘法,来修改自身的数据。
所以如果调用者需要在后续被使用,可以通过?Matrix4#multiplied?返回个新的。如果不需要被使用,通过?Matrix4#multiply方法直接修改自身数据即可。了解其原理之后,就不会用起来稀里糊涂的。那本文就到这里,谢谢观看~
原文:https://juejin.cn/post/7097789306302890020