【HarmonyOS】下拉刷新和上拉加载的实践

1,312 阅读3分钟

下拉刷新和上拉加载是提升用户体验的重要功能,它们让用户在浏览内容时更加直观和便捷。本文将使用Refresh组件、List组件分别实现下拉刷新和上拉加载效果。

Refresh组件 下拉刷新

下拉刷新效果

Refresh组件:可以进行页面下拉操作并显示刷新动效的容器组件

Refresh(value: RefreshOptions){
  子组件()		// 只支持单个子组件
}
.refreshOffset(value: number)	// 设置触发刷新的下拉偏移量
.pullToRefresh(value: boolean)	// 设置是否能触发刷新
// 当前刷新状态变更时,触发回调
.onStateChange(callback: (state: RefreshStatus) => void)
// 进入刷新状态时触发回调
.onRefreshing(callback: () => void)
// 下拉距离发生变化时触发回调
.onOffsetChange(callback: Callback<number>)

常用参数(RefreshOptions):

参数名类型说明
refreshing(必填)boolean组件当前是否处于刷新中状态。
默认值:false,支持$$双向绑定
builder?CustomBuilder自定义刷新区域显示内容
promptText?ResourceStr设置刷新区域底部显示的文本
refreshingContent?自定义组件自定义刷新区域显示内容

显示优先级:refreshingContent > builder > promptText

下拉刷新的几种状态(RefreshStatus):

  1. Inactive:默认未下拉状态,初始状态
  2. Drag:下拉中,下拉距离小于刷新距离
  3. OverDrag:下拉中,下拉距离超过刷新距离
  4. Refresh:下拉结束,回弹至刷新距离,进入刷新中状态
  5. Done:刷新结束,返回初始状态(顶部)
@Component
export struct QuestionListComp {
  @State isRefreshing: boolean = false
  @State refreshText: string = '' // 刷新提示文本

  aboutToAppear(): void {
    this.getData()
  }

  /* 上拉刷新自定义内容 */
  @Builder
  getRefreshBuilder() {
    Row({ space: 8 }) {
      LoadingProgress()
        .width(20)
        .aspectRatio(1)
      Text(this.refreshText)
        .fontSize(14)
        .fontColor('#818181')
    }
  }
  /* 请求数据 */
  async getData() {
    const res = await getQuestionList({ questionBankType: '10', type: this.typeId.toString() })
    this.questions = res.rows
    this.isRefreshing = false
  }

  build() {
    Column() {
      Refresh({ refreshing: $$this.isRefreshing,builder: this.getRefreshBuilder() }) {
        List() {
          ForEach(this.questions, (item: Question) => {
            ListItem() {
              QuestionItemComp({ item })
                .padding({ left: 10, right: 10 })
            }
          })
        }
        .width('100%')
        .height('100%')
      }
     .onStateChange(async (status) => {
        switch (status) {
          case RefreshStatus.Inactive:
            console.log('初始化状态')
            break
          case RefreshStatus.Drag:
            console.log('继续往下拉')
            this.refreshText = '继续往下拉'
            break
          case RefreshStatus.OverDrag:
            console.log('够了,不用拉了')
            this.refreshText = '松手加载'
            break
          case RefreshStatus.Refresh:
            console.log('刷新中')
            this.refreshText = '加载中'
          // 重新调用getData方法获取最新数据
            await this.getData()
            this.refreshText = '加载完成'
            break
          case RefreshStatus.Done:
            this.refreshText = '加载成功'
            console.log('刷新结束,回到初始值')
        }
      })
    }
  }
}

List组件 上拉加载

上拉加载效果图

实现方案:使用List组件结合onReachEnd方法实现触底加载更多

/* 上拉加载时,显示的加载文字和loading */  
@Builder
getBottom(){
    Row({ space: 8 }) {
      if (this.isLoadMore) {
        Text('加载中...')
          .fontSize(14)
          .fontColor('#818181')
        LoadingProgress()
          .width(20)
          .aspectRatio(1)
      }
      if (!this.haveData && !this.isLoadMore) {
        Text('没有更多数据啦')
          .fontSize(14)
          .fontColor('#818181')
      }
    }
    .width('100%')
    .justifyContent(FlexAlign.Center)
    .margin(5)
  }
}

