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

在移动端开发中列表页是很是常见的页面,在React Native中咱们通常使用FlatList或SectionList组件实现这些列表视图。一般列表页都会有大量的数据须要加载显示,这时候就用到了分页加载,所以对于列表组件来讲,实现下拉刷新和上拉加载在不少状况下是必不可少的。react

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

下拉刷新的实现十分简单,这里咱们沿用FlatList自己的属性来实现github

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

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

经过这两个属性设置咱们就能够实现FlatList头部的刷新操做,控件使用默认的样式,Android和iOS沿用各自系统的组件来显示。函数

重点在于上拉加载更多,React Native的列表组件中没有这个功能,须要咱们本身实现。 对于上拉加载,一般咱们有几种状态,这里我建立一个RefreshState.js文件存放上拉加载的状态:flex

export default {
  Idle: 'Idle',               // 初始状态,无刷新的状况
  CanLoadMore: 'CanLoadMore', // 能够加载更多,表示列表还有数据能够继续加载
  Refreshing: 'Refreshing',   // 正在刷新中
  NoMoreData: 'NoMoreData',   // 没有更多数据了
  Failure: 'Failure'          // 刷新失败
}
复制代码

而后根据这几种状态来封装一个RefreshFooter组件,使其根据不一样状态显示不一样内容,废话很少说上代码:ui

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安装,不容易出错。这里用做运行时的类型检查,能够点击这里详细了解。this

defaultProps中咱们定义了几种不一样状态下默认的文本内容,能够在外部传值进行修改。spa

接下来就要来实现这个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地址:github.com/mrarronz/re…,感谢阅读!

相关文章
相关标签/搜索