项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star

效果演示

1. 概述

在现代移动应用中,可滑动列表项是一种常见且高效的交互方式,它允许用户通过水平滑动列表项来显示隐藏的操作按钮,如删除、置顶、归档等。本教程将详细讲解如何使用HarmonyOS NEXT的Row组件结合手势和动画创建一个流畅的可滑动列表项,实现滑动操作按钮的高级交互效果。

2. 可滑动列表项的设计原则

在设计可滑动列表项时,需要考虑以下几个关键原则:

  1. 直观性:滑动交互应该直观,用户能够轻松理解如何操作。
  2. 反馈性:滑动过程中应提供适当的视觉反馈,如内容跟随手指移动。
  3. 可恢复性:用户应能轻松取消滑动操作,如通过反向滑动或点击其他区域。
  4. 操作明确性:滑动显示的操作按钮应清晰明确,避免误操作。
  5. 性能流畅性:滑动动画应流畅,没有卡顿,提供良好的用户体验。

3. 案例分析:可滑动的列表项

本案例展示了如何创建一个可滑动的列表项,通过水平滑动显示隐藏的操作按钮(置顶和删除)。

3.1 完整代码

@Component
export struct SwipeableListItem {
  @State isSwiped: boolean = false
  @State swipeOffset: number = 0
  private swipeThreshold: number = 20
  private maxSwipeDistance: number = 120
  private actionButtonWidth: number = 60
  private name: string = '张三'
  private avatar: Resource = $r('app.media.avatar')
  private lastMessage: string = '你好,最近怎么样?'
  private onPin?: () => void
  private onDelete?: () => void

  build() {
    Row() {
      // 左侧内容(头像、姓名、最后消息)
      Row() {
        Image(this.avatar)
          .width(50)
          .height(50)
          .borderRadius(25)
          .margin({ right: 12 })

        Column() {
          Text(this.name)
            .fontSize(16)
            .fontWeight(FontWeight.Medium)
            .margin({ bottom: 4 })

          Text(this.lastMessage)
            .fontSize(14)
            .fontColor('#666666')
            .maxLines(1)
            .textOverflow({ overflow: TextOverflow.Ellipsis })
        }
        .alignItems(HorizontalAlign.Start)
        .flexGrow(1)
      }
      .width('100%')
      .padding(16)
      .backgroundColor('#FFFFFF')
      .translate({ x: this.swipeOffset })
      .gesture(
        PanGesture({ direction: PanDirection.Horizontal })
          .onActionStart(() => {
            // 开始滑动
          })
          .onActionUpdate((event: GestureEvent) => {
            // 更新滑动位置
            if (event.offsetX < 0) {
              // 只允许向左滑动(负值)
              this.swipeOffset = Math.max(-this.maxSwipeDistance, event.offsetX)
            } else if (this.isSwiped) {
              // 如果已经处于滑开状态,允许向右滑动恢复
              this.swipeOffset = Math.min(0, -this.maxSwipeDistance + event.offsetX)
            }
          })
          .onActionEnd(() => {
            // 结束滑动,根据滑动距离决定是否显示操作按钮
            if (this.swipeOffset < -this.swipeThreshold) {
              // 滑动距离超过阈值,显示操作按钮
              this.swipeOffset = -this.maxSwipeDistance
              this.isSwiped = true
            } else {
              // 滑动距离未超过阈值,恢复原位
              this.swipeOffset = 0
              this.isSwiped = false
            }
          })
      )

      // 右侧操作按钮(置顶、删除)
      if (this.isSwiped) {
        Row() {
          Button({ type: ButtonType.Capsule }) {
            Text('置顶')
              .fontSize(14)
              .fontColor('#FFFFFF')
          }
          .width(this.actionButtonWidth)
          .height(50)
          .backgroundColor('#2196F3')
          .onClick(() => {
            if (this.onPin) {
              this.onPin()
            }
            // 操作完成后恢复原位
            this.swipeOffset = 0
            this.isSwiped = false
          })

          Button({ type: ButtonType.Capsule }) {
            Text('删除')
              .fontSize(14)
              .fontColor('#FFFFFF')
          }
          .width(this.actionButtonWidth)
          .height(50)
          .backgroundColor('#FF5252')
          .onClick(() => {
            if (this.onDelete) {
              this.onDelete()
            }
            // 操作完成后恢复原位
            this.swipeOffset = 0
            this.isSwiped = false
          })
        }
        .width(this.maxSwipeDistance)
        .height('100%')
        .position({ x: '100%' })
        .translate({ x: -this.maxSwipeDistance })
      }
    }
    .width('100%')
    .height(82)
    .clip(true)
  }
}

