摘要列表滑动删除是移动应用中极为常见的交互模式。本文详细讲解如何在 HarmonyOS ArkTS 中实现带有切换删除按钮的列表支持添加和删除列表项涵盖 List 组件、手势交互、状态管理和动画反馈等核心技术点。一、项目概述「滑动删除」功能是移动端列表交互的标配——用户在列表项上向左滑动露出删除按钮点击即可删除该项。同时我们也提供「添加」功能让用户可以动态扩充列表。本项目的核心功能点功能描述技术实现列表展示以列表形式展示数据项List ListItem滑动显示删除左滑列表项露出删除按钮手势监听 状态控制删除确认点击删除按钮移除列表项splice 删除 状态更新新增项添加新的列表条目数组 push List 刷新删除按钮切换点击按钮切换为删除模式Toggle 状态驱动二、项目架构entry/src/main/ets/ ├── pages/ │ └── SwipeDelete.ets // 滑动删除主页面 ├── components/ │ ├── SwipeableItem.ets // 可滑动列表项组件 │ └── AddItemDialog.ets // 添加项目的弹窗组件 ├── model/ │ └── ListItemData.ets // 列表项数据模型 └── common/ └── Constants.ets // 常量与样式定义数据流架构用户手势 → SwipeableItem (位移) → 父组件 (删除/添加) → State 更新 → UI 重渲染三、核心技术分析3.1 List 组件与列表渲染HarmonyOS 的List组件是高性能列表容器配合ListItem和ForEach实现数据驱动渲染// model/ListItemData.ets export interface ListItemData { id: number title: string description: string time: string isFavorite: boolean } export function createMockData(): ListItemData[] { const items: ListItemData[] [] const titles [ HarmonyOS 开发笔记, ArkTS 布局技巧, 状态管理指南, 组件化实践, 网络请求封装, 数据持久化方案, 动画与过渡, 性能优化专题, 调试工具使用, 发布上架流程 ] for (let i 0; i titles.length; i) { items.push({ id: i 1, title: titles[i], description: 第 ${i 1} 篇技术文章, time: 2025-01-${String(i 10).padStart(2, 0)}, isFavorite: i % 3 0 }) } return items }ForEach 渲染列表List({ space: 10 }) { ForEach(this.items, (item: ListItemData, index: number) { ListItem() { SwipeableItem({ item: item, onDelete: () this.deleteItem(index), onToggle: () this.toggleFavorite(index) }) } }) } .width(100%) .height(100%) .divider({ strokeWidth: 1, color: #E8E8E8 })List 组件关键属性属性作用本场景用法space列表项间距10vp增加呼吸感divider分割线浅灰色 1px 分割线edgeEffect边缘弹性效果EdgeEffect.SpringscrollBar滚动条自动显示3.2 滑动删除手势实现滑动删除的核心是手势处理。在 ArkTS 中我们可以通过onTouch事件或PanGesture手势实现滑动检测。这里我们采用状态驱动的方式// components/SwipeableItem.ets Component export struct SwipeableItem { Prop item: ListItemData private onDelete?: () void private onToggle?: () void State private offsetX: number 0 State private isSwiped: boolean false private readonly DELETE_BTN_WIDTH: number 80 private readonly SWIPE_THRESHOLD: number 60 private startX: number 0 build() { Stack() { // 底层删除按钮 Row() { Button(删除) .width(this.DELETE_BTN_WIDTH) .height(100%) .backgroundColor(#FF3B30) .fontColor(Color.White) .fontSize(15) .onClick(() { if (this.onDelete) { this.onDelete() } }) } .width(100%) .height(100%) .justifyContent(FlexAlign.End) // 顶层列表项内容可滑动 Row() { // 图标 Circle({ width: 40, height: 40 }) .fill(this.item.isFavorite ? #FFD700 : #E0E0E0) .margin({ left: 16, right: 12 }) // 文本内容 Column({ space: 4 }) { Text(this.item.title) .fontSize(16) .fontWeight(FontWeight.Medium) .lineHeight(22) Text(this.item.description) .fontSize(13) .fontColor(#888888) Text(this.item.time) .fontSize(12) .fontColor(#AAAAAA) } .alignItems(HorizontalAlign.Start) // 收藏按钮 Button({ type: ButtonType.Circle }) { Image(this.item.isFavorite ? $r(app.media.star_filled) : $r(app.media.star_empty)) .width(24) .height(24) } .width(36) .height(36) .backgroundColor(transparent) .margin({ right: 12 }) .onClick(() { if (this.onToggle) { this.onToggle() } }) } .width(100%) .height(72) .backgroundColor(Color.White) .borderRadius(8) .translate({ x: this.offsetX }) .onTouch((event: TouchEvent) { this.handleTouch(event) }) } .width(100%) .height(72) .clip(true) // 超出部分裁切保持圆角 } handleTouch(event: TouchEvent): void { switch (event.type) { case TouchType.Down: this.startX event.touches[0].x break case TouchType.Move: const currentX event.touches[0].x const deltaX currentX - this.startX // 只允许向左滑动负方向 const newOffset Math.min(0, Math.max(-this.DELETE_BTN_WIDTH, this.offsetX deltaX)) this.offsetX newOffset this.startX currentX break case TouchType.Up: case TouchType.Cancel: if (this.offsetX -this.SWIPE_THRESHOLD) { // 滑过阈值吸附到删除按钮完全显示 this.offsetX -this.DELETE_BTN_WIDTH this.isSwiped true } else { // 未达阈值回弹到原始位置 this.offsetX 0 this.isSwiped false } break } } }滑动逻辑详解TouchType.Down记录触摸起始位置TouchType.Move计算手指位移更新offsetX限制范围在[-DELETE_BTN_WIDTH, 0]TouchType.Up判断是否超过阈值SWIPE_THRESHOLD60vp超过吸附到删除按钮完全显示位置-DELETE_BTN_WIDTH未超过弹性回弹到原始位置0clip(true)裁切超出 Stack 区域的内容保持圆角效果3.3 删除与添加功能删除操作// SwipeDelete.ets 中的删除方法 deleteItem(index: number): void { // 动画效果可以先移除 const deletedItem this.items[index] this.items.splice(index, 1) // 更新状态触发 UI 刷新 this.items [...this.items] // 显示提示 this.showToast(已删除「${deletedItem.title}」) }注意事项使用splice删除后需要重新赋值以触发State更新使用扩展运算符[...this.items]创建新数组引用确保 ArkTS 检测到变化删除后显示 Toast 提示用户操作结果添加操作// 添加新项目 addItem(title: string, description: string): void { const newItem: ListItemData { id: Date.now(), // 使用时间戳生成唯一 ID title: title, description: description, time: new Date().toLocaleDateString(zh-CN), isFavorite: false } this.items [newItem, ...this.items] // 插到列表顶部 this.showToast(已添加新项目) }添加弹窗组件// components/AddItemDialog.ets Component export struct AddItemDialog { State private title: string State private description: string private onConfirm?: (title: string, desc: string) void private onCancel?: () void State private visible: boolean false build() { if (this.visible) { Stack() { // 遮罩层 Rect() .width(100%) .height(100%) .fill(#80000000) .onClick(() this.dismiss()) // 对话框 Column({ space: 16 }) { Text(添加新项目) .fontSize(20) .fontWeight(FontWeight.Bold) TextInput({ placeholder: 标题, text: this.title }) .width(100%) .height(44) .backgroundColor(#F5F5F5) .borderRadius(8) .padding({ left: 12 }) .onChange((v: string) { this.title v }) TextInput({ placeholder: 描述, text: this.description }) .width(100%) .height(44) .backgroundColor(#F5F5F5) .borderRadius(8) .padding({ left: 12 }) .onChange((v: string) { this.description v }) Row({ space: 16 }) { Button(取消) .layoutWeight(1) .height(40) .backgroundColor(#E0E0E0) .fontColor(#333333) .borderRadius(8) .onClick(() this.dismiss()) Button(确认) .layoutWeight(1) .height(40) .backgroundColor(#007AFF) .fontColor(Color.White) .borderRadius(8) .onClick(() { if (this.title.trim().length 0 this.onConfirm) { this.onConfirm(this.title.trim(), this.description.trim()) this.dismiss() } }) } .width(100%) } .width(85%) .padding(24) .backgroundColor(Color.White) .borderRadius(16) } .width(100%) .height(100%) } } show(): void { this.title this.description this.visible true } dismiss(): void { this.visible false } }3.4 Toggle 删除按钮切换除了滑动删除我们还实现一个「切换删除模式」的按钮让用户通过点击进入删除状态// 主页面中的 Toggle 按钮 State private isDeleteMode: boolean false build() { Column() { // 顶部栏 Row() { Text(滑动删除列表) .fontSize(22) .fontWeight(FontWeight.Bold) Blank() // 切换删除模式 Toggle({ type: ToggleType.Switch, isOn: this.isDeleteMode }) .onChange((isOn: boolean) { this.isDeleteMode isOn }) Text(this.isDeleteMode ? 删除模式 : 滑动模式) .fontSize(13) .fontColor(this.isDeleteMode ? #FF3B30 : #888888) // 添加按钮 Button({ type: ButtonType.Circle }) { Image($r(app.media.add_icon)) .width(24) .height(24) } .width(36) .height(36) .backgroundColor(#007AFF) .margin({ left: 8 }) .onClick(() { this.dialog.show() }) } .width(92%) .padding({ top: 16, bottom: 8 }) // 列表 List({ space: 10 }) { ForEach(this.items, (item: ListItemData, index: number) { ListItem() { if (this.isDeleteMode) { // 删除模式下显示删除按钮 Row() { Text(item.title) .fontSize(16) .layoutWeight(1) Button(删除) .width(60) .height(32) .backgroundColor(#FF3B30) .fontColor(Color.White) .fontSize(13) .borderRadius(6) .onClick(() this.deleteItem(index)) } .width(100%) .height(56) .padding({ left: 16, right: 8 }) .backgroundColor(Color.White) .borderRadius(8) } else { // 滑动模式下使用可滑动组件 SwipeableItem({ item: item, onDelete: () this.deleteItem(index), onToggle: () this.toggleFavorite(index) }) } } }) } .width(92%) .layoutWeight(1) } .width(100%) .height(100%) .backgroundColor(#F0F0F0) }Toggle 组件说明属性类型说明typeToggleTypeSwitch / Checkbox / ButtonisOnboolean当前开关状态onChangecallback状态变化回调通过Toggle切换两种模式用户可以选择自己习惯的操作方式。四、HarmonyOS 特性深度解析4.1 手势系统ArkTS 的手势系统支持多种手势识别器手势说明适用场景TapGesture点击按钮点击LongPressGesture长按触发编辑模式PanGesture拖拽滑动删除SwipeGesture快速滑动翻页PinchGesture双指缩放图片缩放本例使用onTouch事件实现滑动因为其提供了更细粒度的触摸控制。也可以改用PanGesture手势SwipeableItem() .gesture( PanGesture({ direction: PanDirection.Left }) .onActionStart((event: GestureEvent) { ... }) .onActionUpdate((event: GestureEvent) { ... }) .onActionEnd((event: GestureEvent) { ... }) )4.2 translate 属性与动画translate属性用于移动组件的渲染位置不影响布局占位.translate({ x: this.offsetX })结合animateTo可以实现平滑动画// 回弹动画 animateTo({ duration: 200, curve: Curve.Friction }, () { this.offsetX 0 })曲线类型对比Curve效果使用场景Linear匀速简单移动Friction带阻尼的减速自然回弹Ease渐入渐出平滑动画Spring弹簧效果弹性交互4.3 clip 属性裁切clip属性控制内容超出容器范围时的显示方式Stack() .clip(true) // 裁切超出部分 // 或 .clip(false) // 允许内容溢出在滑动删除中clip(true)保证删除按钮在列表项圆角范围内优雅显示。五、UI/UX 设计要点5.1 交互反馈设计视觉反馈滑动时列表项跟随手指移动提供实时位置反馈触感反馈删除时调用震动 APIvibrator.vibrate(30)Toast 提示操作完成后显示轻量提示回弹动画未达到阈值时弹性回弹模拟真实物理效果5.2 删除确认机制为防止误操作可添加二次确认deleteWithConfirm(index: number): void { AlertDialog.show({ title: 确认删除, message: 确定要删除「${this.items[index].title}」吗, primaryButton: { value: 取消, action: () {} }, secondaryButton: { value: 删除, action: () { this.deleteItem(index) } } }) }5.3 空状态展示当列表为空时显示友好的空状态提示if (this.items.length 0) { Column() { Image($r(app.media.empty_icon)) .width(120) .height(120) Text(暂无数据点击右上角添加) .fontSize(14) .fontColor(#999999) .margin({ top: 16 }) } .width(100%) .height(100%) .justifyContent(FlexAlign.Center) }六、最佳实践总结6.1 性能优化key 属性为ForEach提供唯一 key提升 diff 效率ForEach(this.items, (item: ListItemData) { ListItem() { ... } }, (item: ListItemData) item.id.toString())避免全量重建数组操作后使用浅拷贝触发更新而非重新创建整个数组LazyForEach大量数据时使用LazyForEach进行虚拟列表渲染6.2 错误处理边界情况空列表删除、删除最后一个项目、快速连续删除同步状态确保删除后列表索引与数据状态一致异常恢复删除失败时恢复数据6.3 无障碍访问为删除按钮添加accessibilityText描述确保滑动操作有替代的按钮操作方式使用Toggle提供模式切换兼顾不同用户习惯七、扩展与演进7.1 多选批量删除State selectedIds: Setnumber new Set() toggleSelection(id: number): void { if (this.selectedIds.has(id)) { this.selectedIds.delete(id) } else { this.selectedIds.add(id) } } batchDelete(): void { this.items this.items.filter(item !this.selectedIds.has(item.id)) this.selectedIds.clear() }7.2 拖拽排序配合onDragStart/onDragEnter事件实现拖拽排序ListItem() .onDragStart(() { ... }) .onDrop((event: DragEvent) { ... })7.3 撤销删除使用栈结构保存删除历史private undoStack: ListItemData[] [] deleteItem(index: number): void { const deleted this.items[index] this.undoStack.push(deleted) this.items.splice(index, 1) this.items [...this.items] showToast(已删除可撤销) } undoDelete(): void { if (this.undoStack.length 0) { const restored this.undoStack.pop() this.items [restored!, ...this.items] } }八、结语滑动删除列表是移动应用中最常见也最实用的交互模式之一。通过本项目的学习我们掌握了List ForEach构建数据驱动列表onTouch 事件实现手势滑动检测translate 属性实现内容位移Toggle 组件切换操作模式状态管理在增删操作中的应用这些技能在笔记应用、任务管理、消息列表、购物车等场景中广泛适用是 ArkTS 开发者的必会技能。
# HarmonyOS ArkTS 滑动删除列表实战(三):交互式列表增删操作
摘要列表滑动删除是移动应用中极为常见的交互模式。本文详细讲解如何在 HarmonyOS ArkTS 中实现带有切换删除按钮的列表支持添加和删除列表项涵盖 List 组件、手势交互、状态管理和动画反馈等核心技术点。一、项目概述「滑动删除」功能是移动端列表交互的标配——用户在列表项上向左滑动露出删除按钮点击即可删除该项。同时我们也提供「添加」功能让用户可以动态扩充列表。本项目的核心功能点功能描述技术实现列表展示以列表形式展示数据项List ListItem滑动显示删除左滑列表项露出删除按钮手势监听 状态控制删除确认点击删除按钮移除列表项splice 删除 状态更新新增项添加新的列表条目数组 push List 刷新删除按钮切换点击按钮切换为删除模式Toggle 状态驱动二、项目架构entry/src/main/ets/ ├── pages/ │ └── SwipeDelete.ets // 滑动删除主页面 ├── components/ │ ├── SwipeableItem.ets // 可滑动列表项组件 │ └── AddItemDialog.ets // 添加项目的弹窗组件 ├── model/ │ └── ListItemData.ets // 列表项数据模型 └── common/ └── Constants.ets // 常量与样式定义数据流架构用户手势 → SwipeableItem (位移) → 父组件 (删除/添加) → State 更新 → UI 重渲染三、核心技术分析3.1 List 组件与列表渲染HarmonyOS 的List组件是高性能列表容器配合ListItem和ForEach实现数据驱动渲染// model/ListItemData.ets export interface ListItemData { id: number title: string description: string time: string isFavorite: boolean } export function createMockData(): ListItemData[] { const items: ListItemData[] [] const titles [ HarmonyOS 开发笔记, ArkTS 布局技巧, 状态管理指南, 组件化实践, 网络请求封装, 数据持久化方案, 动画与过渡, 性能优化专题, 调试工具使用, 发布上架流程 ] for (let i 0; i titles.length; i) { items.push({ id: i 1, title: titles[i], description: 第 ${i 1} 篇技术文章, time: 2025-01-${String(i 10).padStart(2, 0)}, isFavorite: i % 3 0 }) } return items }ForEach 渲染列表List({ space: 10 }) { ForEach(this.items, (item: ListItemData, index: number) { ListItem() { SwipeableItem({ item: item, onDelete: () this.deleteItem(index), onToggle: () this.toggleFavorite(index) }) } }) } .width(100%) .height(100%) .divider({ strokeWidth: 1, color: #E8E8E8 })List 组件关键属性属性作用本场景用法space列表项间距10vp增加呼吸感divider分割线浅灰色 1px 分割线edgeEffect边缘弹性效果EdgeEffect.SpringscrollBar滚动条自动显示3.2 滑动删除手势实现滑动删除的核心是手势处理。在 ArkTS 中我们可以通过onTouch事件或PanGesture手势实现滑动检测。这里我们采用状态驱动的方式// components/SwipeableItem.ets Component export struct SwipeableItem { Prop item: ListItemData private onDelete?: () void private onToggle?: () void State private offsetX: number 0 State private isSwiped: boolean false private readonly DELETE_BTN_WIDTH: number 80 private readonly SWIPE_THRESHOLD: number 60 private startX: number 0 build() { Stack() { // 底层删除按钮 Row() { Button(删除) .width(this.DELETE_BTN_WIDTH) .height(100%) .backgroundColor(#FF3B30) .fontColor(Color.White) .fontSize(15) .onClick(() { if (this.onDelete) { this.onDelete() } }) } .width(100%) .height(100%) .justifyContent(FlexAlign.End) // 顶层列表项内容可滑动 Row() { // 图标 Circle({ width: 40, height: 40 }) .fill(this.item.isFavorite ? #FFD700 : #E0E0E0) .margin({ left: 16, right: 12 }) // 文本内容 Column({ space: 4 }) { Text(this.item.title) .fontSize(16) .fontWeight(FontWeight.Medium) .lineHeight(22) Text(this.item.description) .fontSize(13) .fontColor(#888888) Text(this.item.time) .fontSize(12) .fontColor(#AAAAAA) } .alignItems(HorizontalAlign.Start) // 收藏按钮 Button({ type: ButtonType.Circle }) { Image(this.item.isFavorite ? $r(app.media.star_filled) : $r(app.media.star_empty)) .width(24) .height(24) } .width(36) .height(36) .backgroundColor(transparent) .margin({ right: 12 }) .onClick(() { if (this.onToggle) { this.onToggle() } }) } .width(100%) .height(72) .backgroundColor(Color.White) .borderRadius(8) .translate({ x: this.offsetX }) .onTouch((event: TouchEvent) { this.handleTouch(event) }) } .width(100%) .height(72) .clip(true) // 超出部分裁切保持圆角 } handleTouch(event: TouchEvent): void { switch (event.type) { case TouchType.Down: this.startX event.touches[0].x break case TouchType.Move: const currentX event.touches[0].x const deltaX currentX - this.startX // 只允许向左滑动负方向 const newOffset Math.min(0, Math.max(-this.DELETE_BTN_WIDTH, this.offsetX deltaX)) this.offsetX newOffset this.startX currentX break case TouchType.Up: case TouchType.Cancel: if (this.offsetX -this.SWIPE_THRESHOLD) { // 滑过阈值吸附到删除按钮完全显示 this.offsetX -this.DELETE_BTN_WIDTH this.isSwiped true } else { // 未达阈值回弹到原始位置 this.offsetX 0 this.isSwiped false } break } } }滑动逻辑详解TouchType.Down记录触摸起始位置TouchType.Move计算手指位移更新offsetX限制范围在[-DELETE_BTN_WIDTH, 0]TouchType.Up判断是否超过阈值SWIPE_THRESHOLD60vp超过吸附到删除按钮完全显示位置-DELETE_BTN_WIDTH未超过弹性回弹到原始位置0clip(true)裁切超出 Stack 区域的内容保持圆角效果3.3 删除与添加功能删除操作// SwipeDelete.ets 中的删除方法 deleteItem(index: number): void { // 动画效果可以先移除 const deletedItem this.items[index] this.items.splice(index, 1) // 更新状态触发 UI 刷新 this.items [...this.items] // 显示提示 this.showToast(已删除「${deletedItem.title}」) }注意事项使用splice删除后需要重新赋值以触发State更新使用扩展运算符[...this.items]创建新数组引用确保 ArkTS 检测到变化删除后显示 Toast 提示用户操作结果添加操作// 添加新项目 addItem(title: string, description: string): void { const newItem: ListItemData { id: Date.now(), // 使用时间戳生成唯一 ID title: title, description: description, time: new Date().toLocaleDateString(zh-CN), isFavorite: false } this.items [newItem, ...this.items] // 插到列表顶部 this.showToast(已添加新项目) }添加弹窗组件// components/AddItemDialog.ets Component export struct AddItemDialog { State private title: string State private description: string private onConfirm?: (title: string, desc: string) void private onCancel?: () void State private visible: boolean false build() { if (this.visible) { Stack() { // 遮罩层 Rect() .width(100%) .height(100%) .fill(#80000000) .onClick(() this.dismiss()) // 对话框 Column({ space: 16 }) { Text(添加新项目) .fontSize(20) .fontWeight(FontWeight.Bold) TextInput({ placeholder: 标题, text: this.title }) .width(100%) .height(44) .backgroundColor(#F5F5F5) .borderRadius(8) .padding({ left: 12 }) .onChange((v: string) { this.title v }) TextInput({ placeholder: 描述, text: this.description }) .width(100%) .height(44) .backgroundColor(#F5F5F5) .borderRadius(8) .padding({ left: 12 }) .onChange((v: string) { this.description v }) Row({ space: 16 }) { Button(取消) .layoutWeight(1) .height(40) .backgroundColor(#E0E0E0) .fontColor(#333333) .borderRadius(8) .onClick(() this.dismiss()) Button(确认) .layoutWeight(1) .height(40) .backgroundColor(#007AFF) .fontColor(Color.White) .borderRadius(8) .onClick(() { if (this.title.trim().length 0 this.onConfirm) { this.onConfirm(this.title.trim(), this.description.trim()) this.dismiss() } }) } .width(100%) } .width(85%) .padding(24) .backgroundColor(Color.White) .borderRadius(16) } .width(100%) .height(100%) } } show(): void { this.title this.description this.visible true } dismiss(): void { this.visible false } }3.4 Toggle 删除按钮切换除了滑动删除我们还实现一个「切换删除模式」的按钮让用户通过点击进入删除状态// 主页面中的 Toggle 按钮 State private isDeleteMode: boolean false build() { Column() { // 顶部栏 Row() { Text(滑动删除列表) .fontSize(22) .fontWeight(FontWeight.Bold) Blank() // 切换删除模式 Toggle({ type: ToggleType.Switch, isOn: this.isDeleteMode }) .onChange((isOn: boolean) { this.isDeleteMode isOn }) Text(this.isDeleteMode ? 删除模式 : 滑动模式) .fontSize(13) .fontColor(this.isDeleteMode ? #FF3B30 : #888888) // 添加按钮 Button({ type: ButtonType.Circle }) { Image($r(app.media.add_icon)) .width(24) .height(24) } .width(36) .height(36) .backgroundColor(#007AFF) .margin({ left: 8 }) .onClick(() { this.dialog.show() }) } .width(92%) .padding({ top: 16, bottom: 8 }) // 列表 List({ space: 10 }) { ForEach(this.items, (item: ListItemData, index: number) { ListItem() { if (this.isDeleteMode) { // 删除模式下显示删除按钮 Row() { Text(item.title) .fontSize(16) .layoutWeight(1) Button(删除) .width(60) .height(32) .backgroundColor(#FF3B30) .fontColor(Color.White) .fontSize(13) .borderRadius(6) .onClick(() this.deleteItem(index)) } .width(100%) .height(56) .padding({ left: 16, right: 8 }) .backgroundColor(Color.White) .borderRadius(8) } else { // 滑动模式下使用可滑动组件 SwipeableItem({ item: item, onDelete: () this.deleteItem(index), onToggle: () this.toggleFavorite(index) }) } } }) } .width(92%) .layoutWeight(1) } .width(100%) .height(100%) .backgroundColor(#F0F0F0) }Toggle 组件说明属性类型说明typeToggleTypeSwitch / Checkbox / ButtonisOnboolean当前开关状态onChangecallback状态变化回调通过Toggle切换两种模式用户可以选择自己习惯的操作方式。四、HarmonyOS 特性深度解析4.1 手势系统ArkTS 的手势系统支持多种手势识别器手势说明适用场景TapGesture点击按钮点击LongPressGesture长按触发编辑模式PanGesture拖拽滑动删除SwipeGesture快速滑动翻页PinchGesture双指缩放图片缩放本例使用onTouch事件实现滑动因为其提供了更细粒度的触摸控制。也可以改用PanGesture手势SwipeableItem() .gesture( PanGesture({ direction: PanDirection.Left }) .onActionStart((event: GestureEvent) { ... }) .onActionUpdate((event: GestureEvent) { ... }) .onActionEnd((event: GestureEvent) { ... }) )4.2 translate 属性与动画translate属性用于移动组件的渲染位置不影响布局占位.translate({ x: this.offsetX })结合animateTo可以实现平滑动画// 回弹动画 animateTo({ duration: 200, curve: Curve.Friction }, () { this.offsetX 0 })曲线类型对比Curve效果使用场景Linear匀速简单移动Friction带阻尼的减速自然回弹Ease渐入渐出平滑动画Spring弹簧效果弹性交互4.3 clip 属性裁切clip属性控制内容超出容器范围时的显示方式Stack() .clip(true) // 裁切超出部分 // 或 .clip(false) // 允许内容溢出在滑动删除中clip(true)保证删除按钮在列表项圆角范围内优雅显示。五、UI/UX 设计要点5.1 交互反馈设计视觉反馈滑动时列表项跟随手指移动提供实时位置反馈触感反馈删除时调用震动 APIvibrator.vibrate(30)Toast 提示操作完成后显示轻量提示回弹动画未达到阈值时弹性回弹模拟真实物理效果5.2 删除确认机制为防止误操作可添加二次确认deleteWithConfirm(index: number): void { AlertDialog.show({ title: 确认删除, message: 确定要删除「${this.items[index].title}」吗, primaryButton: { value: 取消, action: () {} }, secondaryButton: { value: 删除, action: () { this.deleteItem(index) } } }) }5.3 空状态展示当列表为空时显示友好的空状态提示if (this.items.length 0) { Column() { Image($r(app.media.empty_icon)) .width(120) .height(120) Text(暂无数据点击右上角添加) .fontSize(14) .fontColor(#999999) .margin({ top: 16 }) } .width(100%) .height(100%) .justifyContent(FlexAlign.Center) }六、最佳实践总结6.1 性能优化key 属性为ForEach提供唯一 key提升 diff 效率ForEach(this.items, (item: ListItemData) { ListItem() { ... } }, (item: ListItemData) item.id.toString())避免全量重建数组操作后使用浅拷贝触发更新而非重新创建整个数组LazyForEach大量数据时使用LazyForEach进行虚拟列表渲染6.2 错误处理边界情况空列表删除、删除最后一个项目、快速连续删除同步状态确保删除后列表索引与数据状态一致异常恢复删除失败时恢复数据6.3 无障碍访问为删除按钮添加accessibilityText描述确保滑动操作有替代的按钮操作方式使用Toggle提供模式切换兼顾不同用户习惯七、扩展与演进7.1 多选批量删除State selectedIds: Setnumber new Set() toggleSelection(id: number): void { if (this.selectedIds.has(id)) { this.selectedIds.delete(id) } else { this.selectedIds.add(id) } } batchDelete(): void { this.items this.items.filter(item !this.selectedIds.has(item.id)) this.selectedIds.clear() }7.2 拖拽排序配合onDragStart/onDragEnter事件实现拖拽排序ListItem() .onDragStart(() { ... }) .onDrop((event: DragEvent) { ... })7.3 撤销删除使用栈结构保存删除历史private undoStack: ListItemData[] [] deleteItem(index: number): void { const deleted this.items[index] this.undoStack.push(deleted) this.items.splice(index, 1) this.items [...this.items] showToast(已删除可撤销) } undoDelete(): void { if (this.undoStack.length 0) { const restored this.undoStack.pop() this.items [restored!, ...this.items] } }八、结语滑动删除列表是移动应用中最常见也最实用的交互模式之一。通过本项目的学习我们掌握了List ForEach构建数据驱动列表onTouch 事件实现手势滑动检测translate 属性实现内容位移Toggle 组件切换操作模式状态管理在增删操作中的应用这些技能在笔记应用、任务管理、消息列表、购物车等场景中广泛适用是 ArkTS 开发者的必会技能。