HarmonyOS ArkUI List 的侧滑删除 用 ListItem 的 swipeAction + endIcon 的做法

鸿蒙的 List 组件自带侧滑交互 ,但 API 不直观------是通过 ListItem.swipeAction 配 start 和 end 两个 builder 实现的。做了几个 app 的列表都要加"侧滑删除 "和"侧滑归档",走通后发现 pattern 很固定。记录一下 swipeAction + ListItem 的做法,顺便分享给有需要的朋友。

一、基础 pattern

typescript 复制代码
@Entry @Component
struct AssetListPage {
  @State items: Asset[] = []

  build() {
    List({ space: 10 }) {
      ForEach(this.items, (item: Asset) => {
        ListItem() {
          AssetRow({ asset: item })
        }
        .swipeAction({
          end: {
            builder: () => this.endSwipeBuilder(item),
            actionAreaDistance: 100,
            onAction: () => this.deleteItem(item.id),
          },
        })
      }, (item: Asset) => item.id.toString())
    }
  }

  @Builder
  endSwipeBuilder(item: Asset) {
    Row() {
      Column() {
        Image($r('app.media.ic_delete')).width(24).height(24)
        Text('删除').fontSize(12).fontColor(Color.White)
      }
      .width(80)
      .height('100%')
      .justifyContent(FlexAlign.Center)
      .backgroundColor('#EF4444')
    }
  }

  private async deleteItem(id: number): Promise<void> {
    await this.dao.delete(id)
    this.items = await this.dao.getAll()
  }
}

关键字段:

end: { builder, actionAreaDistance, onAction } :侧滑到左边(end = 滑到结束方向)。

  • builder:滑开后显示的 UI
  • actionAreaDistance:滑开阈值(100 vp)
  • onAction:滑开后用户再次点击时触发

start :滑到右边(可选)。本案没用。

二、侧滑的交互语义

用户动作 → 效果:

动作 结果
轻滑(<100vp) 回弹,不显示 action
滑开 >100vp 停住,显示 action
滑开后点 action 触发 onAction
滑开后点列表本身 回弹
滑过阈值 2 倍(>200vp) 直接触发 onAction(省一次点击)

用户体验:

  • 怕误触 → 要滑开 + 再点(两步)
  • 效率党 → 直接滑到底(一步)

本案的 100 vp 阈值 :手感合适。

  • 50 vp 太敏感
  • 150 vp 太费力

三、双侧 action(左删 右归档)

typescript 复制代码
.swipeAction({
  start: {
    builder: () => this.startSwipeBuilder(item),
    actionAreaDistance: 100,
    onAction: () => this.archiveItem(item.id),
  },
  end: {
    builder: () => this.endSwipeBuilder(item),
    actionAreaDistance: 100,
    onAction: () => this.deleteItem(item.id),
  },
})

@Builder
startSwipeBuilder(item: Asset) {
  Column() {
    Image($r('app.media.ic_archive')).width(24).height(24)
    Text('归档').fontSize(12).fontColor(Color.White)
  }
  .width(80)
  .height('100%')
  .backgroundColor('#3B82F6')      // 蓝色
}

左右两套 action:

  • start (右滑):归档(蓝)
  • end (左滑):删除(红)

色彩约定:

  • 红 → 危险 / 删除
  • 蓝 → 操作 / 归档
  • 绿 → 确认 / 完成

独立开发者的建议 :红色永远是删除 。不要用红色做其他 action(用户习惯了)。

四、滑动删除后的 UI 更新

typescript 复制代码
private async deleteItem(id: number): Promise<void> {
  await this.dao.delete(id)
  this.items = await this.dao.getAll()     // 👈 整数组重新赋值
}

关键 :整数组重新赋值 ,不是原地 splice。

不要:

typescript 复制代码
// ❌ 原地改
const idx = this.items.findIndex(i => i.id === id)
this.items.splice(idx, 1)

ArkUI 的 List 对数组引用响应 ,splice 可能不触发 rebuild。

正解 :整数组赋值 (本案)或用 immutable 的 filter:

typescript 复制代码
this.items = this.items.filter(i => i.id !== id)

ForEach 的 key(第三参数):

typescript 复制代码
ForEach(this.items, (item: Asset) => {
  // ...
}, (item: Asset) => item.id.toString())

key 的作用 :ArkUI 识别哪些 item 变了,只 rebuild 变化的。

  • 没 key → 全部 rebuild(卡)
  • 有 key → 只 rebuild 变化的(流畅)

独立开发者的建议 :ForEach 的 key 永远指定 ,用 id 字段。

五、动画:删除时淡出

朴素做法 :删了直接消失,生硬。

优化 :animateTo 淡出 + 移除。

typescript 复制代码
private async deleteItem(id: number): Promise<void> {
  // 1. 先标记删除中
  const idx = this.items.findIndex(i => i.id === id)
  this.items[idx].pendingDelete = true

  // 2. 等动画完成
  animateTo({ duration: 300, curve: Curve.EaseOut }, () => {
    this.items = this.items.filter(i => !i.pendingDelete)
  })

  // 3. 异步同步 DB
  await this.dao.delete(id)
}

UI:

typescript 复制代码
ListItem() {
  AssetRow({ asset: item })
}
.opacity(item.pendingDelete ? 0 : 1)
.height(item.pendingDelete ? 0 : undefined)      // 👈 高度塌陷
.animation({ duration: 300 })