3.2 代码详解

3.2.1 组件声明与状态定义

@Component
export struct SwipeableListItem {
  @State isSwiped: boolean = false
  @State swipeOffset: number = 0
  private swipeThreshold: number = 20
  private maxSwipeDistance: number = 120
  private actionButtonWidth: number = 60
  private name: string = '张三'
  private avatar: Resource = $r('app.media.avatar')
  private lastMessage: string = '你好,最近怎么样?'
  private onPin?: () => void
  private onDelete?: () => void

这部分代码声明了一个名为SwipeableListItem的自定义组件,并定义了以下状态和属性:

属性/状态类型说明默认值
isSwipedboolean是否处于滑开状态false
swipeOffsetnumber滑动偏移量0
swipeThresholdnumber触发滑开状态的阈值20
maxSwipeDistancenumber最大滑动距离120
actionButtonWidthnumber操作按钮宽度60
namestring联系人姓名'张三'
avatarResource联系人头像$r('app.media.avatar')
lastMessagestring最后一条消息'你好,最近怎么样?'
onPin() => void置顶回调函数undefined
onDelete() => void删除回调函数undefined

使用@State装饰器定义的状态变量会在值变化时自动触发UI更新,这对于实现滑动效果非常重要。

3.2.2 外层Row容器设置

Row() {
  // 子组件
}
.width('100%')
.height(82)
.clip(true)

这部分代码创建了一个Row容器,作为可滑动列表项的根容器。Row容器的属性设置如下:

属性说明
width'100%'容器宽度为父容器的100%
height82容器高度为82vp
cliptrue裁剪超出容器范围的内容

clip(true)设置非常重要,它确保滑动时超出容器范围的内容会被裁剪,避免影响其他UI元素。

3.2.3 左侧内容设置

Row() {
  Image(this.avatar)
    .width(50)
    .height(50)
    .borderRadius(25)
    .margin({ right: 12 })

  Column() {
    Text(this.name)
      .fontSize(16)
      .fontWeight(FontWeight.Medium)
      .margin({ bottom: 4 })

    Text(this.lastMessage)
      .fontSize(14)
      .fontColor('#666666')
      .maxLines(1)
      .textOverflow({ overflow: TextOverflow.Ellipsis })
  }
  .alignItems(HorizontalAlign.Start)
  .flexGrow(1)
}
.width('100%')
.padding(16)
.backgroundColor('#FFFFFF')
.translate({ x: this.swipeOffset })
.gesture(
  PanGesture({ direction: PanDirection.Horizontal })
    // 手势处理...
)

这部分代码创建了一个Row容器,用于显示左侧内容(头像、姓名、最后消息)。Row容器的属性设置如下:

属性说明
width'100%'容器宽度为父容器的100%
padding16设置内边距为16vp
backgroundColor'#FFFFFF'设置背景色为白色
translate{ x: this.swipeOffset }设置水平平移距离为swipeOffset

内部包含两个子组件:

  1. Image组件:显示联系人头像

    • width: 50 - 设置宽度为50vp
    • height: 50 - 设置高度为50vp
    • borderRadius: 25 - 设置边框圆角为25vp,使头像呈现为圆形
    • margin: { right: 12 } - 设置右侧外边距为12vp
  2. Column容器:垂直排列姓名和最后消息

