news 2026/2/8 18:52:30

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flutter 实现一个容器内部元素可平移、缩放和旋转等功能(七)

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

Flutter: 3.35.7

前面我们抽取了区域的配置,主要实现了对内置区域的自定义,现在有个问题,如果是我们想自定义某个特定区域实现特定的效果,现在的数据都在部件的内部,如果不能提升到外部,那我们不能由外界传入貌似也没有什么用,因为功能需求都不一样,只有尽可能的以我现在的能力去进行封装。其实对于大多数的场景,修改内置的区域配置即可,我们就不需要考虑自定义这些,然而预留一些口子就可以极大地方便其他人使用。

这里就简单实现一下自定义区域,之前我们预留了自定义区域的fn,但那只是一个简单的占位,如果用户想自定义一些区域实现部分的功能,基本上就是对当前选中元素的操作,我们将可以给出的数据给到用户,让用户自行计算,并将计算完成的元素返回:

typedefAreaFunction=ElementModelFunction({/// 点击的坐标required Offset tapPoint,/// 选中的元素required ElementModel element,/// 容器的宽度required double containerWidth,/// 容器的高度required double containerHeight,/// 移动的坐标Offset?movePoint,});

接下来我们基于此实现一个简单的点击将元素移动到容器中心的功能,用这个例子来大概说明一下自定义区域的使用。我们之前实现了初始化区域配置的函数,现在加入自定义的区域,所以我们得对其进行改造:

/// 初始化响应区域void_initArea(){// 初始为配置里面定义的List<ResponseAreaModel>areaList=[...ConstantsConfig.baseAreaList];if(widget.areaConfigList!=null){// 将外界传递的配置合并for(varareainwidget.areaConfigList!){finalint index=areaList.indexWhere((item)=>item.status==area.status);// 如果是内置的区域,则修改配置if(index>-1){// 如果是不使用,则移除if(area.use==false){areaList.removeAt(index);}else{// 否则进行修改配置areaList[index].copyWith(xRatio:area.xRatio,yRatio:area.yRatio,icon:area.icon,fn:area.fn,);}}else{// 如果是自定义的区域,我们默认该有的参数是存在的areaList.add(ResponseAreaModel(areaWidth:ConstantsConfig.minSize,areaHeight:ConstantsConfig.minSize,xRatio:area.xRatio!,yRatio:area.yRatio!,trigger:area.trigger,icon:area.icon!,status:area.status,fn:area.fn,));}}}setState((){_areaList=areaList;});}

配置(已经让外界传入):

// 外界容器// 其他省略...late List<CustomAreaConfig>_customAreaList;@overridevoidinitState(){super.initState();_customAreaList=[// 不使用缩放区域CustomAreaConfig(status:ElementStatus.scale.value,use:false,),// 将旋转移到右下角CustomAreaConfig(status:ElementStatus.rotate.value,xRatio:1,yRatio:1,),// 测试自定义区域CustomAreaConfig(status:'center',xRatio:0,yRatio:1,icon:'assets/images/icon_center.png',trigger:TriggerMethod.down,fn:_centerFn,),];}/// 测试自定义区域的函数ElementModel_centerFn({/// 点击的坐标required Offset tapPoint,/// 选中的元素required ElementModel element,/// 容器的宽度required double containerWidth,/// 容器的高度required double containerHeight,/// 移动的坐标Offset?movePoint,}){returnElementModel(id:element.id,elementWidth:element.elementWidth,elementHeight:element.elementHeight,);}// 其他省略...Column(children:[Expanded(child:MultipleTransformContainer(// 传入配置areaConfigList:_customAreaList,),),],),

运行效果:

可以看到我们定义了左下角为自定义区域,其他内置的区域配置也生效了,接下来我们来实现这个区域的方法。在实现之前,我们得对按下事件函数、移动事件函数和更新属性函数进行逻辑新增,新增如果是点击的自定义区域,则响应自定义的方法,将必要参数传递过去:

/// 当前元素属性变化的时候更新列表中对应元素的属性////// 因为可能是触发用户的自定义区域,/// 所以如果是用户自定义的区域,则将对应元素的属性修改成用户计算后的元素属性void_onChange({ElementModel?data}){if(_currentElement==null||_temporary==null)return;finalElementModel?tempElement=data??_currentElement;for(vari=0;i<_elementList.length;i++){finalElementModel item=_elementList[i];if(item.id==tempElement?.id){_elementList[i]=item.copyWith(x:tempElement?.x,y:tempElement?.y,elementWidth:tempElement?.elementWidth,elementHeight:tempElement?.elementHeight,rotationAngle:tempElement?.rotationAngle,);setState((){});break;}}}/// 处理自定义事件////// 通过当前状态[status]来确定是否是自定义区域, 如果是,/// 则将按下坐标 [tapPoint], 移动坐标 [movePoint] (如果是移动状态),/// 和当前元素[element]传递过去用于自定义的计算void_onCustomFn({required ElementModel element,required Offset tapPoint,required String?status,Offset?movePoint,}){finalint index=_areaList.indexWhere((item)=>item.status==status);if(index>-1){finalResponseAreaModel item=_areaList[index];if(item.fn!=null){finalElementModel data=item.fn!(tapPoint:tapPoint,element:element,movePoint:movePoint,containerHeight:_containerHeight,containerWidth:_containerWidth,);_onChange(data:data);}}}/// 按下事件void_onPanDown(DragDownDetails details){// 其他省略...// 新增判断// 如果当前有选中的元素且和点击区域的currentElement是一个元素// 并且 temp 的 status对应的触发方式为点击,那么就响应对应的点击事件if(currentElement?.id==_currentElement?.id&&temp.trigger==TriggerMethod.down){finalFunction?fn=_onElementStatus(x:dx,y:dy)[temp.status];if(fn!=null){fn();}else{// final int index = _areaList.indexWhere((item) => item.status == temp.status);//// if (index > -1) {// final ResponseAreaModel item = _areaList[index];//// if (item.fn != null) {// final ElementModel data = item.fn!(// tapPoint: Offset(dx, dy),// element: currentElement!,// containerHeight: _containerHeight,// containerWidth: _containerWidth,// );// _onChange(data: data);// }// }// 新增处理自定义函数_onCustomFn(element:currentElement!,tapPoint:Offset(dx,dy),status:temp.status,);}if(temp.status==ElementStatus.deleteStatus.value){// 因为是删除,就置空选中,让下面代码执行最后的清除currentElement=null;}}// 其他省略...}/// 按下移动事件void_onPanUpdate(DragUpdateDetails details){// 其他省略...// if (_temporary?.trigger == TriggerMethod.move && fn != null) fn();if(_temporary?.trigger==TriggerMethod.move){if(fn!=null){fn();}else{// 新增处理自定义函数_onCustomFn(element:_currentElement!,tapPoint:_startPosition,movePoint:Offset(x,y),status:_temporary?.status,);}}}

接下来我们简单实现自定义区域的功能

/// 测试自定义区域的函数ElementModel_centerFn({/// 点击的坐标required Offset tapPoint,/// 选中的元素required ElementModel element,/// 容器的宽度required double containerWidth,/// 容器的高度required double containerHeight,/// 移动的坐标Offset?movePoint,}){// 计算容器中心finaldouble x=(containerWidth-element.elementWidth)/2;finaldouble y=(containerHeight-element.elementHeight)/2;returnElementModel(id:element.id,elementWidth:element.elementWidth,elementHeight:element.elementHeight,x:x,y:y,rotationAngle:element.rotationAngle,);}

运行效果:

这样我们就对自定义区域及事件做出了响应,虽然可能很少使用,但因为预留了这个口子而多了一丝灵活性。如果还需要实现其他自定义可能,可以按照类似的方式来实现。

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

好了,今天的分享就结束了,后续就开始实现容器属性的设置了,比如辅助线,比如撤销还原。感谢阅读~拜拜~

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/8 13:43:34

树莓派5安装ROS2配置步骤完整示例

树莓派5上从零搭建ROS2开发环境&#xff1a;实战配置全记录 最近在做一款自主移动机器人的原型开发&#xff0c;主控平台选用了刚入手的 树莓派5 。这颗小板子性能确实惊艳——四核A76、2.4GHz主频、支持NVMe SSD扩展&#xff0c;完全不像传统印象中“跑不动复杂系统”的树莓…

作者头像 李华
网站建设 2026/2/8 14:37:01

新手必看:Multisim14.0虚拟电源设置通俗解释

新手避坑指南&#xff1a;Multisim 14.0 虚拟电源设置全解析你有没有遇到过这种情况&#xff1f;辛辛苦苦画好了一个运放电路&#xff0c;信心满满地点下“运行仿真”&#xff0c;结果输出波形一片死寂——没有信号、没有响应&#xff0c;连万用表都测不到电压。别急&#xff0…

作者头像 李华
网站建设 2026/2/6 1:04:12

虚拟机中使用USB over Network:Win主机实战配置

虚拟机中如何“隔空”使用本地USB设备&#xff1f;一文讲透Windows主机实战配置 你有没有遇到过这种情况&#xff1a;手头有一台运行虚拟机的电脑&#xff0c;但真正需要的加密狗、U盾或测试仪器却插在另一台办公室主机上。想用&#xff1f;得跑过去拔下来&#xff0c;再插到自…

作者头像 李华
网站建设 2026/2/5 16:34:31

深度剖析ES6语法:Iterator遍历器工作原理

深度剖析 ES6 遍历器&#xff1a;从for...of到自定义迭代的底层逻辑你有没有想过&#xff0c;为什么 JavaScript 中数组可以用for...of遍历&#xff0c;而普通对象却不行&#xff1f;为什么像Map、Set甚至字符串都能被展开运算符...处理&#xff1f;这背后其实隐藏着一个统一的…

作者头像 李华
网站建设 2026/2/6 17:47:11

一文说清Vivado下载在Artix-7上的实现方法

一文讲透Vivado如何将设计下载到Artix-7 FPGA 你有没有遇到过这样的场景&#xff1a;在Vivado里辛辛苦苦写完代码、综合实现成功&#xff0c;结果点“Program Device”时却卡住——设备没识别&#xff1f;下载失败&#xff1f;烧录后无法启动&#xff1f;明明 .bit 文件生成了…

作者头像 李华
网站建设 2026/2/3 7:53:34

基于电路仿真circuits网页版的实验课设计:完整指南

用浏览器做电路实验&#xff1f;这款免费工具让电子教学彻底“轻”起来你有没有过这样的经历&#xff1a;一节讲戴维南等效的电路课&#xff0c;学生围在实验台前忙活半小时&#xff0c;最后发现电压读不对——不是理论错了&#xff0c;而是某个电阻焊反了&#xff0c;或者万用…

作者头像 李华