鸿蒙6.0应用开发——Grid网格元素拖拽交换

张开发
2026/4/14 6:45:33 15 分钟阅读

分享文章

鸿蒙6.0应用开发——Grid网格元素拖拽交换
【高心星出品】文章目录Grid网格元素拖拽交换概述实现原理关键技术开发流程相同大小网格元素长按拖拽场景描述开发步骤网格元素长按后显示抖动动画场景描述开发步骤Grid网格元素拖拽交换概述Grid网格元素拖拽交换功能在应用中经常会被使用如当编辑九宫格图片需要拖拽图片改变排序时就会使用到该功能。当网格中图片进行拖拽交换时元素排列会跟随图片拖拽的位置而发生变化并且会有对应的动画效果以达到良好的用户体验。Grid网格布局一般由Grid容器组件和子组件GridItem构建组成Grid用于设置网格布局相关参数GridItem定义子组件相关特征。网格布局中含有网格元素当给Grid容器组件设置editMode属性为true时可开启Grid组件的编辑模式。首先开启编辑模式。然后给GridItem组件绑定长按、拖拽等手势。最后需要添加动画属性animateTo并设置相应的动画效果。最终呈现出网格元素拖拽交换的动效过程如下示意图。实现原理关键技术Grid网格元素拖拽交换功能实现是通过Grid容器组件、组合手势、动画属性animateTo结合来实现的。Grid组件可以构建网格元素布局。组合手势可以实现元素拖拽交换的效果。显式动画可以给元素拖拽交换的过程中添加动画效果。Grid组件当前支持GridItem拖拽动画通过给Grid容器组件设置supportAnimation为true即可开启动画效果。但仅支持在滚动模式下设置rowsTemplate、columnsTemplate其中一个支持动画。且仅在大小规则的Grid中支持拖拽动画跨行或跨列场景不支持。因此在跨行或跨列场景下需要通过自定义Grid布局、自定义手势和显式动画来实现拖拽交换的效果。开发流程在需要拖拽交换的场景中实现Grid布局启动editMode编辑模式进入编辑模式可以拖拽Grid组件内部GridItem。给网格元素GridItem绑定相关手势实现可拖拽操作。使用显式动画animateTo实现GridItem拖拽过程中的动画效果。相同大小网格元素长按拖拽场景描述在编辑九宫格等多图的场景中长按图片网格元素可以拖拽交换排序拖拽图片的过程中旁边的图片也会即时移动以产生新的宫格排布。示意效果图如下。开发步骤Grid布局及相同大小的GridItem界面开发。其中scrollBar可设置滚动条状态值为BarState.Off时表示不显示滚动条columnsTemplate可设置当前网格布局列的数量、固定列宽或最小列宽值columnsGap可设置列与列的间距rowsGap可设置行与行的间距。Grid(){ForEach(this.numbers,(item:number){GridItem(){Image($r(app.media.image${item})).width(100%).height(this.curBpmd?131:105).draggable(false).animation({curve:Curve.Sharp,duration:300})}},(item:number)item.toString())}.width(this.curBpmd?66%:100%).scrollBar(BarState.Off).columnsTemplate(1fr 1fr 1fr).columnsGap(this.curBpmd?6:4).rowsGap(this.curBpmd?6:4).height(this.curBpmd?406:323)代码逻辑走读网格布局定义使用Grid()定义一个网格布局容器。循环生成网格项通过ForEach循环遍历this.numbers数组为每个数字创建一个GridItem。图片组件定义在每个GridItem中使用Image组件加载图片图片的资源路径由$r(app.media.image${item})生成其中item是当前循环的数字。图片属性设置设置图片的宽度为100%高度根据this.curBp的值动态调整不可拖动且动画持续时间为300毫秒。网格属性设置设置网格的宽度、滚动条状态、列模板、列间距、行间距和高度这些属性值也根据this.curBp的值动态调整。给Grid组件设置editMode为true即Grid进入编辑模式进入编辑模式可以拖拽Grid组件内部GridItem。设置supportAnimation为true即Grid拖拽元素时支持动画。.editMode(true).supportAnimation(true)代码逻辑走读调用.editMode(true)方法启用编辑模式这意味着用户可以对当前界面进行编辑操作。调用.supportAnimation(true)方法启用动画效果支持界面元素可以执行动画展示。定义拖拽过程中的数组交换逻辑。changeIndex(index1:number,index2:number){lettmpthis.numbers.splice(index1,1);this.numbers.splice(index2,0,tmp[0])}代码逻辑走读定义changeIndex方法接受两个参数index1和index2。使用splice方法从this.numbers数组中移除位于index1位置的元素并将该元素存储在变量tmp中。使用splice方法在this.numbers数组的index2位置插入tmp数组中的第一个元素即原index1位置的元素。给Grid组件绑定onItemDragStart和onItemDrop事件在onItemDragStart回调中设置拖拽过程中显示的图片并在onItemDrop中完成交换数组位置的逻辑。onItemDragStart回调在开始拖拽网格元素时触发onItemDrop回调当在网格元素内停止拖拽时触发。.onItemDragStart((_,itemIndex:number){this.imageNumthis.numbers[itemIndex];returnthis.pixelMapBuilder();}).onItemDrop((_,itemIndex:number,insertIndex:number,isSuccess:boolean){if(!isSuccess||insertIndexthis.numbers.length){return;}this.changeIndex(itemIndex,insertIndex);})代码逻辑走读拖拽开始事件处理当用户开始拖拽列表项时.onItemDragStart回调函数被触发。通过itemIndex获取当前拖拽项对应的数字并更新this.imageNum。调用this.pixelMapBuilder()方法构建或更新像素图。拖拽结束事件处理当用户释放拖拽时.onItemDrop回调函数被触发。检查isSuccess是否为false或insertIndex是否超出this.numbers的长度。如果条件满足则直接返回不进行后续操作。如果拖拽成功且插入位置有效调用this.changeIndex(itemIndex, insertIndex)方法更新列表项的索引位置。网格元素长按后显示抖动动画场景描述在设备列表页面时如果想要移除设备在选中设备并长按后可对网格元素进行编辑。此时设备图片会有抖动的效果。示意效果图如下。开发步骤使用Grid布局及GridItem界面开发。Grid(){ForEach(this.numbers,(item:number){GridItem(){Stack({alignContent:Alignment.TopEnd}){Column(){Image($r(app.media.space${item})).width(44).height(44).draggable(false)Image($r(app.media.space_bottom)).width(16).height(16).draggable(false)}.width(100%).height(73).justifyContent(FlexAlign.Center).borderRadius(10).backgroundColor(#F1F3F5).animation({curve:Curve.Sharp,duration:300}).onClick((){return;})if(this.isEdit){Image($r(app.media.close)).width(20).height(20).objectFit(ImageFit.Contain).draggable(false).position({x:this.isFoldAblethis.foldStatus2?60:this.isFoldAblethis.foldStatus1?86:70,y:-8}).onClick((){this.getUIContext().animateTo({duration:300},(){this.numbersthis.numbers.filter((element)element!item);})})}}}.rotate({z:this.rotateZ,angle:1,centerX:50%,centerY:50%}).width(100%).zIndex(this.dragItemitem?1:0).translate(this.dragItemitem?{x:this.offsetX,y:this.offsetY}:{x:0,y:0})// ...},(item:number)item.toString())}.width(100%).height(100%).editMode(true).clip(false).scrollBar(BarState.Off).columnsTemplate(this.curBpmd?1fr 1fr 1fr 1fr 1fr:1fr 1fr 1fr 1fr).columnsGap(12).rowsGap(12).margin({top:5})代码逻辑走读Grid布局初始化使用Grid()组件初始化一个网格布局设置其宽度和高度为100%启用编辑模式并禁用滚动条。根据当前的断点设置列模板以适应不同的屏幕尺寸。数据遍历与渲染使用ForEach循环遍历this.numbers数组为每个数字创建一个GridItem。每个GridItem包含一个Stack组件用于堆叠内容。Stack组件构建在Stack中首先创建一个Column组件包含两个Image组件分别用于显示图标和底部图标。设置Column的宽度、高度、对齐方式、背景颜色、边框圆角和动画效果。编辑模式下的删除功能如果处于编辑模式this.isEdit为真在Stack中添加一个Image组件作为关闭按钮。设置关闭按钮的位置和点击事件点击后通过animateTo动画和filter方法从this.numbers中移除当前项。旋转和拖拽功能为每个GridItem设置旋转和拖拽属性根据this.dragItem和this.offsetX、this.offsetY动态调整位置。设置GridItem的zIndex确保拖拽时的层级关系。整体布局调整设置网格的列间距和行间距以及顶部外边距以完成整体布局的美化。添加抖动动画。privatejumpWithSpeed(speed:number){if(this.isEdit){this.rotateZ-1;this.getUIContext().animateTo({delay:0,tempo:speed,duration:1000,curve:Curve.Smooth,playMode:PlayMode.Normal,iterations:-1},(){this.rotateZ1;})}else{this.stopJump();}}代码逻辑走读方法定义定义了一个名为jumpWithSpeed的私有方法该方法接受一个speed参数类型为number。条件判断如果this.isEdit为true则执行动画逻辑。如果this.isEdit为false则调用stopJump方法停止跳跃。动画逻辑设置this.rotateZ为-1表示动画开始。调用getUIContext().animateTo方法配置动画参数delay: 0动画立即开始。tempo: speed使用传入的speed作为动画速度。duration: 1000动画持续时间为 1000 毫秒1秒。curve: Curve.Smooth使用平滑的动画曲线。playMode: PlayMode.Normal动画以正常模式播放。iterations: -1动画无限循环。在动画完成后将this.rotateZ设置为1表示动画结束。定义stopJump()方法执行后能使网格元素停止抖动。private stopJump() { this.getUIContext().animateTo({ delay: 0, tempo: 5, duration: 0, curve: Curve.Smooth, playMode: PlayMode.Normal, iterations: 1 }, () { this.rotateZ 0; }) }代码逻辑走读定义一个私有方法stopJump用于停止UI元素的动画。调用getUIContext()获取当前UI上下文。使用animateTo方法配置动画参数delay: 0动画立即开始没有延迟。tempo: 5设置动画的速度为5。duration: 0动画持续时间为0表示动画立即完成。curve: Curve.Smooth使用平滑的曲线类型。playMode: PlayMode.Normal动画播放模式为正常模式。iterations: 1动画迭代次数为1表示动画只执行一次。在动画执行完成后通过回调函数将rotateZ设置为0重置元素的旋转角度从而停止动画效果。GridItem绑定组合手势长按、拖拽。并在手势的回调函数中设置显式动画。.gesture(GestureGroup(GestureMode.Sequence,LongPressGesture({repeat:true}).onAction((){if(!this.isEdit){this.isEdittrue;this.jumpWithSpeed(5);}}),PanGesture({fingers:1,direction:null,distance:0}).onActionStart((){this.dragItemitem;this.dragRefOffSetX0;this.dragRefOffSetY0;}).onActionUpdate((event:GestureEvent){this.offsetXevent.offsetX-this.dragRefOffSetX;this.offsetYevent.offsetY-this.dragRefOffSetY;this.getUIContext().animateTo({curve:curves.interpolatingSpring(0,1,400,38)},(){let indexthis.numbers.indexOf(this.dragItem);if(this.curBpmd){if(this.offsetXthis.FIX_VP_X/2(this.offsetY50this.offsetY-50)![4].includes(index)){this.right(index);this.stopJump();this.jumpWithSpeed(5);}elseif(this.offsetX-this.FIX_VP_X/2(this.offsetY50this.offsetY-50)){this.left(index);this.stopJump();this.jumpWithSpeed(5);}}else{if(this.offsetYthis.FIX_VP_Y/2(this.offsetX44this.offsetX-44)[...this.downArr].includes(index)){this.down(index);this.stopJump();this.jumpWithSpeed(5);}elseif(this.offsetY-this.FIX_VP_Y/2(this.offsetX44this.offsetX-44)){this.up(index);this.stopJump();this.jumpWithSpeed(5);}elseif(this.offsetXthis.FIX_VP_X/2(this.offsetY50this.offsetY-50)![...this.rightArr].includes(index)){this.right(index);this.stopJump();this.jumpWithSpeed(5);}elseif(this.offsetX-this.FIX_VP_Y/2(this.offsetY50this.offsetY-50)![...this.leftArr].includes(index)){this.left(index);this.stopJump();this.jumpWithSpeed(5);}}})}).onActionEnd((){this.getUIContext().animateTo({curve:curves.interpolatingSpring(0,1,400,38)},(){this.dragItem-1;})})))代码逻辑走读长按手势配置使用LongPressGesture配置长按手势设置repeat: true表示长按可以重复触发。当长按动作发生时检查this.isEdit是否为false如果是则设置this.isEdit为true并调用this.jumpWithSpeed(5)方法。拖拽手势配置使用PanGesture配置拖拽手势限制为单指拖拽不限制方向和距离。拖拽开始时记录当前拖拽的元素this.dragItem并初始化偏移量this.dragRefOffSetX和this.dragRefOffSetY。拖拽过程中根据手势事件的偏移量更新元素的offsetX和offsetY并调用this.getUIContext().animateTo方法执行动画效果。在动画回调中根据当前的布局尺寸this.curBp和偏移量判断拖拽方向执行相应的移动操作如this.right(index)、this.left(index)、this.up(index)、this.down(index)并在特定条件下调用this.stopJump()和this.jumpWithSpeed(5)。拖拽不限制方向和距离。拖拽开始时记录当前拖拽的元素this.dragItem并初始化偏移量this.dragRefOffSetX和this.dragRefOffSetY。拖拽过程中根据手势事件的偏移量更新元素的offsetX和offsetY并调用this.getUIContext().animateTo方法执行动画效果。在动画回调中根据当前的布局尺寸this.curBp和偏移量判断拖拽方向执行相应的移动操作如this.right(index)、this.left(index)、this.up(index)、this.down(index)并在特定条件下调用this.stopJump()和this.jumpWithSpeed(5)。拖拽结束时重置拖拽状态调用this.getUIContext().animateTo方法执行动画效果并将this.dragItem重置为-1。

更多文章