用户看到 :item 淡出 + 列表平滑合拢。

独立开发者的建议 :删除动画 是app 精致度的关键 。1-2 行代码的差别,用户感知的 "app 质感" 翻倍。

六、确认对话框:防止误删

重要数据 (如联系人、笔记)不要滑开就删:

typescript 复制代码
.swipeAction({
  end: {
    builder: () => this.endSwipeBuilder(item),
    onAction: () => {
      promptAction.showDialog({
        title: '删除确认',
        message: `确定删除「${item.title}」吗?此操作不可撤销。`,
        buttons: [
          { text: '取消', color: '#999' },
          { text: '删除', color: '#EF4444' },
        ],
      }).then((result) => {
        if (result.index === 1) {
          this.deleteItem(item.id)
        }
      })
    },
  },
})

用户 flow :滑开 → 点删除 → 弹对话框 → 再确认 。三步。

不要 :不重要的数据也弹对话框。打断用户。

决策:

  • 轻量数据 (to-do / 书摘):滑开直接删(可撤销)
  • 重要数据 (联系人 / 账单):滑开 + 确认

七、Undo(撤销删除)

滑开直接删 + 显示 undo toast:

typescript 复制代码
private async deleteItem(id: number): Promise<void> {
  const deleted = this.items.find(i => i.id === id)
  if (!deleted) return

  this.items = this.items.filter(i => i.id !== id)
  await this.dao.delete(id)

  // 显示 undo
  promptAction.showToast({
    message: '已删除。点此撤销',
    duration: 5000,
    // 鸿蒙 Toast 不支持 action button,用 Snackbar 替代
  })

  this.lastDeleted = deleted
  this.lastDeletedTime = Date.now()
}

private async undoLast(): Promise<void> {
  if (!this.lastDeleted) return
  if (Date.now() - this.lastDeletedTime > 5000) return    // 5 秒内才能撤销
  await this.dao.insert(this.lastDeleted)
  this.items = await this.dao.getAll()
  this.lastDeleted = null
}

Snackbar / 自定义 Dialog 带按钮 。鸿蒙的内置 Toast 没按钮,要自画。

撤销窗口 5 秒 :平衡。

  • 2 秒:用户反应不过来
  • 10 秒:太长,用户已经走了

八、空列表的 Empty State

typescript 复制代码
if (this.items.length === 0) {
  Column() {
    Image($r('app.media.ic_empty')).width(80).height(80)
    Text('还没有数据').fontSize(14).fontColor('#999')
    Button('添加第一条').onClick(() => this.openAddSheet())
  }
  .width('100%')
  .height('100%')
  .justifyContent(FlexAlign.Center)
} else {
  List({ space: 10 }) { ... }
}

空列表要有引导:

  • 图标 + 文案 + 按钮
  • 用户知道要去哪里添加

不要 :只显示白屏。用户以为 app 坏了。

踩坑清单

  • 侧滑用 swipeAction({ start, end })
  • actionAreaDistance 100 vp 手感最好
  • 红色 = 删除,蓝色 = 归档,约定俗成
  • 删除要整数组赋值(or filter),不要 splice
  • ForEach 的 key 必须指定
  • 加淡出动画提升质感
  • 重要数据要确认 dialog
  • 轻量数据加 undo toast
  • 空列表要有 引导(图标 + 文案 + 按钮)

尾声

侧滑交互 是列表 app 的标准动作 。做好这一个交互 → 用户觉得"像原生 iOS Mail "(高质感);做不好 → 用户觉得"像半成品"。

独立开发者的建议:

  • 所有 List 页都加侧滑(哪怕只有删除)
  • 配 删除动画
  • 轻量数据加 undo
  • 空列表加 引导

对标:

  • iOS Mail:侧滑归档 + 侧滑删除 的鼻祖
  • Gmail:类似 pattern
  • 鸿蒙 ArkUI:内置了这个 API ,比 Android 的 ItemTouchHelper 省心

鸿蒙的 List 比 iOS 的 UITableView 更现代 (声明式)。独立开发者用 ArkUI 的 List 开发速度比 iOS 快 30-50%。

相关推荐
usdoc文档预览3 小时前
USDOC系列之PPT文件在线编辑-VUE前端实现在线浏览器在线编辑PPT文档
前端·vue.js·编辑器·office365·ppt在线编辑·office文件在线编辑·文件在线编辑
用户2705077725243 小时前
Agent 调工具卡住?3 种超时与取消策略实测对比
前端
LuHang4 小时前
我一行代码没写,把领导派的活做成了公司内部系统😭
前端
明月_清风4 小时前
Meta 开源了一个 AI Native 组件库:Astryx,150+ React 组件,专为 AI Agent 而设计
前端·人工智能·ai编程
用户850869276154 小时前
秒杀系统中 Shell 脚本并发控制 Bug:究竟哪里出了
前端
炫酷的大高个7 小时前
6 亿播放的闪身步,我用前端算出 15 倍重心差
前端·javascript
不可能片场7 小时前
内容运营自动化 配图折行别把词切两半
前端·electron
Lank_M7 小时前
HEIC转JPG统一转sRGB,P3高饱和色就回不来了
前端
补码缩写7 小时前
sharp默认转JPG颜色没错,ICC和EXIF却全没了
前端