当前位置: 首页 > news >正文

Flutter 实现一个容器内部元素可平移、缩放和旋转等功能(三)

Flutter 实现一个容器内部元素可平移、缩放和旋转等功能(三)

Flutter: 3.35.6

因为实现了单个的,给出github链接:https://github.com/yhtqw/FrontEndDemo

前面我们简单实现了元素的平移和缩放,接下来我们继续实现旋转功能。

元素的旋转会改变角度,角度一变,那么响应事件的热区也会跟着改变,所以我们得提前考虑这些会因为角度改变而改变的地方。

先来简单实现一下旋转,先不考虑上述的热区问题。

要实现旋转,我们就得知道元素的旋转角度,主要得出旋转的角度,那么实现起来就比较简单,所以简单使用数学的知识分析一下吧

从我们这个需求中可以提取到的数据为按下点的坐标,拖动时变换的坐标;所以我们能否根据一个点的坐标,计算出该点与某点形成的夹角,好像刚好有个满足部分,就是arctan2,arctan2的主要作用是根据一个点的坐标,计算出该点与坐标原点所形成的夹角(主要作用);如果我们要知道给出点与任意(x’, y’)形成的夹角呢?前面的arctan2中将坐标原点换成任意某点不就行了?

使用 arctan2 计算两点连线的角度,核心是计算两点之间的坐标差 (Δx, Δy),然后将其作为 arctan2 的参数。所以实现起来就比较简单了。其实这个实现的原理在很多地方都一样,例如web端的元素拖动旋转也可以使用这个原理。实现的方式应该不止一种吧,只要能计算出这个角度就行了。

值得注意的是,现在我们研究的是单个元素,所以坐标系就是以元素自身形成的(响应的事件也是在这个元素上),等后期要实现多个,坐标系就得以外层容器作为参考了。

// 其他省略.../// 新增旋转状态热区字符串constString statusRotate='rotate';/// 抽取响应旋转操作区域的大小finaldouble rotateWidth=20;finaldouble rotateHeight=20;/// 旋转角度double rotateNumber=0;double initRotateNumber=0;void_onPanUpdate(DragUpdateDetails details){print('更新: $details');if(status==statusMove){_onMove(details.localPosition.dx,details.localPosition.dy);}elseif(status==statusScale){_onScale(details.delta.dx,details.delta.dy);}elseif(status==statusRotate){// 新增旋转热区的响应事件_onRotate(details.localPosition.dx,details.localPosition.dy);}}void_onPanEnd(){print('抬起或者因为某些原因并没有触发onPanDown事件');setState((){// 当次结束后重新记录,也可以在按下时记录initX=x;initY=y;// 新增旋转角度的记录initRotateNumber=rotateNumber;});}/// 处理旋转void_onRotate(double dx,double dy){/// 要计算点 (x, y) 与任意点 (x', y') 连线所成的角度,可以使用 arctan2 函数。/// 关键在于将两点之间的相对坐标差作为 arctan2 的输入参数。/// 这里我们以元素的中心为旋转中心/// 利用上述方法计算起始点(按下时)与中心的连线组成的夹角为初始夹角,/// 拖动的点与中心点连线组层的夹角为结束时的夹角,/// 通过初始夹角与结束夹角计算旋转的角度// 确定旋转中心,因为这里的拖动是单个元素,坐标都是相对于元素自身形成的坐标系,所以坐标中心始终都是元素的中心double centerX=elementWidth/2;double centerY=elementHeight/2;double diffStartX=startPosition.dx-centerX;double diffStartY=startPosition.dy-centerY;double diffEndX=dx-centerX;double diffEndY=dy-centerY;double angleStart=atan2(diffStartY,diffStartX);double angleEnd=atan2(diffEndY,diffEndX);setState((){rotateNumber=initRotateNumber+angleEnd-angleStart;});}/// 判断点击在什么区域String?_onDownZone(double x,double y){if(x>=elementWidth-scaleWidth&&x<=elementWidth&&y>=elementHeight-scaleHeight&&y<=elementHeight){returnstatusScale;}elseif(x>=elementWidth-rotateHeight&&x<=elementWidth&&y>=0&&y<=rotateHeight){// 固定右上角为旋转热区returnstatusRotate;}elseif(x>=0&&x<=elementWidth&&y>=0&&y<=elementHeight){returnstatusMove;}returnnull;}// 新增响应旋转操作Positioned(left:x,top:y,child:Transform.rotate(angle:rotateNumber,child:GestureDetector(onPanDown:_onPanDown,onPanUpdate:_onPanUpdate,onPanEnd:(details)=>_onPanEnd(),onPanCancel:_onPanEnd,child:Container(width:elementWidth,height:elementHeight,color:Colors.transparent,child:Stack(alignment:Alignment.center,clipBehavior:Clip.none,children:[Container(width:elementWidth,height:elementHeight,color:Colors.amber,),// 响应旋转操作Positioned(top:0,right:0,child:Container(width:scaleWidth,height:scaleHeight,color:Colors.white,),),// 响应缩放操作],),),),),),// 其他省略...

运行效果:

这样就简单实现了旋转。然后我们继续考虑热区的问题,当旋转一定角度的时候,再次点击对应的热区,就无法响应事件了,因为旋转后热区坐标已经发生改变,所以我们得对点击判断中加入角度的影响。

已知某点坐标和旋转角度,求旋转后的坐标值?

要计算旋转后的坐标,可以使用旋转矩阵。给定一个点 (x, y) 绕原点逆时针旋转角度 θ 后的新坐标 (x’, y’) 计算公式如下:

