DEV Community

哟嚯
哟嚯

Posted on

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

前言

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

加载更多方案

  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垂直列表 瀑布流垂直列表
Image description Image description Image description Image description
横向列表
List横向列表 Grid横向列表 瀑布流横向列表
Image description Image description Image description

第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:()=>{}
    })
  }
}
Enter fullscreen mode Exit fullscreen mode
@Component
export struct LoadMoreView {
    build() {
      Text("正在加载更多...")
      .width("100%")
      .height("50")
      .onVisibleAreaChange([0], (isVisible: boolean, currentRatio: number) => {
        if (isVisible) {
          /*触发加载更多逻辑*/
        }
      })
    }
}
Enter fullscreen mode Exit fullscreen mode

完整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
}
Enter fullscreen mode Exit fullscreen mode

完整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();
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Heroku

Amplify your impact where it matters most — building exceptional apps.

Leave the infrastructure headaches to us, while you focus on pushing boundaries, realizing your vision, and making a lasting impression on your users.

Get Started

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Engage with a wealth of insights in this thoughtful article, valued within the supportive DEV Community. Coders of every background are welcome to join in and add to our collective wisdom.

A sincere "thank you" often brightens someone’s day. Share your gratitude in the comments below!

On DEV, the act of sharing knowledge eases our journey and fortifies our community ties. Found value in this? A quick thank you to the author can make a significant impact.

Okay