欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

React Native 自定义下拉刷新上拉加载的列表的示例

程序员文章站 2022-07-10 17:43:21
在移动端开发中列表页是非常常见的页面,在react native中我们一般使用flatlist或sectionlist组件实现这些列表视图。通常列表页都会有大量的数据需要加...

在移动端开发中列表页是非常常见的页面,在react native中我们一般使用flatlist或sectionlist组件实现这些列表视图。通常列表页都会有大量的数据需要加载显示,这时候就用到了分页加载,因此对于列表组件来说,实现下拉刷新和上拉加载在很多情况下是必不可少的。

本篇文章基于flatlist封装一个支持下拉刷新和上拉加载的refreshlistview,对原始的flatlist进行封装之后,再调用上拉和下拉刷新就十分方便了。

下拉刷新的实现十分简单,这里我们沿用flatlist本身的属性来实现

onrefresh— 设置此选项后,则会在列表头部添加一个标准的refreshcontrol控件,以便实现“下拉刷新”的功能。同时你需要正确设置refreshing属性。

refreshing—— bool值,用来控制刷新控件的显示与隐藏。刷新完成后设为false。

通过这两个属性设置我们就可以实现flatlist头部的刷新操作,控件使用默认的样式,android和ios沿用各自系统的组件来显示。

重点在于上拉加载更多,react native的列表组件中没有这个功能,需要我们自己实现。 对于上拉加载,通常我们有几种状态,这里我创建一个refreshstate.js文件存放上拉加载的状态:

export default {
 idle: 'idle',        // 初始状态,无刷新的情况
 canloadmore: 'canloadmore', // 可以加载更多,表示列表还有数据可以继续加载
 refreshing: 'refreshing',  // 正在刷新中
 nomoredata: 'nomoredata',  // 没有更多数据了
 failure: 'failure'     // 刷新失败
}

然后根据这几种状态来封装一个refreshfooter组件,使其根据不同状态显示不同内容,废话不多说上代码:

import react, {component} from 'react';
import {view, text, activityindicator, stylesheet, touchableopacity} from 'react-native';
import refreshstate from './refreshstate';
import proptypes from 'prop-types';

export default class refreshfooter extends component {

 static proptypes = {
  onloadmore: proptypes.func,   // 加载更多数据的方法
  onretryloading: proptypes.func, // 重新加载的方法
 };
 
 static defaultprops = {
  footerrefreshingtext: "努力加载中",
  footerloadmoretext: "上拉加载更多",
  footerfailuretext: "点击重新加载",
  footernomoredatatext: "已全部加载完毕"
 };
 
 render() {
  let {state} = this.props;
  let footer = null;
  switch (state) {
   case refreshstate.idle:
    // idle情况下为null,不显示尾部组件
    break;
   case refreshstate.refreshing:
    // 显示一个loading视图
    footer =
     <view style={styles.loadingview}>
      <activityindicator size="small"/>
      <text style={styles.refreshingtext}>{this.props.footerrefreshingtext}</text>
     </view>;
    break;
   case refreshstate.canloadmore:
    // 显示上拉加载更多的文字
    footer =
     <view style={styles.loadingview}>
      <text style={styles.footertext}>{this.props.footerloadmoretext}</text>
     </view>;
    break;
   case refreshstate.nomoredata:
    // 显示没有更多数据的文字,内容可以自己修改
    footer =
     <view style={styles.loadingview}>
      <text style={styles.footertext}>{this.props.footernomoredatatext}</text>
     </view>;
    break;
   case refreshstate.failure:
    // 加载失败的情况使用touchableopacity做一个可点击的组件,外部调用onretryloading重新加载数据
    footer =
     <touchableopacity style={styles.loadingview} onpress={()=>{
      this.props.onretryloading && this.props.onretryloading();
     }}>
      <text style={styles.footertext}>{this.props.footerfailuretext}</text>
     </touchableopacity>;
    break;
  }
  return footer;
 }
}

const styles = stylesheet.create({
 loadingview: {
  flexdirection: 'row',
  justifycontent: 'center',
  alignitems: 'center',
  padding: 15,
 },
 refreshingtext: {
  fontsize: 12,
  color: "#666666",
  paddingleft: 10,
 },
 footertext: {
  fontsize: 12,
  color: "#666666"
 }
});

注意,proptypes是我们给refreshfooter组件定义的给外部调用的方法,方法类型需要使用proptypes来指定,需要安装facebook的prop-types依赖库,最好使用 yarn add prop-types 安装,不容易出错。这里用作运行时的类型检查,可以详细了解。

defaultprops中我们定义了几种不同状态下默认的文本内容,可以在外部传值进行修改。

接下来就要来实现这个refreshlistview了。首先应该明确的是,这个refreshlistview要有头部刷新和尾部刷新的调用方法,具体调用数据的方法应该在外部实现。先跟refreshfooter一样定义两个方法:

static proptypes = {
 onheaderrefresh: proptypes.func, // 下拉刷新的方法,供外部调用
 onfooterrefresh: proptypes.func, // 上拉加载的方法,供外部调用
};

上面说到头部的下拉刷新使用flatlist自带特性实现,我们需要定义一个bool值isheaderrefreshing来作为refreshing属性的值,控制头部显示与否。同时定义一个isfooterrefreshing来判断尾部组件的刷新状态。定义footerstate用来设定当前尾部组件的state,作为refreshfooter的值。