    • alignItems: HorizontalAlign.Start - 子组件在水平方向上左对齐
    • flexGrow: 1 - 设置弹性增长系数为1,占据Row容器中的剩余空间
    • 内部包含两个Text组件:

      • 姓名Text

        • fontSize: 16 - 设置字体大小为16fp
        • fontWeight: FontWeight.Medium - 设置字体粗细为中等
        • margin: { bottom: 4 } - 设置底部外边距为4vp
      • 最后消息Text

        • fontSize: 14 - 设置字体大小为14fp
        • fontColor: '#666666' - 设置字体颜色为深灰色
        • maxLines: 1 - 设置最大显示行数为1行
        • textOverflow: { overflow: TextOverflow.Ellipsis } - 设置文本溢出时显示省略号

最关键的是translate({ x: this.swipeOffset })设置,它根据swipeOffset状态变量动态调整Row容器的水平位置,实现滑动效果。

3.2.4 手势处理

.gesture(
  PanGesture({ direction: PanDirection.Horizontal })
    .onActionStart(() => {
      // 开始滑动
    })
    .onActionUpdate((event: GestureEvent) => {
      // 更新滑动位置
      if (event.offsetX < 0) {
        // 只允许向左滑动(负值)
        this.swipeOffset = Math.max(-this.maxSwipeDistance, event.offsetX)
      } else if (this.isSwiped) {
        // 如果已经处于滑开状态,允许向右滑动恢复
        this.swipeOffset = Math.min(0, -this.maxSwipeDistance + event.offsetX)
      }
    })
    .onActionEnd(() => {
      // 结束滑动,根据滑动距离决定是否显示操作按钮
      if (this.swipeOffset < -this.swipeThreshold) {
        // 滑动距离超过阈值,显示操作按钮
        this.swipeOffset = -this.maxSwipeDistance
        this.isSwiped = true
      } else {
        // 滑动距离未超过阈值,恢复原位
        this.swipeOffset = 0
        this.isSwiped = false
      }
    })
)

这部分代码为Row容器添加了一个水平方向的平移手势(PanGesture),用于处理滑动操作。手势处理包含三个阶段:

  1. onActionStart:手势开始时触发,可以在这里进行初始化操作。
  2. onActionUpdate:手势更新时触发,在这里根据手指移动距离更新swipeOffset状态变量。

    • 如果手指向左滑动(event.offsetX < 0),则更新swipeOffset为手指移动距离和最大滑动距离的较大值(负值),确保不会超过最大滑动距离。
    • 如果已经处于滑开状态且手指向右滑动,则允许向右滑动恢复原位,但不超过0。
  3. onActionEnd:手势结束时触发,在这里根据最终滑动距离决定是否显示操作按钮。

    • 如果滑动距离超过阈值(this.swipeThreshold),则将swipeOffset设置为最大滑动距离的负值,并将isSwiped设置为true,表示处于滑开状态。
    • 如果滑动距离未超过阈值,则将swipeOffset设置为0,并将isSwiped设置为false,表示恢复原位。

这种手势处理方式使列表项能够根据用户的滑动操作自然地显示或隐藏操作按钮,提供流畅的交互体验。

3.2.5 右侧操作按钮设置

if (this.isSwiped) {
  Row() {
    Button({ type: ButtonType.Capsule }) {
      Text('置顶')
        .fontSize(14)
        .fontColor('#FFFFFF')
    }
    .width(this.actionButtonWidth)
    .height(50)
    .backgroundColor('#2196F3')
    .onClick(() => {
      if (this.onPin) {
        this.onPin()
      }
      // 操作完成后恢复原位
      this.swipeOffset = 0
      this.isSwiped = false
    })

    Button({ type: ButtonType.Capsule }) {
      Text('删除')
        .fontSize(14)
        .fontColor('#FFFFFF')
    }
    .width(this.actionButtonWidth)
    .height(50)
    .backgroundColor('#FF5252')
    .onClick(() => {
      if (this.onDelete) {
        this.onDelete()
      }
      // 操作完成后恢复原位
      this.swipeOffset = 0
      this.isSwiped = false
    })
  }
  .width(this.maxSwipeDistance)
  .height('100%')
  .position({ x: '100%' })
  .translate({ x: -this.maxSwipeDistance })
}

这部分代码使用条件渲染,只有在isSwiped为true时才显示右侧操作按钮。操作按钮包含在一个Row容器中,Row容器的属性设置如下:

属性说明
widththis.maxSwipeDistance容器宽度为最大滑动距离
height'100%'容器高度为父容器的100%
position{ x: '100%' }设置水平位置为父容器的100%(右侧)
translate{ x: -this.maxSwipeDistance }设置水平平移距离为最大滑动距离的负值

内部包含两个Button组件:

