HarmonyOS NEXT数据列表加载更多(无需监听列表滑到最底部)

该文首发在juejin平台,当天就被人抄袭到优快云了

前言

有数据列表的页面,一般需要使用下拉刷新和上拉加载的功能
而数据列表实现加载下一页的功能,目前有以下几种方案
(如果还有更优雅的方案,欢迎大佬们在评论区补充)

(源码地址在文末)

加载更多方案

  1. 监听列表的滑动事件,在列表滑动到底部时触发加载更多的逻辑
  2. 自定义布局,监听手势,上拉布局到某个高度时->释放手势->触发加载更多的逻辑
    (实现原理上篇文章有说)
  3. 手动给数据列表加个ListItem,然后监听onVisibleAreaChange事件 (本文主要介绍的方案)
  4. LazyForEach中通过index判断是否是最后一个item,然后触发加载更多逻辑
//在IDataSource中的totalCount方法中返回真实item数量+1
totalCount(): number {
  return this.dataList.length + 1;
}
LazyForEach(IDataSource, (item: Object, index) => {
  if(通过index判断是否是最后一个item){
    ListItem() {
      /*footer布局*/
    }
  }else{
    ListItem() {
      /*正常item布局*/
    }
  }
})

各个方案缺点

第1种方案
列表必须滑动到最底部才能触发事件,哪怕列表滑到99%也不能触发。
如果列表有回弹效果,回弹时又会触发一次(小问题,可以通过逻辑判断规避或者禁用回弹效果)

第2种方案
每次都需要一个上拉的操作,用户体验感略差(如果产品明确需要这种交互效果除外)

第4种方案
该方案也是我一开始实现的方案,体验感也是最好的一种
(真正的无感加载,正常滑动列表时,还没看到footer布局时,下一页可能就加载完成了)
但是因为实现逻辑不够优雅,被我放弃了,然后改用第3种方案实现(对已有布局没有入侵性)

效果图
垂直列表
下拉刷新+上拉加载List垂直列表Grid垂直列表瀑布流垂直列表
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
横向列表
List横向列表Grid横向列表瀑布流横向列表
在这里插入图片描述在这里插入图片描述在这里插入图片描述

第3种方案

实现过程
  1. 在list列表尾部添加一个item布局,然后监听item布局的onVisibleAreaChange事件
  2. onVisibleAreaChange()第一个参数传[0],这样滑动列表时item布局在屏幕显示一丢丢时,就会触发该事件的回调,然后在该事件中执行加载更多的逻辑,
    这样就规避第1种方案中必须滑动到最底部的问题

如果用户滑动速度不是很快,可以做到无感加载下一页
如果产品需要footer视图出现一半及以上的面积,让用户感知明显,再触发加载更多
只需要将第一个参数改成[0.5]即可

List() {
  LazyForEach(this.adapter, (item: Object, index) => {
    ListItem() {
       Text("正常item布局")
    }
  })
  
  /*手动添加一个footer布局*/
  ListItem() {
    LoadMoreView({
      /*控制器,用于通知LoadMoreView内部是否加载完成或加载错误或是否还有更多数据*/
      controller: this.loadMoreController,
      
      loadMore: () => {
        /*触发加载更多*/
      },
      
      /*非必传:是否是垂直列表*/
      vertical: this.isVertical,
      
      /*非必传:加载数据中的自定义视图*/
      loadingView:()=>{},
      
      /*非必传:加载错误的自定义视图*/
      errorView:()=>{},
      
      /*非必传:暂无更多数据的自定义视图*/
      noMoreView:()=>{}
    })
  }
}
@Component
export struct LoadMoreView {
    build() {
      Text("正在加载更多...")
      .width("100%")
      .height("50")
      .onVisibleAreaChange([0], (isVisible: boolean, currentRatio: number) => {
        if (isVisible) {
          /*触发加载更多逻辑*/
        }
      })
    }
}

完整LoadMoreController代码

