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,);}运行效果:
这样我们就对自定义区域及事件做出了响应,虽然可能很少使用,但因为预留了这个口子而多了一丝灵活性。如果还需要实现其他自定义可能,可以按照类似的方式来实现。
感兴趣的也可以关注我的微信公众号【前端学习小营地】,不定时会分享一些小功能~
好了,今天的分享就结束了,后续就开始实现容器属性的设置了,比如辅助线,比如撤销还原。感谢阅读~拜拜~