  1. 置顶按钮

    • type: ButtonType.Capsule - 设置按钮类型为胶囊形
    • width: this.actionButtonWidth - 设置宽度为操作按钮宽度
    • height: 50 - 设置高度为50vp
    • backgroundColor: '#2196F3' - 设置背景色为蓝色
    • onClick: 点击时调用onPin回调函数,并恢复列表项原位
  2. 删除按钮

    • type: ButtonType.Capsule - 设置按钮类型为胶囊形
    • width: this.actionButtonWidth - 设置宽度为操作按钮宽度
    • height: 50 - 设置高度为50vp
    • backgroundColor: '#FF5252' - 设置背景色为红色
    • onClick: 点击时调用onDelete回调函数,并恢复列表项原位

position({ x: '100%' })translate({ x: -this.maxSwipeDistance })的组合使操作按钮定位在列表项的右侧,并向左平移最大滑动距离,使其在列表项滑开时正好显示在右侧。

4. 滑动交互的实现原理

在HarmonyOS NEXT中,滑动交互主要通过手势和状态管理实现。

4.1 手势识别

HarmonyOS NEXT提供了丰富的手势识别功能,在本案例中,我们使用PanGesture识别水平滑动手势:

PanGesture({ direction: PanDirection.Horizontal })

PanGesture是一种平移手势,可以识别用户的拖动操作。通过设置direction参数为PanDirection.Horizontal,我们限制了只识别水平方向的滑动,忽略垂直方向的滑动。

4.2 状态管理

在滑动交互中,状态管理非常重要。本案例使用两个状态变量管理滑动状态:

