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…,感謝閱讀!

相關文章
相關標籤/搜索