鸿蒙开发:案例集合List:多列表相互拖拽(删除/插入,偏移动效)(微暇)

🎯 案例集合List:多列表相互拖拽(删除/插入,偏移动效)(微暇)

🌍 案例集合List

🚪 最近开启了学员班级点我欢迎加入(学习相关知识可获得定制礼盒)

🏷️ 效果图

已实现

  • 不同列表交换
  • 同列表交换
  • 组件边缘滑动 (原生自带)
  • 组件检测偏移动效
  • 拖出/插入过渡动画

📖 参考

🧩 拆解

javascript 复制代码
import { curves } from "@kit.ArkUI"

/**
 * 组件在拖拽中需要用到的额外信息
 */
interface extraParamsObj {
  insertIndex: number // 插入项下标
  selectedIndex: number // 选中项下标
}

/**
 * mockData
 */
class ItemData {
  id: number = 0
  content: string = ''
}

/**
 * 组件标识
 */
enum CompType {
  LeftList = 'LeftList',
  RightList = 'RightList',
}

@Component
export struct MultipleListsForMutualDelAndIns {
  /**
   * mock数据1
   */
  @State leftList: ItemData[] =
    [
      { id: 1, content: "A" },
      { id: 2, content: "B" },
      { id: 3, content: "C" },
      { id: 4, content: "D" },
      { id: 5, content: "E" },
      { id: 6, content: "F" },
      { id: 111, content: "" },// 用于拖拽到底部无法位移
    ]
  /**
   * mock数据2
   */
  @State rightList: ItemData[] =
    [
      { id: 7, content: "G" },
      { id: 8, content: "H" },
      { id: 9, content: "I" },
      { id: 10, content: "j" },
      { id: 11, content: "K" },
      { id: 12, content: "L" },
      { id: 222, content: "" },// 用于拖拽到底部无法位移
    ]
  /**
   * 当前悬停在那个组件上方的下标
   */
  @State hoverIdx: number = -1
  /**
   * 当前悬停在那个组件上方
   */
  @State hoverComp: CompType | undefined = undefined
  /**
   * 拖拽的item
   */
  @State scaleItem: ItemData = new ItemData()

  /**
   * 拖拽项视图
   * @param item
   */
  @Builder
  private dragBuilder(item: ItemData) {
    Text(item.content)
      .width(150)
      .height(50)
      .fontSize(20)
      .fontWeight(FontWeight.Bold)
      .backgroundColor(Color.Orange)
      .textAlign(TextAlign.Center)
      .borderRadius(20)
      .scale({ x: 1.15, y: 1.15 })
  }

  /**
   * 拖拽开始统一方法
   * @param compType
   * @param extraParams
   */
  private itemDragAndDropToStart(compType: CompType, idx: number, item: ItemData) {
    this.scaleItem = item
    this.hoverComp = compType
    this.hoverIdx = idx
    this.listDataType(compType).splice(idx, 1)
  }

  /**
   * 拖拽释放统一方法
   * @param compType
   * @param extraParams
   */
  private itemDragAndDropEnds(compType: CompType) {
    this.listDataType(compType).splice(this.hoverIdx, 0, this.scaleItem)

    // 重置当前悬停在那个组件上方的下标
    this.hoverIdx = -1
    // 重置组件标识
    this.hoverComp = undefined
  }

  /**
   * 悬停组件过渡
   * @param compType
   * @param extraParams
   */
  private itemDragMove(compType: CompType, extraParams: string) {
    this.hoverComp = compType
    this.hoverIdx = (JSON.parse(extraParams) as extraParamsObj)?.insertIndex

    this.getUIContext()
      .getPromptAction()
      .showToast({ message: `当前组件:${this.hoverComp}---准备替换的目标下标${this.hoverIdx}` })
  }

  /**
   * 数据类型
   * @param dragType
   * @returns
   */
  private listDataType(dragType: CompType) {
    switch (dragType) {
      case CompType.LeftList:
        return this.leftList

      case CompType.RightList:
        return this.rightList
    }
  }

