DEV Community

Cover image for 鸿蒙开发:实现一个超简单的网格拖拽
程序员一鸣
程序员一鸣

Posted on

鸿蒙开发:实现一个超简单的网格拖拽

前言

本文基于Api12

网格拖拽,此功能很是常见,一般用于频道的编辑或者条目顺序的排列,在鸿蒙的开发中,针对网格的编辑,系统也给出了相关的Api,通过onItemDragStart和在onItemDrop即可轻松实现,onItemDragStart用于设置拖拽过程中的显示,onItemDrop是进行数据交换逻辑处理。

根据官方提供,我们随便实现了一个简单的拖拽效果:

@Entry
@Component
struct Index {
  @State numbers: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

  @Builder
  itemLayout(text: string) {
    Text(text)
      .fontSize(16)
      .backgroundColor(Color.Pink)
      .width(80)
      .height(80)
      .textAlign(TextAlign.Center)
  }

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

  build() {
    Column() {
      Grid() {
        ForEach(this.numbers, (n: number) => {
          GridItem() {
            this.itemLayout(n.toString())
          }
        })
      }
      .columnsTemplate('1fr 1fr 1fr')
      .columnsGap(10)
      .rowsGap(10)
      .editMode(true) //设置Grid是否进入编辑模式
      .onItemDragStart((event: ItemDragInfo, itemIndex: number) => { //第一次拖拽此事件绑定的组件时,触发回调。
        return this.itemLayout(this.numbers[itemIndex].toString()) //设置拖拽过程中显示
      })
      .onItemDrop((event: ItemDragInfo, itemIndex: number, insertIndex: number,
        isSuccess: boolean) => {
        if (!isSuccess || insertIndex >= this.numbers.length) {
          return
        }
        this.changeIndex(itemIndex, insertIndex)
      })
    }.width('100%')
    .height("100%")
  }
}
Enter fullscreen mode Exit fullscreen mode

Image description

以上代码我们就很简单的实现了一个网格拖拽,实际运行之后,长按确实发生了拖拽,和对应的item也进行了交换,但是并没有一个移动交换的过程,这是因为没有设置拖拽动画方法,如果想要拖拽过程中有移动动画,只需要设置supportAnimation为true即可,除此之外,还有一个非常重要的属性,那就是editMode,为true即为编辑模式,false即不可编辑。

拖拽的代码很是简单,毕竟官方也给出了相关案例,其中有两个点是未给出的,那就是禁止拖拽,和禁止与其交换。

比如,第一个和第二个条目,不能拖拽,如何进行设置呢?再比如,任何条目都不能和第一个进行交换,又该如何设置呢?

禁止拖拽

还是上述的代码,例如,把第一个条目禁止,不让其执行拖拽,实现起来很是简单,在onItemDragStart方法里,如果触摸的是指定索引,不让其执行即可。

     .onItemDragStart((event: ItemDragInfo, itemIndex: number) => { 
            //第一次拖拽此事件绑定的组件时,触发回调。
            if (itemIndex == 0) {
              //禁止拖拽
              return
            }
            return this.itemLayout(this.numbers[itemIndex].toString()) //设置拖拽过程中显示
          })
Enter fullscreen mode Exit fullscreen mode

需要注意的是,以上的代码仅仅是做到了禁止拖拽,但是,有一个潜在的问题是,别的条目是可以和它进行交换的,交换过之后,由于它的索引发生了变化,就变得可以拖拽了,如果想实现真正的,即便被交换后也不能拖拽,那就不能判断索引了,可以以唯一值进行判断,比如item数据换成对象,在对象里定义唯一值。

禁止交换

实际的开发中,除了某个条目禁止拖拽之外,也有不能和它进行交换的逻辑,比如第一个条目,就是固定的,不仅仅禁止拖拽,也不能和其进行交换,这种情况下如何进行实现呢?

 onItemDrop((event: ItemDragInfo, itemIndex: number, insertIndex: number,
            isSuccess: boolean) => {
            //第一个禁止交换
            if (insertIndex <= 0) {
              return
            }
            if (!isSuccess || insertIndex >= this.numbers.length) {
              return
            }
            this.changeIndex(itemIndex, insertIndex)
          })
Enter fullscreen mode Exit fullscreen mode

很是简单,如果是第一个,直接不执行即可,以上的代码虽然实现了禁止交换,但是有一个问题,那就是实际的动画已经执行了,只是最后的交换没有执行而已,在视觉上仍然存在很大的问题。

Image description

我们想要的效果是,保留移动拖拽的动画,只是拖拽到第一个时,动画不执行,其他的该怎么执行就怎么执行。

那么,在设置supportAnimation动画的时候,就不能直接设置为true了,而是移动到条目一的位置后,设置为fasle,其他再设置为true。

如何拿到第一个item的坐标呢,其实每一个组件都有一个方法,onAreaChange,通过这个属性,我们就可以拿到任何一个组件的宽高还有,xy的坐标,当然,你也可以通过计算的方式,毕竟,屏幕的宽高,还有条目的宽高你都知道。

手势移动的坐标可以通过onTouch方法进行获取,在Move事件中进行判断,如果移动到了条目一的范围之内,取消动画,否则就执行动画。

Image description

简单实现

目前呢这个网格拖拽功能已经做了一层封装,放到了refresh库中,大家如果想直接使用,可以依赖这个库:

    "dependencies": { "@abner/refresh": "^1.3.6"}
Enter fullscreen mode Exit fullscreen mode

简单案例如下:

 import { GridDropView } from '@abner/refresh'

    @Entry
    @Component
    struct Index {
      @State numbers: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

      @Builder
      itemLayout(item: Object, _: number) {
        Text(item.toString())
          .fontSize(16)
          .backgroundColor(Color.Pink)
          .width(80)
          .height(80)
          .textAlign(TextAlign.Center)
      }



      build() {
        Column() {
          GridDropView({
            columnsTemplate:"1fr 1fr 1fr",
            items: this.numbers,
            itemLayout: this.itemLayout,
            dropLayout: this.itemLayout,
            rowsGap: 10,
            columnsGap: 10,
            isEditMode: true,
            prohibitDrop: [0, 1], //禁止拖拽的索引
            prohibitMaxSwap: 0, //禁止交换
            onDropData: (items) => {
              console.log("拖拽结束:" + JSON.stringify(items))
            }
          })
        }.width('100%')
        .height("100%")

      }
    }
Enter fullscreen mode Exit fullscreen mode

注意事项

实现拖拽,最重要的三个方法就是,打开编辑状态editMode,实现onItemDragStart和onItemDrop,设置拖拽移动动画和交换数据,如果想到开启补位动画,还需要实现supportAnimation方法。

AWS Q Developer image

Your AI Code Assistant

Automate your code reviews. Catch bugs before your coworkers. Fix security issues in your code. Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Get started free in your IDE

Top comments (0)

AWS GenAI LIVE image

How is generative AI increasing efficiency?

Join AWS GenAI LIVE! to find out how gen AI is reshaping productivity, streamlining processes, and driving innovation.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay