HarmonyOS鸿蒙Next中使用List组件实现拖拽排序

HarmonyOS鸿蒙Next中使用List组件实现拖拽排序

如何使用List组件实现一个常用的拖拽排序?

3 回复

实现思路

1、创建一个具名的 class来定义列表项的数据结构。

2、使用 @State 管理一个 CourseItem 类型的数组。

3、使用 List 和 ForEach 渲染列表,ForEach 的 keyGenerator 使用模型实例的唯一ID。

4、手势处理:为每个 ListItem 绑定 PanGesture,在 onActionUpdate 中计算目标位置,在 onActionEnd 中交换数组中的 CourseItem 实例。

5、通过 @State 变量控制被拖拽项和目标位置的样式,并使用 translate 和 animation 实现平滑的动画效果。

使用场景

1、课程管理应用中的学习顺序调整。

2、团队协作工具中的任务优先级排序。

3、灌溉系统中的灌溉顺序排序

等等。

实现效果

长按模块即可拖动。

cke_8993.png

完整代码

class CourseItem {
  public id: number;
  public text: string;

  constructor(id: number, text: string) {
    this.id = id;
    this.text = text;
  }
}

@Entry
@Component
struct DraggableListDemo {
  // 使用定义好的 CourseItem class 作为数组元素的类型
  [@State](/user/State) listItems: Array<CourseItem> = [
    new CourseItem(1, '鸿蒙开发基础'),
    new CourseItem(2, 'ArkTS语法详解'),
    new CourseItem(3, '组件与布局'),
    new CourseItem(4, '状态管理'),
    new CourseItem(5, '动画与手势'),
    new CourseItem(6, '网络与数据持久化'),
    new CourseItem(7, '性能优化实践')
  ];

  [@State](/user/State) draggingIndex: number = -1;
  [@State](/user/State) dragOffsetY: number = 0;
  [@State](/user/State) draggingOverIndex: number = -1;

  private readonly ITEM_HEIGHT: number = 80;

  build() {
    Column() {
      Text('拖拽课程以调整顺序')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 20, bottom: 20 })

      List({ space: 10 }) {
        // ForEach 中使用 CourseItem 类型
        ForEach(this.listItems, (item: CourseItem, index: number) => {
          ListItem() {
            this.ListItemBuilder(item, index)
          }
          // keyGenerator 使用 item 的 id,确保唯一性和稳定性
        }, (item: CourseItem) => item.id.toString())
      }
      .listDirection(Axis.Vertical)
      .edgeEffect(EdgeEffect.Spring)
      .layoutWeight(1)
      .padding({ left: 12, right: 12 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F1F3F5')
  }

  @Builder
  // Builder 方法的参数也使用 CourseItem 类型
  ListItemBuilder(item: CourseItem, index: number) {
    Row() {
      Text(item.text)
        .fontSize(18)
        .fontColor('#333333')
    }
    .width('100%')
    .height(this.ITEM_HEIGHT)
    .padding({ left: 16, right: 16 })
    .backgroundColor(this.draggingIndex === index ? '#FFE0B2' : (this.draggingOverIndex === index ? '#E1F5FE' : '#FFFFFF'))
    .borderRadius(12)
    .justifyContent(FlexAlign.Start)
    .alignItems(VerticalAlign.Center)
    .shadow({
      radius: this.draggingIndex === index ? 8 : 2,
      color: '#1A000000',
      offsetX: 0,
      offsetY: this.draggingIndex === index ? 4 : 1
    })
    .translate({
      y: this.draggingIndex === index ? this.dragOffsetY : 0
    })
    .animation({
      duration: 250,
      curve: Curve.EaseInOut,
      delay: 0
    })
    .gesture(
      PanGesture({ fingers: 1, direction: PanDirection.Vertical })
        .onActionStart((event: GestureEvent) => {
          this.draggingIndex = index;
          this.dragOffsetY = 0;
        })
        .onActionUpdate((event: GestureEvent) => {
          this.dragOffsetY = event.offsetY;
          const totalOffset = this.dragOffsetY;
          const targetIndex = Math.round(totalOffset / this.ITEM_HEIGHT) + index;

          if (targetIndex < 0 || targetIndex >= this.listItems.length || targetIndex === index) {
            this.draggingOverIndex = -1;
            return;
          }
          this.draggingOverIndex = targetIndex;
        })
        .onActionEnd(() => {
          if (this.draggingOverIndex !== -1 && this.draggingIndex !== this.draggingOverIndex) {
            // 5. 操作的是 CourseItem 实例数组
            const newList = [...this.listItems];
            const draggedItem = newList[this.draggingIndex];

            newList.splice(this.draggingIndex, 1);
            newList.splice(this.draggingOverIndex, 0, draggedItem);

            this.listItems = newList;
          }

          this.draggingIndex = -1;
          this.draggingOverIndex = -1;
          this.dragOffsetY = 0;
        })
    )
  }
}

更多关于HarmonyOS鸿蒙Next中使用List组件实现拖拽排序的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS Next中,使用List组件实现拖拽排序主要依赖ArkUI的拖拽事件和List组件的onDragStart、onDragEnter、onDrop事件。通过设置List的editMode属性为true启用编辑模式,结合ListItem的draggable属性为true使项目可拖拽。在拖拽过程中,通过事件回调获取拖拽起始和目标位置索引,并操作List的数据源数组完成数据交换,从而实现视觉上的排序效果。

在HarmonyOS Next中,使用List组件实现拖拽排序,主要依赖ListItem组件的onDragStartonDragEnteronDrop事件。以下是核心实现步骤:

  1. 数据与状态准备:使用@State装饰器管理列表数据源,并准备一个变量(如fromIndex)记录拖拽起始位置。

  2. 启动拖拽:在ListItem上设置onDragStart事件,当用户长按列表项时触发,在此事件中记录被拖拽项的索引,并调用dragControl.start()方法启动拖拽。

    .onDragStart((event: DragEvent) => {
      this.fromIndex = index; // 记录拖拽起始索引
      event.promise = dragControl.start(); // 启动拖拽
    })
    
  3. 处理拖拽进入:为ListItem设置onDragEnter事件。当拖拽项进入其他列表项区域时,交换数据源中fromIndex与当前进入项索引toIndex对应的数据,并更新fromIndextoIndex,以实现视觉上的实时位置交换。

    .onDragEnter((event: DragEvent) => {
      // 交换数据源中 fromIndex 和 toIndex 的数据
      let temp = this.dataArray[this.fromIndex];
      this.dataArray[this.fromIndex] = this.dataArray[index];
      this.dataArray[index] = temp;
      this.fromIndex = index; // 更新起始索引为当前位置
    })
    
  4. 处理放置:在ListItem或整个List上设置onDrop事件。当用户释放拖拽项时,执行最终的数据交换(如果之前实时交换已满足需求,此处可能仅需重置状态),并调用dragControl.finish()结束拖拽。

    .onDrop((event: DragEvent) => {
      // 可在此处执行最终的同步逻辑(如果与onDragEnter逻辑重复,可省略或做清理)
      event.promise = dragControl.finish(); // 结束拖拽
    })
    

关键点

  • 拖拽的视觉反馈(如半透明效果)通常由系统自动处理。
  • 数据交换逻辑是核心,需在onDragEnter中实时更新@State数据驱动UI刷新。
  • 确保dragControlstartfinish方法配对调用。

此方案通过数据驱动实现了List的拖拽排序交互。

回到顶部