  1. swipeOffset:记录当前的滑动偏移量,用于控制列表项的水平位置。
  2. isSwiped:记录列表项是否处于滑开状态,用于控制操作按钮的显示。

这两个状态变量使用@State装饰器定义,确保它们的变化会自动触发UI更新。

4.3 动态位置调整

通过translate属性,我们可以动态调整组件的位置:

.translate({ x: this.swipeOffset })

swipeOffset状态变量变化时,组件的水平位置会随之变化,实现滑动效果。

4.4 条件渲染

通过条件渲染,我们可以根据状态动态显示或隐藏操作按钮:

if (this.isSwiped) {
  // 显示操作按钮
}

isSwiped状态变量为true时,操作按钮会显示;当为false时,操作按钮会隐藏。

5. 滑动阈值与动画效果

为了提升滑动交互的用户体验,我们需要设置适当的滑动阈值和添加动画效果。

5.1 滑动阈值

滑动阈值是指触发滑开状态的最小滑动距离。在本案例中,我们设置了20vp的滑动阈值:

private swipeThreshold: number = 20

当滑动距离超过这个阈值时,列表项会自动滑到最大滑动距离,显示操作按钮;当滑动距离小于这个阈值时,列表项会自动恢复原位。

这种设计使用户可以通过小幅度的滑动来取消操作,提高了交互的容错性。

5.2 添加动画效果

为了使滑动交互更加流畅,我们可以添加动画效果:

.translate({ x: this.swipeOffset })
.animation({
  duration: 200,
  curve: Curve.Ease
})

通过添加animation属性,我们可以为translate属性的变化添加动画效果,使列表项的滑动更加平滑。

duration参数指定了动画的持续时间,curve参数指定了动画的缓动曲线。在本例中,我们使用了200毫秒的持续时间和Curve.Ease缓动曲线,使动画既不会太快也不会太慢,提供自然的过渡效果。

6. 可滑动列表项的样式优化

为了提升可滑动列表项的视觉效果和用户体验,我们可以进行以下优化:

6.1 分割线

添加分割线可以清晰地区分不同的列表项:

Row() {
  // 现有内容
}
.width('100%')
.height(82)
.clip(true)
.border({
  width: { bottom: 1 },
  color: { bottom: '#EEEEEE' },
  style: { bottom: BorderStyle.Solid }
})

这些设置在列表项底部添加了一条浅灰色的分割线,使不同列表项之间的边界更加清晰。

6.2 点击效果

添加点击效果可以提升用户交互体验:

Row() {
  // 左侧内容
}
.width('100%')
.padding(16)
.backgroundColor('#FFFFFF')
.translate({ x: this.swipeOffset })
.stateStyles({
  pressed: {
    opacity: 0.8,
    backgroundColor: '#F8F8F8'
  }
})
.gesture(
  // 手势处理
)
.onClick(() => {
  // 处理点击事件
  if (this.isSwiped) {
    // 如果处于滑开状态,点击时恢复原位
    this.swipeOffset = 0
    this.isSwiped = false
  } else {
    // 如果未处于滑开状态,点击时执行其他操作
  }
})

这些设置使列表项在被点击时有明显的视觉反馈,提示用户点击操作已被识别。同时,我们添加了点击事件处理,使用户可以通过点击列表项来恢复滑开状态。

6.3 操作按钮样式

优化操作按钮的样式可以提升视觉效果:

Button({ type: ButtonType.Capsule }) {
  Column() {
    Image($r('app.media.ic_pin'))
      .width(24)
      .height(24)
      .margin({ bottom: 4 })
    
    Text('置顶')
      .fontSize(12)
      .fontColor('#FFFFFF')
  }
  .alignItems(HorizontalAlign.Center)
}
.width(this.actionButtonWidth)
.height(70)
.backgroundColor('#2196F3')

这些设置使操作按钮包含图标和文本,提供更丰富的视觉信息,帮助用户理解按钮的功能。

7. 可滑动列表项的交互优化

为了提升可滑动列表项的交互体验,我们可以添加以下功能:

7.1 滑动反馈

添加触觉反馈可以增强滑动交互的体验:

.onActionEnd(() => {
  // 结束滑动,根据滑动距离决定是否显示操作按钮
  if (this.swipeOffset < -this.swipeThreshold) {
    // 滑动距离超过阈值,显示操作按钮
    this.swipeOffset = -this.maxSwipeDistance
    this.isSwiped = true
    vibrate(VibrationType.Light) // 触发轻微振动
  } else {
    // 滑动距离未超过阈值,恢复原位
    this.swipeOffset = 0
    this.isSwiped = false
  }
})

通过调用vibrate函数,我们可以在列表项滑开时触发轻微振动,提供触觉反馈,增强用户的操作感知。

7.2 自动关闭

添加自动关闭功能,使用户点击其他区域时自动关闭已滑开的列表项:

@Component
export struct SwipeableList {
  @State activeItem: number = -1 // 当前滑开的列表项索引
  