export class LoadMoreController {
  /*加载结束*/
  loadEnd: (hasMore: boolean) => void = (hasMore: boolean) => {
    this.loadIsError = false
    //请求数据完成,hasMore true:还有更多数据,false:暂无更多数据
    this.hasMore = hasMore
  }
  /*加载错误*/
  loadError: () => void = () => {
    //请求数据失败
    this.loadIsError = true
  }
  /*隐藏加载提示*/
  setHiddenView: (hiddenView: boolean) => void = (hiddenView: boolean) => {
    this.hiddenView = hiddenView
  }
  /*是否还有更多数据*/
  public hasMore: boolean = false
  /*加载是否错误*/
  public loadIsError: boolean = false
  public hiddenView: boolean = false
  public isEndRequest: boolean = true
}

完整LoadMoreView代码

import { LoadMoreController } from './LoadMoreController';


/*默认状态*/
const state_def = 0;
/*加载中*/
const state_loading = 1;
/*加载失败*/
const state_error = 2;
/*暂无更多*/
const state_no_more = 3;

@Component
export struct LoadMoreView {
  controller: LoadMoreController = new LoadMoreController()
  loadMore: () => void = () => {
  }
  @State vertical: boolean = true
  @BuilderParam loadingView?: () => void
  @BuilderParam noMoreView?: () => void
  @BuilderParam errorView?: () => void
  @State state: number = state_def

  private setState() {
    if (this.controller.hasMore) {
      this.state = state_loading
    } else {
      this.state = state_no_more
    }
    if (this.controller.loadIsError) {
      this.state = state_error
    }
    if (this.controller.hiddenView) {
      this.state = state_def
    }
  }

  aboutToAppear() {
    this.setState()
    this.controller.loadEnd = (hasMore: boolean) => {
      this.controller.isEndRequest = true
      this.controller.loadIsError = false
      this.controller.hiddenView = false
      this.controller.hasMore = hasMore
      this.setState()
    }
    this.controller.loadError = () => {
      this.controller.isEndRequest = true
      this.controller.loadIsError = true
      this.controller.hiddenView = false
      this.setState()
    }
    this.controller.setHiddenView = (hiddenView: boolean) => {
      this.controller.hiddenView = hiddenView
      this.setState()
    }


    this.startLoadMore();
  }

  private startLoadMore() {
    if (this.controller.isEndRequest && this.state == state_loading) {
      this.controller.isEndRequest = false
      this.loadMore()
    }
  }

  build() {
    if (state_loading == this.state) {
      if (this.loadingView) {
        Stack() {
          this.loadingView()
        }
        .width(this.vertical ? "100%" : "auto")
        .height(!this.vertical ? "100%" : "auto")
        .onVisibleAreaChange([0], (isVisible: boolean, currentRatio: number) => {
          if (isVisible) {
            this.startLoadMore();
          }
        })
      } else {
        Text(this.vertical ? "正在加载更多..." : "正\n在\n加\n载\n更\n多\n...")
          .width(this.vertical ? "100%" : "50")
          .height(this.vertical ? "50" : "100%")
          .textAlign(TextAlign.Center)
          .onVisibleAreaChange([0], (isVisible: boolean, currentRatio: number) => {
            if (isVisible) {
              this.startLoadMore();
            }
          })
      }

    } else if (state_no_more == this.state) {
      if (this.noMoreView) {
        this.noMoreView()
      } else {
        Text(this.vertical ? "暂无更多数据" : "暂\n无\n更\n多\n数\n据")
          .width(this.vertical ? "100%" : "50")
          .height(this.vertical ? "50" : "100%")
          .textAlign(TextAlign.Center)
      }
    } else if (state_error == this.state) {
      if (this.errorView) {
        Stack() {
          this.errorView()
        }
        .width(this.vertical ? "100%" : "auto")
        .height(!this.vertical ? "100%" : "auto")
        .onClick(() => {
          this.clickLoadMore();
        })

      } else {
        Text(this.vertical ? "加载失败,点击重试" : "加\n载\n失\n败\n,\n点\n击\n重\n试")
          .width(this.vertical ? "100%" : "50")
          .height(this.vertical ? "50" : "100%")
          .textAlign(TextAlign.Center)
          .onClick(() => {
            this.clickLoadMore();
          })
      }
    }
  }