constructor(props) {
  super(props);
  this.state = {
   isheaderrefreshing: false, // 头部是否正在刷新
   isfooterrefreshing: false, // 尾部是否正在刷新
   footerstate: refreshstate.idle, // 尾部当前的状态,默认为idle,不显示控件
  }
 }

render函数如下:

render() {
  return (
   <flatlist
    {...this.props}
    onrefresh={()=>{ this.beginheaderrefresh() }}
    refreshing={this.state.isheaderrefreshing}
    onendreached={() => { this.beginfooterrefresh() }}
    onendreachedthreshold={0.1} // 这里取值0.1(0~1之间不包括0和1),可以根据实际情况调整,取值尽量小
    listfootercomponent={this._renderfooter}
   />
  )
 }
 
 _renderfooter = () => {
  return (
   <refreshfooter
    state={this.state.footerstate}
    onretryloading={()=>{
     this.beginfooterrefresh()
    }}
   />
  )
 };

可以看到上面的代码中有beginheaderrefresh和beginfooterrefresh两个方法,这两个方法就是用来调用刷新的,但是在刷新之前还有一些逻辑情况需要判断。比如头部和尾部不能够同时刷新,不然数据处理结果可能受到影响,正在刷新时要防止重复的刷新操作,这些都是要考虑的。这里我在代码中详细注释了:

/// 开始下拉刷新
beginheaderrefresh() {
 if (this.shouldstartheaderrefreshing()) {
  this.startheaderrefreshing();
 }
}

/// 开始上拉加载更多
beginfooterrefresh() {
 if (this.shouldstartfooterrefreshing()) {
  this.startfooterrefreshing();
 }
}

/***
 * 当前是否可以进行下拉刷新
 * @returns {boolean}
 *
 * 如果列表尾部正在执行上拉加载,就返回false
 * 如果列表头部已经在刷新中了,就返回false
 */
shouldstartheaderrefreshing() {
 if (this.state.footerstate === refreshstate.refreshing ||
  this.state.isheaderrefreshing ||
  this.state.isfooterrefreshing) {
  return false;
 }
 return true;
}

/***
 * 当前是否可以进行上拉加载更多
 * @returns {boolean}
 *
 * 如果底部已经在刷新,返回false
 * 如果底部状态是没有更多数据了,返回false
 * 如果头部在刷新,则返回false
 * 如果列表数据为空,则返回false(初始状态下列表是空的,这时候肯定不需要上拉加载更多,而应该执行下拉刷新)
 */
shouldstartfooterrefreshing() {
 if (this.state.footerstate === refreshstate.refreshing ||
  this.state.footerstate === refreshstate.nomoredata ||
  this.props.data.length === 0 ||
  this.state.isheaderrefreshing ||
  this.state.isfooterrefreshing) {
  return false;
 }
 return true;
}

其中startheaderrefreshing和startfooterrefreshing的逻辑如下:

/// 下拉刷新,设置完刷新状态后再调用刷新方法,使页面上可以显示出加载中的ui,注意这里setstate写法
startheaderrefreshing() {
 this.setstate(
  {
   isheaderrefreshing: true
  },
  () => {
   this.props.onheaderrefresh && this.props.onheaderrefresh();
  }
 );
}

/// 上拉加载更多,将底部刷新状态改为正在刷新,然后调用刷新方法,页面上可以显示出加载中的ui,注意这里setstate写法
startfooterrefreshing() {
 this.setstate(
  {
   footerstate: refreshstate.refreshing,
   isfooterrefreshing: true
  },
  () => {
   this.props.onfooterrefresh && this.props.onfooterrefresh();
  }
 );
}

在刷新之前,我们需要将头部或尾部的组件显示出来,然后再调用外部的数据接口方法。这里setstate这样写的好处是state中的值更新完成后才会调用箭头函数中的方法,是有严格顺序的,如果把 this.props.onfooterrefresh && this.props.onfooterrefresh() 写在setstate外部,在ui上我们可能看不到头部的loading或者尾部的努力加载中,接口方法就已经调用完毕了。

最后,在刷新结束后我们还需要调用停止刷新的方法,使头部或尾部组件不再显示,否则一直是加载中还可能让人以为是bug。下面看看停止刷新的方法:

/**
 * 根据尾部组件状态来停止刷新
 * @param footerstate
 *
 * 如果刷新完成,当前列表数据源是空的,就不显示尾部组件了。
 * 这里这样做是因为通常列表无数据时,我们会显示一个空白页,如果再显示尾部组件如"没有更多数据了"就显得很多余
 */
endrefreshing(footerstate: refreshstate) {
 let footerrefreshstate = footerstate;
 if (this.props.data.length === 0) {
  footerrefreshstate = refreshstate.idle;
 }
 this.setstate({
  footerstate: footerrefreshstate,
  isheaderrefreshing: false,
  isfooterrefreshing: false
 })
}

这里传入一个尾部组件状态的参数是为了更新尾部组件的样式。同时对数据源data进行一个判断,如果为空说明当前没有数据,可以显示空白页面,那么尾部组件也没必要显示了。

以下是我使用refreshlistview实现的豆瓣电影页面分页加载的效果图:

完整的demo地址: https://github.com/mrarronz/react-native-blog-examples/tree/master/chapter4-pullrefresh/pullrefreshexample

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。