  build() {
    List() {
      ForEach(this.items, (item, index) => {
        ListItem() {
          SwipeableListItem({
            name: item.name,
            avatar: item.avatar,
            lastMessage: item.lastMessage,
            isActive: this.activeItem === index,
            onSwipeStateChange: (isSwiped) => {
              if (isSwiped) {
                // 如果当前列表项被滑开,记录其索引
                this.activeItem = index
              } else if (this.activeItem === index) {
                // 如果当前列表项被关闭,重置活动索引
                this.activeItem = -1
              }
            },
            onPin: () => {
              // 处理置顶操作
            },
            onDelete: () => {
              // 处理删除操作
            }
          })
        }
      })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
    .onClick(() => {
      // 点击列表空白区域时,关闭所有滑开的列表项
      if (this.activeItem !== -1) {
        this.activeItem = -1
      }
    })
  }
}

在这个优化版本中,我们使用activeItem状态变量记录当前滑开的列表项索引,并通过onSwipeStateChange回调函数更新这个状态。当用户点击列表空白区域时,我们将activeItem重置为-1,关闭所有滑开的列表项。

8. 可滑动列表项的扩展功能

基于本案例的基本结构,我们可以扩展更多功能:

8.1 多操作按钮

在某些场景下,可能需要显示更多的操作按钮:

Row() {
  Button({ type: ButtonType.Capsule }) {
    Text('标记')
      .fontSize(14)
      .fontColor('#FFFFFF')
  }
  .width(this.actionButtonWidth)
  .height(50)
  .backgroundColor('#4CAF50')
  .onClick(() => {
    // 处理标记操作
  })

  Button({ type: ButtonType.Capsule }) {
    Text('置顶')
      .fontSize(14)
      .fontColor('#FFFFFF')
  }
  .width(this.actionButtonWidth)
  .height(50)
  .backgroundColor('#2196F3')
  .onClick(() => {
    // 处理置顶操作
  })

  Button({ type: ButtonType.Capsule }) {
    Text('删除')
      .fontSize(14)
      .fontColor('#FFFFFF')
  }
  .width(this.actionButtonWidth)
  .height(50)
  .backgroundColor('#FF5252')
  .onClick(() => {
    // 处理删除操作
  })
}
.width(this.actionButtonWidth * 3)
.height('100%')

在这个扩展功能中,我们添加了一个"标记"按钮,使用户可以标记重要的列表项。同时,我们将Row容器的宽度调整为操作按钮宽度的3倍,确保有足够的空间显示所有按钮。

8.2 滑动方向

在某些场景下,可能需要支持从右向左滑动显示操作按钮:

@Component
export struct BiDirectionalSwipeableListItem {
  @State leftSwipeOffset: number = 0
  @State rightSwipeOffset: number = 0
  @State isLeftSwiped: boolean = false
  @State isRightSwiped: boolean = false
  private swipeThreshold: number = 20
  private maxSwipeDistance: number = 120
  
  build() {
    Row() {
      // 左侧操作按钮(收藏、转发)
      if (this.isLeftSwiped) {
        Row() {
          // 左侧操作按钮
        }
        .width(this.maxSwipeDistance)
        .height('100%')
        .position({ x: 0 })
        .translate({ x: this.leftSwipeOffset - this.maxSwipeDistance })
      }
      
      // 中间内容
      Row() {
        // 列表项内容
      }
      .width('100%')
      .padding(16)
      .backgroundColor('#FFFFFF')
      .translate({ x: this.leftSwipeOffset + this.rightSwipeOffset })
      .gesture(
        PanGesture({ direction: PanDirection.Horizontal })
          .onActionUpdate((event: GestureEvent) => {
            // 处理水平滑动
            if (event.offsetX > 0 && !this.isRightSwiped) {
              // 向右滑动,显示左侧操作按钮
              this.leftSwipeOffset = Math.min(this.maxSwipeDistance, event.offsetX)
              this.rightSwipeOffset = 0
            } else if (event.offsetX < 0 && !this.isLeftSwiped) {
              // 向左滑动,显示右侧操作按钮
              this.rightSwipeOffset = Math.max(-this.maxSwipeDistance, event.offsetX)
              this.leftSwipeOffset = 0
            }
          })
          .onActionEnd(() => {
            // 处理滑动结束
            if (this.leftSwipeOffset > this.swipeThreshold) {
              // 显示左侧操作按钮
              this.leftSwipeOffset = this.maxSwipeDistance
              this.isLeftSwiped = true
              this.isRightSwiped = false
            } else if (this.rightSwipeOffset < -this.swipeThreshold) {
              // 显示右侧操作按钮
              this.rightSwipeOffset = -this.maxSwipeDistance
              this.isRightSwiped = true
              this.isLeftSwiped = false
            } else {
              // 恢复原位
              this.leftSwipeOffset = 0
              this.rightSwipeOffset = 0
              this.isLeftSwiped = false
              this.isRightSwiped = false
            }
          })
      )
      
      // 右侧操作按钮(置顶、删除)
      if (this.isRightSwiped) {
        Row() {
          // 右侧操作按钮
        }
        .width(this.maxSwipeDistance)
        .height('100%')
        .position({ x: '100%' })
        .translate({ x: this.rightSwipeOffset })
      }
    }
    .width('100%')
    .height(82)
    .clip(true)
  }
}

在这个扩展功能中,我们支持双向滑动:向左滑动显示右侧操作按钮,向右滑动显示左侧操作按钮。我们使用四个状态变量管理滑动状态:leftSwipeOffsetrightSwipeOffsetisLeftSwipedisRightSwiped

9. 可滑动列表项组件的封装与复用

为了提高代码复用性,可以将可滑动列表项封装为一个独立的组件:

@Component
export struct SwipeableListItem {
  @Prop name: string = ''
  @Prop avatar: Resource = $r('app.media.default_avatar')
  @Prop lastMessage: string = ''
  @Prop isActive: boolean = false
  @State isSwiped: boolean = false
  @State swipeOffset: number = 0
  private swipeThreshold: number = 20
  private maxSwipeDistance: number = 120
  private actionButtonWidth: number = 60
  onSwipeStateChange?: (isSwiped: boolean) => void
  onPin?: () => void
  onDelete?: () => void
  