  private clickLoadMore() {
    if (this.controller.isEndRequest && this.state == state_error) {
      this.state = state_loading;
      this.controller.isEndRequest = false;
      this.loadMore();
    }
  }
}

项目源码

–>源码地址
https://gitee.com/zhongrui_developer/LoadMoreProject

<think>嗯,用户想在HarmonyOS Next中实现列表的上拉加载更多功能。首先,我需要回忆一下HarmonyOS的开发文档和相关组件。记得在之前的引用中看到,Scroll容器有下拉触底的函数,这可能和上拉加载有关。比如,引用[2]提到使用Scroll容器的下拉触底函数来实现加载更多。所以可能需要用到onReachEnd事件。 接下来,用户可能需要具体的步骤和代码示例。首先,应该导入必要的组件,比如Scroll和PullToRefresh,不过根据引用[1],PullToRefresh是第三方库,用于下拉刷新,但上拉加载可能直接在Scroll里处理。所以可能需要结合两者,或者单独使用Scroll的触底事件。 然后,数据加载的逻辑是关键。用户需要维护一个数据列表和当前页码,当触发上拉时,页码增加,获取新数据并追加到列表。同时,加载状态和错误处理也很重要,比如显示“加载中”或“加载失败”的提示。 还需要考虑性能优化,比如防抖处理,避免频繁触发加载。另外,列表项的渲染要高效,可能用到ForEach循环和条件判断。 后,测试和效果预览是必要的,确保功能正常,并且用户体验流畅。可能还需要参考官方文档确认API的用法,确保代码的正确性。现在把这些思路整理成步骤,并提供示例代码,应该就能帮助用户实现了。</think>在 HarmonyOS Next 中实现列表上拉加载更多功能,可以通过以下步骤实现(结合系统提供的 Scroll 容器特性): ### 一、核心实现步骤 1. **使用 Scroll 容器** 在布局文件中使用 Scroll 容器作为列表的父组件,通过其 `onReachEnd` 事件监听触底操作[^2] ```typescript Scroll() { // 列表内容 } .onReachEnd(() => { this.loadMoreData() // 触底时触发加载 }) .width('100%') ``` 2. **数据加载逻辑** ```typescript @State pageIndex: number = 1 @State isLoading: boolean = false loadMoreData() { if (this.isLoading) return this.isLoading = true // 模拟异步请求 setTimeout(() => { // 追加新数据 this.dataList = this.dataList.concat(newData) this.pageIndex++ this.isLoading = false }, 1000) } ``` 3. **加载状态提示** ```typescript if (this.isLoading) { Text('加载中...') .fontSize(12) .textAlign(TextAlign.Center) } ``` ### 二、完整示例代码 ```typescript @Entry @Component struct ListPage { @State dataList: string[] = ['Item 1', 'Item 2', 'Item 3'] @State pageIndex: number = 1 @State isLoading: boolean = false build() { Column() { Scroll() { Column() { ForEach(this.dataList, (item: string) => { Text(item) .fontSize(18) .padding(10) }) if (this.isLoading) { Text('加载中...') .fontSize(12) .textAlign(TextAlign.Center) } } } .onReachEnd(() => { this.loadMoreData() }) } } loadMoreData() { if (this.isLoading) return this.isLoading = true // 模拟API请求 setTimeout(() => { const newData = Array.from({length: 5}, (_, i) => `Item ${this.dataList.length + i + 1}`) this.dataList = this.dataList.concat(newData) this.pageIndex++ this.isLoading = false }, 1500) } } ``` ### 三、优化建议 1. **防抖处理**:通过设置标志位防止重复请求 2. **错误处理**:添加加载失败状态提示 3. **性能优化**:使用 `LazyForEach` 代替 `ForEach` 处理长列表 4. **分页控制**:当无更多数据时显示提示信息
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Harmony钟睿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值