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


Posted in Javascript onMarch 01, 2018

在移动端开发中列表页是非常常见的页面,在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

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

Javascript 相关文章推荐
用原生JS获取CLASS对象(很简单实用)
Oct 15 Javascript
每天一篇javascript学习小结(基础知识)
Nov 10 Javascript
jquery实现垂直和水平菜单导航栏
Aug 27 Javascript
浅谈react.js 之 批量添加与删除功能
Apr 17 Javascript
基于webpack 实用配置方法总结
Sep 28 Javascript
解读vue生成的文件目录结构及说明
Nov 27 Javascript
9102年webpack4搭建vue项目的方法步骤
Feb 20 Javascript
vue elementUI table 自定义表头和行合并的实例代码
May 22 Javascript
使用JS判断页面是首次被加载还是刷新
May 26 Javascript
解决layui动态添加的元素click等事件触发不了的问题
Sep 20 Javascript
vue 使用 canvas 实现手写电子签名
Mar 06 Javascript
使用JavaScript获取Django模板指定键值数据
May 27 Javascript
解决vue中无法动态修改jqgrid组件 url地址的问题
Mar 01 #Javascript
vue 实现类似淘宝星级评分的示例
Mar 01 #Javascript
vue-star评星组件开发实例
Mar 01 #Javascript
浅谈Angular 的变化检测的方法
Mar 01 #Javascript
ES6学习笔记之map、set与数组、对象的对比
Mar 01 #Javascript
Node.js静态服务器的实现方法
Feb 28 #Javascript
JS脚本加载后执行相应回调函数的操作方法
Feb 28 #Javascript
You might like
利用php递归实现无限分类 格式化数组的详解
2013/06/08 PHP
PHP register_shutdown_function()函数的使用示例
2015/06/23 PHP
使用图灵api创建微信聊天机器人
2015/07/23 PHP
给WordPress中的留言加上楼层号的PHP代码实例
2015/12/14 PHP
php使用PDO从数据库表中读取数据的实现方法(必看)
2017/06/02 PHP
一个JS翻页效果
2007/07/23 Javascript
niceTitle 基于jquery的超链接提示插件
2010/05/31 Javascript
Js setInterval与setTimeout(定时执行与循环执行)的代码(可以传入参数)
2010/06/11 Javascript
简单实现限制uploadify上传个数
2015/11/16 Javascript
JQuery对ASP.NET MVC数据进行更新删除
2016/07/13 Javascript
BootStrap Validator对于隐藏域验证和程序赋值即时验证的问题浅析
2016/12/01 Javascript
Angular的自定义指令以及实例
2016/12/26 Javascript
微信小程序-拍照或选择图片并上传文件
2017/01/06 Javascript
基于JS代码实现简单易用的倒计时 x 天 x 时 x 分 x 秒效果
2017/07/13 Javascript
基于Vue2的独立构建与运行时构建的差别(详解)
2017/12/06 Javascript
如何为你的JavaScript代码日志着色详解
2019/04/08 Javascript
layer弹出层自适应高度,垂直水平居中的实现
2019/09/16 Javascript
[16:56]heroes英雄教学 司夜刺客
2014/09/18 DOTA
python解析文件示例
2014/01/23 Python
python获得linux下所有挂载点(mount points)的方法
2015/04/29 Python
python通过imaplib模块读取gmail里邮件的方法
2015/05/08 Python
Python3.2模拟实现webqq登录
2016/02/15 Python
Python正则替换字符串函数re.sub用法示例
2017/01/19 Python
python opencv 图像尺寸变换方法
2018/04/02 Python
PyQt5 QListWidget选择多项并返回的实例
2019/06/17 Python
flask框架单元测试原理与用法实例分析
2019/07/23 Python
python subprocess pipe 实时输出日志的操作
2020/12/05 Python
网络安全类面试题
2015/08/01 面试题
大学同学十年聚会感言
2014/02/21 职场文书
小学教师自我剖析材料
2014/09/29 职场文书
人事行政助理岗位职责
2015/04/11 职场文书
乒乓球比赛通知
2015/04/27 职场文书
导游词之任弼时故居
2020/01/07 职场文书
Python机器学习算法之决策树算法的实现与优缺点
2021/05/13 Python
Spring mvc是如何实现与数据库的前后端的连接操作的?
2021/06/30 Java/Android
JavaScript阻止事件冒泡的方法
2021/12/06 Javascript