  aboutToAppear() {
    // 监听isActive属性变化
    if (!this.isActive && this.isSwiped) {
      // 如果不是活动项但处于滑开状态,恢复原位
      this.swipeOffset = 0
      this.isSwiped = false
      if (this.onSwipeStateChange) {
        this.onSwipeStateChange(false)
      }
    }
  }
  
  build() {
    // 组件内容
  }
}

然后在应用中使用这个组件:

@Entry
@Component
struct ChatListPage {
  @State contacts: Array<{
    name: string,
    avatar: Resource,
    lastMessage: string
  }> = [
    {
      name: '张三',
      avatar: $r('app.media.avatar_1'),
      lastMessage: '你好,最近怎么样?'
    },
    {
      name: '李四',
      avatar: $r('app.media.avatar_2'),
      lastMessage: '周末有空吗?一起打球吧。'
    }
    // 更多联系人...
  ]
  @State activeItem: number = -1
  
  build() {
    Column() {
      // 标题
      Text('消息')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .padding({ left: 16, top: 16, bottom: 8 })
      
      // 联系人列表
      List() {
        ForEach(this.contacts, (contact, index) => {
          ListItem() {
            SwipeableListItem({
              name: contact.name,
              avatar: contact.avatar,
              lastMessage: contact.lastMessage,
              isActive: this.activeItem === index,
              onSwipeStateChange: (isSwiped) => {
                if (isSwiped) {
                  this.activeItem = index
                } else if (this.activeItem === index) {
                  this.activeItem = -1
                }
              },
              onPin: () => {
                // 处理置顶操作
                console.info('置顶联系人:' + contact.name)
              },
              onDelete: () => {
                // 处理删除操作
                console.info('删除联系人:' + contact.name)
              }
            })
          }
        })
      }
      .width('100%')
      .layoutWeight(1)
      .backgroundColor('#F5F5F5')
      .onClick(() => {
        // 点击列表空白区域时,关闭所有滑开的列表项
        if (this.activeItem !== -1) {
          this.activeItem = -1
        }
      })
    }
    .width('100%')
    .height('100%')
  }
}

10. 总结

本教程详细讲解了如何使用HarmonyOS NEXT的Row组件结合手势和动画创建流畅的可滑动列表项,实现滑动操作按钮的高级交互效果。


全栈若城
1 声望2 粉丝