HarmonyOS 鸿蒙Next开发案例 | 在网格Grid中拖拽交换子组件位置

HarmonyOS 鸿蒙Next开发案例 | 在网格Grid中拖拽交换子组件位置

场景说明

在使用网格Grid的应用中,可以通过拖拽子组件GridItem的方式,交换子组件的显示位置。

效果呈现

本示例的最终效果如下:

GIF

运行环境

本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发:

  • IDE:DevEco Studio 3.1.1 Release
  • SDK:3.1.0(API 9)

实现原理

  1. 设置Grid的editMode属性为true,使Grid进入编辑模式,从而可以拖拽Grid组件内部GridItem。

  2. 在Grid的相关拖拽事件中进行拖拽逻辑处理:

    • 在onItemDragStart事件中显示拖拽过程中的图片,即被拖拽的GridItem。
    • 在onItemDrop事件中根据拖拽前后的位置,完成两个GridItem位置交换的逻辑。

开发步骤

  1. 构建Grid组件及子组件GridItem,开启Grid组件的editMode属性。
@Entry
@Component
struct Index {
  @State numbers: String[] = []
  scroller: Scroller = new Scroller()
  @State text: string = 'drag'

  // 拖拽过程中展示的样式
  @Builder pixelMapBuilder() {
    Column() {
      Text(this.text)
        .fontSize(16)
        .backgroundColor(0xF9CF93)
        .width(80)
        .height(80)
        .textAlign(TextAlign.Center)
    }
  }

  aboutToAppear() {
    for (let i = 1;i <= 15; i++) {
      this.numbers.push(i + '')
    }
  }

  // 交换数组中元素位置
  changeIndex(index1: number, index2: number) {
    [this.numbers[index1], this.numbers[index2]] = [this.numbers[index2], this.numbers[index1]];
  }

  build() {
    Column({ space: 5 }) {
      Grid(this.scroller) {
        ForEach(this.numbers, (day: string) => {
          GridItem() {
            Text(day)
              .fontSize(16)
              .backgroundColor(0xF9CF93)
              .width(80)
              .height(80)
              .textAlign(TextAlign.Center)
              .onTouch((event: TouchEvent) => {
                if (event.type === TouchType.Down) {
                  this.text = day
                }
              })
          }
        })
      }
      .columnsTemplate('1fr 1fr 1fr')
      .columnsGap(10)
      .rowsGap(10)
      .onScrollIndex((first: number) => {
        console.info(first.toString())
      })
      .width('90%')
      .backgroundColor(0xFAEEE0)
      .height('100%')
      // 设置Grid是否进入编辑模式,进入编辑模式可以拖拽Grid组件内部GridItem
      .editMode(true)
      // 第一次拖拽此事件绑定的组件时,触发回调
      .onItemDragStart((event: ItemDragInfo, itemIndex: number) => {
        // 设置拖拽过程中显示的图片
        return this.pixelMapBuilder()
      })
      // 绑定此事件的组件可作为拖拽释放目标,当在本组件范围内停止拖拽行为时,触发回调
      // itemIndex为拖拽起始位置,insertIndex为拖拽插入位置
      .onItemDrop((event: ItemDragInfo, itemIndex: number, insertIndex: number, isSuccess: boolean) => {
        // 不支持拖拽到已有内容以外的位置
        if(insertIndex < this.numbers.length){
          this.changeIndex(itemIndex, insertIndex)
        }
      })
    }.width('100%').margin({ top: 5 })
  }
}
  1. 当长按GridItem时触发onItemDragStart事件,在该事件中提供被拖拽GridItem的显示逻辑。

  2. 停止拖拽时触发onItemDrop事件,在该事件中完成两个GridItem位置交换的逻辑。


更多关于HarmonyOS 鸿蒙Next开发案例 | 在网格Grid中拖拽交换子组件位置的实战教程也可以访问 https://www.itying.com/category-93-b0.html

9 回复

拖拽太生硬了,怎么添加动画效果?类似于下图会自动回填。

更多关于HarmonyOS 鸿蒙Next开发案例 | 在网格Grid中拖拽交换子组件位置的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


这种移位动效和抖动动效如何实现?试了属性动画貌似没移位效果,

有要学HarmonyOS AI的同学吗,联系我:https://www.itying.com/goods-1206.html

您好,这个有实现方案吗?

华为开发提供demo,测试可以。开发者您好,请参考以下demo:

import curves from '@ohos.curves';

@Entry
@Component
struct Index {
  @State numbers: String[] = ["娱乐", "关注", "热榜", "航天", "动漫", "宠物", "保险", "NBA", "汽车", "财经", "体育", "女性", "搞笑", "军事", "有料", "北京", "深度", "5G"]
  scroller: Scroller = new Scroller()
  @State text: string = 'drag'
  @State isShowDelete: boolean = false
  @State isEdit: boolean = false
  @State rotateZ: number = 0;

  private stopJump() {
    animateTo({
      delay: 0,
      tempo: 5,
      duration: 0,
      curve: Curve.Smooth,
      playMode: PlayMode.Normal,
      iterations: 1
    },
    () => {
      this.rotateZ = 0;
    })
  }

  //抖动的动画
  private jumpWithSpeed(speed: number) {
    if (this.isEdit) {
      this.rotateZ = -1;
      animateTo({
        delay: 0, //延时播放
        tempo: speed, //动画的播放速度,值越大动画播放越快
        duration: 1000, //动画持续的时间
        curve: Curve.Smooth, //动画曲线
        playMode: PlayMode.Normal, //动画播放模式
        iterations: -1
      },
      () => {
        this.rotateZ = 1;
      })
    } else {
      this.stopJump()
    }
  }

