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

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,);}

运行效果:

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

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

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

http://www.zskr.cn/news/138076.html

相关文章:

  • 使用libiconv-win-build在Windows平台下编译libiconv
  • 新手必看:Multisim14.0虚拟电源设置通俗解释
  • 驱动开发中WinDbg分析DMP蓝屏文件的完整指南
  • 基于minidump的系统崩溃分析:手把手教程
  • Packet Tracer汉化完整指南:适用于初学者的配置流程
  • 数据库性能优化实战:从工程架构到SQL深度调优的全面指南
  • 分布式搜索ES面试题精讲:实战案例
  • Babel中实现ES6函数扩展的深度剖析
  • 零基础也能懂的ESP32连接阿里云MQTT讲解
  • 一文说清Vivado下载在Artix-7上的实现方法
  • 工业自动化设备PCB布线可制造性设计:DFM实践指南
  • Flutter AR 开发:打造厘米级精度的室内导航应用
  • 项目超编与人力如何优化处理
  • GlcNAc beta(1-3)GalNAc-alpha-Thr—糖肽研究与治疗的关键糖基化结构单元 CAS号: 126740-76-9
  • 小程序springboot新能源汽车4S店试驾平台_i3v8mexl
  • 小程序springboot校园学生宿舍报修管理系统_th4x9yos
  • 小程序springboot校园智能垃圾分类回收预约平台_myez9h59
  • Gemini vs GPT-4 vs Claude免费额度对比
  • 幽冥大陆(六十) SmolVLM 本地部署 轻量 AI 方案—东方仙盟筑基期
  • Kafka 消费者的负载均衡在大数据中的实现
  • ModbusRTU报文结构完整指南(主从模式)
  • 一文说清Batocera游戏整合包的ROM目录结构与规范
  • NetActuate扩建丹佛数据中心提升混合云与灾备能力
  • LC.173 | 二叉搜索树迭代器 | 树 | 中序展开/栈模拟
  • Java计算机毕设之基于springboot的旧物回收商城系统的设计与实现基于Springboot的旧物置换网站实现(完整前后端代码+说明文档+LW,调试定制等)
  • 基于Springboot企业进销存管理系统【附源码+文档】
  • 工业现场总线替代方案:SerialPort技术可行性分析
  • 专用蚊子苍蝇检测数据集(含背景样本):适用于目标检测任务
  • OpenMV识别物体结合WiFi传输的安防监控:项目实践
  • c++进程池(Linux)的实现(2025.12.22)