鸿蒙开发:案例集合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 })
  }
}

🌸🌼🌺

相关推荐
tyqtyq223 小时前
药品说明书解读 —— AI 安全用药助手,鸿蒙原生应用深度解析
人工智能·学习·华为·生活·harmonyos
风华圆舞4 小时前
鸿蒙HarmonyOS滚字组件实战 —— 逐字 Slot Roll,从浏览器到原生 ArkUI
华为·harmonyos·arkui·slottext·measureutils·共享时间轴·滚字
不羁的木木4 小时前
HarmonyOS APP实战-画图APP - 第8篇:图像效果应用(亮度/模糊/灰度)
华为·harmonyos
●VON4 小时前
HarmonyKit | 鸿蒙开发展望:HarmonyKit 未来路线图与社区共建
华为·单元测试·harmonyos·鸿蒙
HarmonyOS_SDK4 小时前
借助游戏预启动提前加载游戏,读条快人一步
harmonyos
不羁的木木5 小时前
HarmonyOS APP实战-画图APP - 第3篇:矩形与圆形绘制
华为·harmonyos
绝世番茄6 小时前
Column 与 if/else 条件渲染:动态子组件的布局行为
华为·harmonyos·鸿蒙
花开彼岸天~6 小时前
鸿蒙实战:AppStorage 全局状态管理
华为·harmonyos·鸿蒙系统
爱吃大芒果7 小时前
鸿蒙 7 新特性科普:什么是系统 Agent 智能体?
华为·harmonyos·智能体
listening7777 小时前
HarmonyOS 6.1 元服务实战:把电商卡片装进系统“负一屏”
华为·harmonyos