  //拖拽过程中展示的样式
  @Builder
  pixelMapBuilder() {
    Column() {
      Text(this.text)
        .fontSize(16)
        .backgroundColor(0xF9CF93)
        .width(80)
        .height(40)
        .textAlign(TextAlign.Center)
        .borderRadius(20)
        .borderWidth(2)
        .borderColor(Color.Orange)
  }

  //改变数组中元素位置
  changeIndex(index1: number, index2: number) {
    this.numbers.splice(index2, 0, this.numbers.splice(index1, 1)[0])
  }

  build() {
    Column({ space: 5 }) {
      Row({ space: 30 }) {
        Text("我的频道")
          .fontSize(20)
        Text(this.isEdit ? "长按拖动调整顺序" : "点击编辑按钮进入编辑")
          .fontColor(Color.Gray)
        Button() {
          Text(this.isEdit ? "完成" : "编辑")
            .fontColor(this.isEdit ? Color.Black : 0xF9CF93)
        }.width(80)
        .height(30)
        .backgroundColor(Color.White)
        .onClick(() => {
          this.isEdit = !this.isEdit
          this.jumpWithSpeed(5)
        })
      }.padding({ left: 15 })

      Grid(this.scroller) {
        ForEach(this.numbers, (day: string, index: number) => {
          GridItem() {
            Stack({ alignContent: Alignment.TopEnd }) {
              Text(day)
                .borderRadius(20)
                .borderWidth(2)
                .borderColor(Color.Orange)
                .fontSize(16)
                .backgroundColor(0xF9CF93)
                .width(80)
                .height(40)
                .textAlign(TextAlign.Center)
                .onTouch((event: TouchEvent) => {
                  if (event.type === TouchType.Down) {
                    this.text = day
                  }
                })
              if (this.isEdit) {
                Image($r('app.media.close'))
                  .width(20)
                  .height(20)
                  .onClick(() => {
                    animateTo({ duration: 300 }, () => {
                      this.numbers.splice(index, 1)
                    })
                    this.stopJump()
                    this.jumpWithSpeed(5)
                  })
              }
            }
            .rotate({ z: this.rotateZ,
              angle: 0.4,
              centerX: 0.5,
              centerY: 0.5
            })

          }
          .transition({ type: TransitionType.All, translate: { x: 100 }, scale: { x: 1, y: 1 } })
          .padding({ top: 15 })

        })
      }
      .columnsTemplate('1fr 1fr 1fr')
      .columnsGap(10)
      .rowsGap(10)
      .onScrollIndex((first: number) => {
        console.info(first.toString())
      })
      .margin({ top: 5 })
      .width('100%')
      .backgroundColor(0xFAEEE0)
      .height('100%')
      .supportAnimation(true) //是否支持动画
      //设置Grid是否进入编辑模式,进入编辑模式可以拖拽Grid组件内部GridItem
      .editMode(this.isEdit)
      //第一次拖拽此事件绑定的组件时,触发回调
      .onItemDragStart((event: ItemDragInfo, itemIndex: number) => {
        //设置拖拽过程中显示的图片
        return this.pixelMapBuilder()
      })
      //绑定此事件的组件可作为拖拽释放目标,当在本组件范围内停止拖拽行为时,触发回调
      //itemIndex为拖拽起始位置,insertIndex为拖拽插入位置
      .onItemDrop((event: ItemDragInfo, itemIndex: number, insertIndex: number, isSuccess: boolean) => {
        //不支持拖拽到已有内容以外的位置
        if (insertIndex < this.numbers.length) {
          this.changeIndex(itemIndex, insertIndex)
          this.stopJump()
          this.jumpWithSpeed(5)
        }
      })
    }.width('100%')
    .margin({ top: 5 })
    .alignItems(HorizontalAlign.End)
  }
}

有图片的拖动 demo 嘛

图片拖动解决了吗?貌似图片不能拖动,

在HarmonyOS鸿蒙Next开发框架中,若要在网格(Grid)布局中实现拖拽交换子组件位置的功能,可以通过以下方式实现:

首先,需要利用鸿蒙提供的触摸事件监听机制。为每个子组件添加触摸监听器,监听用户的拖拽动作。这通常包括按下(ACTION_DOWN)、移动(ACTION_MOVE)和抬起(ACTION_UP)事件。

在按下事件中,记录拖拽开始的位置,并设置一个标志位表示拖拽开始。在移动事件中,根据触摸点的变化,实时更新拖拽组件的位置。同时,需要检测拖拽组件是否与其他组件重叠,若重叠,则准备交换它们的位置。

在抬起事件中,根据拖拽路径和最终位置,完成组件位置的交换,并清除拖拽标志位。

此外,为了实现平滑的拖拽效果,可能需要对拖拽组件进行视觉上的反馈,如改变透明度或添加阴影。同时,在拖拽过程中,其他组件可能需要暂时禁用点击事件,以避免冲突。

需要注意的是,拖拽交换功能可能涉及复杂的布局计算和事件处理,开发者需确保逻辑的正确性和性能的优化。

如果问题依旧没法解决请联系官网客服,官网地址是 https://www.itying.com/category-93-b0.html

回到顶部