x’ = x * cosθ - y * sinθ;
y’ = x * sinθ + y * cosθ;

如果我们是绕任意点而不是原点,需要先平移坐标系

  1. 平移: 将 (x, y) 平移到原点,新坐标为 (x - a, y - b);
  2. 旋转: 按照上述公式计算 (x’, y’);
  3. 平移回原坐标系: 新坐标为(x’ + a, y’ + b)。

基于上面的公式,我们更改热区点击判断方法:

/// 判断点击在什么区域String?_onDownZone(double x,double y){finaloffsetScale=rotatePoint(elementWidth,elementHeight);// 设置都是最大的顶点坐标,方便下面判断区域的方式结构一致// 后续就好抽取方法finaloffsetRotate=rotatePoint(elementWidth,rotateHeight);if(x>=offsetScale.dx-scaleWidth&&x<=offsetScale.dx&&y>=offsetScale.dy-scaleHeight&&y<=offsetScale.dy){returnstatusScale;}elseif(x>=offsetRotate.dx-rotateHeight&&x<=offsetRotate.dx&&y>=offsetRotate.dy-rotateHeight&&y<=offsetRotate.dy){returnstatusRotate;}elseif(x>=0&&x<=elementWidth&&y>=0&&y<=elementHeight){returnstatusMove;}returnnull;}/// 计算旋转后的点坐标OffsetrotatePoint(double x,double y){finaldeg=rotateNumber*pi/180;// 确定旋转中心,因为这里的拖动是单个元素,坐标都是相对于元素自身形成的坐标系,所以坐标中心始终都是元素的中心finalcenterX=elementWidth/2;finalcenterY=elementHeight/2;finaldiffX=x-centerX;finaldiffY=y-centerY;finaldx=diffX*cos(deg)-diffY*sin(deg)+centerX;finaldy=diffX*sin(deg)+diffY*cos(deg)+centerY;returnOffset(dx,dy);}

可以看到的是旋转和缩放热区即使在旋转后依然能够正常响应,还有最后一点,就是移动的时候也要应用旋转角度计算,因为我们使用的是元素自身为坐标系,坐标系旋转了,自然移动时的计算方式也得跟着变,其实对于后期将事件应用到容器上了过后就不需要考虑这些了,因为外层容器并不会变换,所以后期不使用逆运算,所以我们这里直接使用globalPosition来计算值即可(变换计算坐标感兴趣的可以自行研究一下):

void_onPanDown(DragDownDetails details){print('按下: $details');String?tempStatus=_onDownZone(details.localPosition.dx,details.localPosition.dy);print(tempStatus);setState((){if(tempStatus==statusMove){// 如果是移动,则使用globalPositionstartPosition=details.globalPosition;}else{startPosition=details.localPosition;}status=tempStatus;});}void_onPanUpdate(DragUpdateDetails details){print('更新: $details');if(status==statusMove){_onMove(details.globalPosition.dx,details.globalPosition.dy);}elseif(status==statusScale){_onScale(details.delta.dx,details.delta.dy);}elseif(status==statusRotate){_onRotate(details.localPosition.dx,details.localPosition.dy);}}

这样就对单个元素实现了变换的效果,前置就算时铺垫完成了,后续就开始实现多个的。

感兴趣的也可以关注我的微信公众号【前端学习小营地】,不定时会分享一些小功能~

今天的分享到此结束,感谢阅读~拜拜~

http://www.cnnetsun.cn/news/43148.html

相关文章:

  • 【面板数据】全球稀土贸易数据(2018-2024年)
  • 【后端】【Java】一文详解Spring Boot 统一日志与链路追踪实践
  • 无需运动恢复结构(SfM)的层级训练三维高斯溅射(3D Gaussian Splatting)
  • CS配合CrossC2插件,实现MacOS/Linux上线
  • 4、Puppet 入门:从基础使用到主从架构搭建
  • 线性代数(五)向量空间与子空间
  • matlab debug 调试程序
  • VibeVoice-Large-Q8:语音模型存储与性能的革命性突破——8位选择性量化技术深度解析
  • 腾讯开源双引擎AI模型:混元3D开创多模态创作新纪元,千倍效率革命重塑数字内容生产
  • Csharp学习笔记——常用类、集合框架、泛型、字典精华总结
  • 下载神器downkyi:5分钟掌握任务优先级管理技巧
  • 63.测试策略-领域模型测试集成测试实操方法-附测试框架选择
  • 1.2 主流大模型初探:解锁OpenAI、Gemini、Claude的强大能力
  • Ring-mini-linear-2.0:融合线性注意力与稀疏专家的下一代高效大语言模型
  • MFC消息处理机制
  • 商业级图像合成引擎6.0版本重磅发布:解锁跨场景视觉创作新范式
  • MyBatis-Plus与Spring整合(02--Service的代理)
  • 11、渗透测试实战:目标探索、利用与攻击行动
  • 16、攻击收尾:报告与撤离
  • 20、树莓派的替代项目探索
  • 事件查看器-事件ID
  • 单步出图革命:Consistency Model如何以100倍效率重构AI绘画产业格局
  • 搭建鸿蒙PC命令行适配环境测试hello程序
  • 编辑相似度(Edit Similarity):原理、演进与多模态扩展
  • 【深度解析】MiniCPM 2.0:端侧大模型的技术性进展与技术革新
  • ClickHouse 快速入门
  • 基于SpringBoot的人事管理系统设计与实现
  • 【论文阅读】Multi-modal Spatial Clustering for Spatial Transcriptomics Utilizing High-resolution Histology
  • Day36官方文档的阅读
  • Windows右键菜单终极优化指南:让你的右键菜单重获新生