做网站设计都需要什么软件,网站突然打不开,软件下载商店,网站设计需求分析报告Flutter 实现一个容器内部元素可平移、缩放和旋转等功能#xff08;三#xff09;
Flutter: 3.35.6
因为实现了单个的#xff0c;给出github链接#xff1a;https://github.com/yhtqw/FrontEndDemo
前面我们简单实现了元素的平移和缩放#xff0c;接下来我们继续实现旋转功…Flutter 实现一个容器内部元素可平移、缩放和旋转等功能三Flutter: 3.35.6因为实现了单个的给出github链接https://github.com/yhtqw/FrontEndDemo前面我们简单实现了元素的平移和缩放接下来我们继续实现旋转功能。元素的旋转会改变角度角度一变那么响应事件的热区也会跟着改变所以我们得提前考虑这些会因为角度改变而改变的地方。先来简单实现一下旋转先不考虑上述的热区问题。要实现旋转我们就得知道元素的旋转角度主要得出旋转的角度那么实现起来就比较简单所以简单使用数学的知识分析一下吧从我们这个需求中可以提取到的数据为按下点的坐标拖动时变换的坐标所以我们能否根据一个点的坐标计算出该点与某点形成的夹角好像刚好有个满足部分就是arctan2arctan2的主要作用是根据一个点的坐标计算出该点与坐标原点所形成的夹角主要作用如果我们要知道给出点与任意(x’, y’)形成的夹角呢前面的arctan2中将坐标原点换成任意某点不就行了使用 arctan2 计算两点连线的角度核心是计算两点之间的坐标差 (Δx, Δy)然后将其作为 arctan2 的参数。所以实现起来就比较简单了。其实这个实现的原理在很多地方都一样例如web端的元素拖动旋转也可以使用这个原理。实现的方式应该不止一种吧只要能计算出这个角度就行了。值得注意的是现在我们研究的是单个元素所以坐标系就是以元素自身形成的响应的事件也是在这个元素上等后期要实现多个坐标系就得以外层容器作为参考了。// 其他省略.../// 新增旋转状态热区字符串constString statusRotaterotate;/// 抽取响应旋转操作区域的大小finaldouble rotateWidth20;finaldouble rotateHeight20;/// 旋转角度double rotateNumber0;double initRotateNumber0;void_onPanUpdate(DragUpdateDetails details){print(更新: $details);if(statusstatusMove){_onMove(details.localPosition.dx,details.localPosition.dy);}elseif(statusstatusScale){_onScale(details.delta.dx,details.delta.dy);}elseif(statusstatusRotate){// 新增旋转热区的响应事件_onRotate(details.localPosition.dx,details.localPosition.dy);}}void_onPanEnd(){print(抬起或者因为某些原因并没有触发onPanDown事件);setState((){// 当次结束后重新记录也可以在按下时记录initXx;initYy;// 新增旋转角度的记录initRotateNumberrotateNumber;});}/// 处理旋转void_onRotate(double dx,double dy){/// 要计算点 (x, y) 与任意点 (x, y) 连线所成的角度可以使用 arctan2 函数。/// 关键在于将两点之间的相对坐标差作为 arctan2 的输入参数。/// 这里我们以元素的中心为旋转中心/// 利用上述方法计算起始点按下时与中心的连线组成的夹角为初始夹角/// 拖动的点与中心点连线组层的夹角为结束时的夹角/// 通过初始夹角与结束夹角计算旋转的角度// 确定旋转中心因为这里的拖动是单个元素坐标都是相对于元素自身形成的坐标系所以坐标中心始终都是元素的中心double centerXelementWidth/2;double centerYelementHeight/2;double diffStartXstartPosition.dx-centerX;double diffStartYstartPosition.dy-centerY;double diffEndXdx-centerX;double diffEndYdy-centerY;double angleStartatan2(diffStartY,diffStartX);double angleEndatan2(diffEndY,diffEndX);setState((){rotateNumberinitRotateNumberangleEnd-angleStart;});}/// 判断点击在什么区域String?_onDownZone(double x,double y){if(xelementWidth-scaleWidthxelementWidthyelementHeight-scaleHeightyelementHeight){returnstatusScale;}elseif(xelementWidth-rotateHeightxelementWidthy0yrotateHeight){// 固定右上角为旋转热区returnstatusRotate;}elseif(x0xelementWidthy0yelementHeight){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θ;如果我们是绕任意点而不是原点需要先平移坐标系平移: 将 (x, y) 平移到原点新坐标为 (x - a, y - b);旋转: 按照上述公式计算 (x’, y’);平移回原坐标系: 新坐标为(x’ a, y’ b)。基于上面的公式我们更改热区点击判断方法/// 判断点击在什么区域String?_onDownZone(double x,double y){finaloffsetScalerotatePoint(elementWidth,elementHeight);// 设置都是最大的顶点坐标方便下面判断区域的方式结构一致// 后续就好抽取方法finaloffsetRotaterotatePoint(elementWidth,rotateHeight);if(xoffsetScale.dx-scaleWidthxoffsetScale.dxyoffsetScale.dy-scaleHeightyoffsetScale.dy){returnstatusScale;}elseif(xoffsetRotate.dx-rotateHeightxoffsetRotate.dxyoffsetRotate.dy-rotateHeightyoffsetRotate.dy){returnstatusRotate;}elseif(x0xelementWidthy0yelementHeight){returnstatusMove;}returnnull;}/// 计算旋转后的点坐标OffsetrotatePoint(double x,double y){finaldegrotateNumber*pi/180;// 确定旋转中心因为这里的拖动是单个元素坐标都是相对于元素自身形成的坐标系所以坐标中心始终都是元素的中心finalcenterXelementWidth/2;finalcenterYelementHeight/2;finaldiffXx-centerX;finaldiffYy-centerY;finaldxdiffX*cos(deg)-diffY*sin(deg)centerX;finaldydiffX*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(tempStatusstatusMove){// 如果是移动则使用globalPositionstartPositiondetails.globalPosition;}else{startPositiondetails.localPosition;}statustempStatus;});}void_onPanUpdate(DragUpdateDetails details){print(更新: $details);if(statusstatusMove){_onMove(details.globalPosition.dx,details.globalPosition.dy);}elseif(statusstatusScale){_onScale(details.delta.dx,details.delta.dy);}elseif(statusstatusRotate){_onRotate(details.localPosition.dx,details.localPosition.dy);}}这样就对单个元素实现了变换的效果前置就算时铺垫完成了后续就开始实现多个的。感兴趣的也可以关注我的微信公众号【前端学习小营地】不定时会分享一些小功能今天的分享到此结束感谢阅读拜拜