  build() {
    Row({ space: 10 }) {
      // 左列表
      List({ space: 10 }) {
        ForEach(this.leftList, (item: ItemData, idx: number) => {
          if (this.leftList.length - 1 !== idx) {
            ListItem() {
              ListItemCase({
                item,
                itemIdx: idx,
                hoverIdx: this.hoverIdx,
                defCompType: CompType.LeftList,
                curCompType: this.hoverComp,
              })
            }
            .onDragStart((event: DragEvent, extraParams: string) => {
              this.itemDragAndDropToStart(CompType.LeftList, idx, item)
              return this.dragBuilder(item)
            })
          } else {
            // 处理拖拽到最后一个元素无法换位置
            ListItem()
              .margin({
                top: this.leftList.length - 1 === this.hoverIdx && this.hoverComp === CompType.LeftList ? 60 : 0
              })
              .animation({ curve: curves.interpolatingSpring(0, 1, 400, 38), duration: 100 })
          }
        }, (item: ItemData) => item.id.toString())
      }
      .onDragMove((event: DragEvent, extraParams: string) => {
        this.itemDragMove(CompType.LeftList, extraParams)
      })
      .onDrop((event: DragEvent, extraParams: string) => {
        this.itemDragAndDropEnds(CompType.LeftList)
        event.useCustomDropAnimation = true // 落入没有动效
      })
      .width(180)
      .height(250)
      .scrollBar(BarState.On)
      .edgeEffect(EdgeEffect.None)

      // 右列表
      List({ space: 10 }) {
        ForEach(this.rightList, (item: ItemData, idx: number) => {
          if (this.rightList.length - 1 !== idx) {
            ListItem() {
              ListItemCase({
                item,
                itemIdx: idx,
                hoverIdx: this.hoverIdx,
                defCompType: CompType.RightList,
                curCompType: this.hoverComp
              })
            }
            .onDragStart((event: DragEvent, extraParams: string) => {
              this.itemDragAndDropToStart(CompType.RightList, idx, item)
              return this.dragBuilder(item)
            })
          } else {
            // 处理拖拽到最后一个元素无法换位置
            ListItem()
              .margin({
                top: this.rightList.length - 1 === this.hoverIdx && this.hoverComp === CompType.RightList ? 60 : 0
              })
              .animation({ curve: curves.interpolatingSpring(0, 1, 400, 38), duration: 100 })
          }
        }, (item: ItemData) => item.id.toString())
      }
      .onDragMove((event: DragEvent, extraParams: string) => {
        this.itemDragMove(CompType.RightList, extraParams)
      })
      .onDrop((event: DragEvent, extraParams: string) => {
        this.itemDragAndDropEnds(CompType.RightList)
        event.useCustomDropAnimation = true // 落入没有动效
      })
      .width(180)
      .height(250)
      .scrollBar(BarState.On)
      .edgeEffect(EdgeEffect.None)
    }
    .width('100%')
    .height('100%')
    .padding({ left: 10, right: 10 })
    .justifyContent(FlexAlign.Center)
  }
}

@Component
export struct ListItemCase {
  /**
   * 当前项
   */
  @Prop item: ItemData
  /**
   * 当前项下标
   */
  @Prop itemIdx: number
  /**
   * 当前悬停在某个项的下标
   */
  @Prop hoverIdx: number
  /**
   * 默认组件类型 (标识)
   */
  @Prop defCompType: CompType
  /**
   * 当前组件类型
   */
  @Prop curCompType: CompType

  /**
   * 添加外边距top
   * @returns
   */
  private itemMarTop() {
    return this.hoverIdx === this.itemIdx && this.defCompType === this.curCompType ? 60 : 0
  }

  build() {
    Text(this.item.content)
      .width(150)
      .height(50)
      .fontSize(20)
      .fontWeight(FontWeight.Bold)
      .backgroundColor(Color.Orange)
      .textAlign(TextAlign.Center)
      .borderRadius(20)
      .margin({ top: this.itemMarTop() })
      .animation({ curve: curves.interpolatingSpring(0, 1, 400, 38), duration: 100 })
  }
}

🌸🌼🌺

相关推荐
waeng_luo2 小时前
[鸿蒙2025领航者闯关]使用RelationalStore实现增删改查(CRUD)操作
harmonyos·鸿蒙·#鸿蒙2025领航者闯关·#鸿蒙6实战
500843 小时前
鸿蒙 Flutter 分布式数据同步:DistributedData 实时协同实战
分布式·flutter·华为·electron·开源·wpf·音视频
后端小张3 小时前
【鸿蒙2025领航者闯关】从技术突破到生态共建,开发者的成长与远航
华为·wpf·生态·harmonyos·鸿蒙·鸿蒙系统·鸿蒙2025领航者试炼
song5013 小时前
鸿蒙 Flutter 图像编辑:原生图像处理与滤镜开发
图像处理·人工智能·分布式·flutter·华为·交互
花启莫你是不是傻3 小时前
鸿蒙下FFmpeg编译流程梳理
华为·ffmpeg·harmonyos
malajisi013 小时前
鸿蒙PC开发笔记三:HarmonyOS PC 命令行开发和Helloworld
笔记·华为·harmonyos·命令行开发·鸿蒙pc
lqj_本人3 小时前
DevUI高频组件(表格组件)深度用法与避坑指南
华为
500843 小时前
鸿蒙 Flutter 超级终端适配:多设备流转与状态无缝迁移
java·人工智能·flutter·华为·性能优化·wpf
song5013 小时前
鸿蒙 Flutter 应用签名:证书配置与上架实战
人工智能·分布式·python·flutter·华为·开源鸿蒙