List() {
  ForEach(this.questions, (item: Question) => {
    ListItem() {
      ...
    }
  })
  ListItem() {
    this.getBottom()
  }
.width('100%')
.height('100%')
.onReachEnd(() => {
  // 如果还有数据,就触底加载更多
  if (this.haveData) {
    this.isLoadMore = true
    this.getData()
  }
})

:::danger 注:onReachEnd方法在页面进入的时候会触发一次

:::

完整示例

上拉加载 和 下拉刷新效果

import { getQuestionList } from '../../../api/question'
import { MyLoadingDialog } from '../../../common/components'
import { Logger } from '../../../common/utils/logger'
import { Question, QuestionParams } from '../../../models'
import { QuestionItemComp } from '../QuestionItemComp/index'

@Component
export struct QuestionListComp {
  @Prop typeId: number
  @State questions: Question[] = []
  @State isRefreshing: boolean = false
  @State isLoadMore: boolean = false // 是否下拉刷新
  @State page: number = 0
  @State haveData: boolean = true // 是否还有数据
  @State refreshText: string = '' // 刷新提示文本

  aboutToAppear(): void {
    // 页面刚加载会触发List的onReachEnd事件
    // this.getData()
  }

  /* 上拉刷新 */
  @Builder
  getRefreshBuilder() {
    Row({ space: 8 }) {
      LoadingProgress()
        .width(20)
        .aspectRatio(1)
      Text(this.refreshText)
        .fontSize(14)
        .fontColor('#818181')
    }
  }

  async getData() {
    // 上拉加载,加载下一页10条数据,page++
    if (this.isLoadMore) {
      this.page++
    }
    // 下拉刷新,重置到第一条数据,page为1
    if (this.isRefreshing) {
      this.page = 1
    }
    const res = await getQuestionList({
      page:this.page,
      pageSize: 10
    })
    if (this.isLoadMore) {
      // 下拉刷新追加数据
      this.questions.push(...res.rows)
    } else {
      // 刷新重置数据替换
      this.questions = res.rows
    }
    // 判断是否还有数据
    this.haveData = !(res.total === this.questions.length)
    // 关闭刷新状态
    this.isRefreshing = false
    // 关闭下刷新
    this.isLoadMore = false
  }

  build() {
    Column() {
      Refresh({ refreshing: $$this.isRefreshing, builder: this.getRefreshBuilder() }) {
        List() {
          ForEach(this.questions, (item: Question) => {
            ListItem() {
              QuestionItemComp({ item })
                .padding({ left: 10, right: 10 })
            }
          })
          ListItem() {
            Row({ space: 8 }) {
              if (this.isLoadMore) {
                Text('加载中...')
                  .fontSize(14)
                  .fontColor('#818181')
                LoadingProgress()
                  .width(20)
                  .aspectRatio(1)
              }
              if (!this.haveData && !this.isLoadMore) {
                Text('没有更多数据啦')
                  .fontSize(14)
                  .fontColor('#818181')
              }
            }
            .width('100%')
            .justifyContent(FlexAlign.Center)
            .margin(5)
          }
        }
        .width('100%')
        .height('100%')
        .onReachEnd(() => {
          // 如果还有数据,就触底加载更多
          if (this.haveData) {
            this.isLoadMore = true
            this.getData()
          }
        })
      }
      .onStateChange(async (status) => {
        switch (status) {
          case RefreshStatus.Inactive:
            break
          case RefreshStatus.Drag:
            this.refreshText = '继续往下拉'
            break
          case RefreshStatus.OverDrag:
            this.refreshText = '松手加载'
            break
          case RefreshStatus.Refresh:
            this.refreshText = '加载中'
            await this.getData()
            break
          case RefreshStatus.Done:
            this.refreshText = '加载成功'
        }
      })
    }
  }
}