react-native 自定义 下拉刷新 / 上拉加载更多 组件

1.封装 Scroller 组件

/**
 * 下拉刷新/上拉加载更多 组件(Scroller)
 */
import React, {Component} from 'react';
import {
  StyleSheet,
  Text,
  View,
  ListView,
  ActivityIndicator,
  RefreshControl,
} from 'react-native';
 
export default class Scroller extends Component {
  // 构造函数
  constructor(props) {
    super(props);
    this.state = {
      //
    }
  }
 
  render() {
    const { dataSource, renderRow, isRefreshing } = this.props;
    // console.log(this.props);
 
    return (
      <View style={styles.container}>
        {/*列表数据*/}
        <ListView
          // 数据源
          dataSource={dataSource}
          // 从数据源(dataSource)中接受一条数据,以及它和它所在section的ID
          renderRow={renderRow}
          // 页头与页脚会在每次渲染过程中都重新渲染(允许在ListView底部增加一栏,便于显示加载动画)
          renderFooter={this._renderFooter.bind(this)}
          // 当所有的数据都已经渲染过,并且列表被滚动到距离最底部不足onEndReachedThreshold个像素的距离时调用
          onEndReached={this._fetchMoreData.bind(this)}
          // 调用onEndReached之前的临界值,单位是像素。(预加载)
          onEndReachedThreshold={20}
          // 隐藏右侧滚动条
          showsVerticalScrollIndicator={false}
          // finished warning : in next release ...
          enableEmptySections={true}
          // 自动调整迁移内容
          // 导航栏或标签栏或工具栏不覆盖 Scrollview 内容
          // 去除默认定位间距
          automaticallyAdjustContentInsets={false}
          // 下拉刷新
          refreshControl={
            <RefreshControl
              // 是否刷新
              refreshing={isRefreshing}
              onRefresh={this._onRefresh.bind(this)}
              tintColor={"#ff6600"}
              title={"拼命加载中..."}
            />
          }
        />
      </View>
    )
  }
 
  /**
   * 下拉刷新
   */
  _onRefresh() {
    // console.log('下拉刷新');
    if (this.props.isRefreshing || !this._hasMore()) {
      return
    }
    // 向后台发送 '0',告知刷新操作
    this.props.fetchData(0);
  }
 
  /**
   * 加 _ 代表私有方法
   * 上拉加载更多
   */
  _fetchMoreData() {
    // console.log('上拉加载更多');
    /**
     * this._hasMore() 验证还有更多数据
     * isLoadingTail true/false 加载动画(菊花图)
     */
    if (!this._hasMore() || this.props.isLoadingTail) {
      return
    }
    let page = this.props.cachedResults.nextPage;
    this.props.fetchData(page);
  }
 
  /**
   * 验证还有更多数据
   */
  _hasMore() {
    return this.props.cachedResults.items.length !== this.props.cachedResults.items.total;
  }
 
  /**
   * 底部加载动画 及 没有更多数据文本(ListView底部增加一栏,便于显示加载动画)
   */
  _renderFooter() {
    if (!this._hasMore() && this.props.cachedResults.total !== 0) {
      return (
        <View style={styles.loadingMore}>
          <Text style={styles.loadingText}>没有更多了</Text>
        </View>
      )
    }
 
    if (!this.props.isLoadingTail) {
      return (
        <View style={styles.loadingMore}></View>
      )
    }
 
    // 菊花图
    return (
      <ActivityIndicator style={styles.loadingMore}/>
    )
  }
 
}
 
// 样式
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F5FCFF',
  },
  // 菊花图
  loadingMore: {
    marginVertical: 20
  },
  // 文案样式
  loadingText: {
    color: '#777',
    textAlign: 'center'
  }
});

2.页面调用

/**
 * 视频列表页
 */
import React, {Component} from 'react';
import {
  StyleSheet,
  Text,
  View,
  ImageBackground,
  ListView,
  TouchableHighlight,
  Alert,
  Dimensions,
  ActivityIndicator,
  RefreshControl,
} from 'react-native';
// 下拉刷新/上拉加载更多组件
import Scroller from '../../components/Scroller';
// 图标
import Icon from 'react-native-vector-icons/Ionicons';
// item 组件
import CreationItem from '../../components/CreationItem';
import config from '../../common/config';
import request from '../../common/request';
 
let {width} = Dimensions.get("window");
 
// 缓存列表中所有数据
let cachedResults = {
  nextPage: 1, // 下一页
  items: [], // listview 数据(视频列表)
  total: 0 // 总数
};
 
export default class List extends Component {
  // 构造函数
  constructor() {
    super();
    let ds = new ListView.DataSource({
      // 比较两条数据是否是一样的,来判断数据是否发生改变
      rowHasChanged: (r1, r2) => r1 !== r2
    });
    this.state = {
      dataSource: ds.cloneWithRows([]),
      isLoadingTail: false, // loading?
      isRefreshing: false // refresh?
    }
  }
 
  render() {
    return (
      <View style={styles.container}>
        {/*顶部标题栏*/}
        <View style={styles.header}>
          <Text style={styles.headerTitle}>列表页面</Text>
        </View>
        {/*列表数据*/}
        <Scroller
          // 数据源
          dataSource={this.state.dataSource}
          // 渲染item(子组件)
          renderRow={this._renderRow.bind(this)}
          // 是否可以刷新
          isRefreshing={this.state.isRefreshing}
          // 是否可以加载更多
          isLoadingTail={this.state.isLoadingTail}
          // 请求数据
          fetchData={this._fetchData.bind(this)}
          // 缓存列表数据
          cachedResults={cachedResults}
        />
      </View>
    )
  }
 
  // 生命周期-组件挂载完毕 请求数据
  componentDidMount() {
    this._fetchData(1);
  }
 
  // 请求数据
  _fetchData(page) {
    let that = this;
 
    if (page !== 0) { // 加载更多操作
      this.setState({
        isLoadingTail: true
      });
    } else { // 刷新操作
      this.setState({
        isRefreshing: true
      });
      // 初始哈 nextPage
      cachedResults.nextPage = 1;
    }
 
    request
      .get(config.api.base + config.api.creations, {
        accessToken: 'abc'
      })
      // data 变化的新数据
      .then((data) => {
        if (data.success) {
          // 保存原数据
          let items = cachedResults.items.slice();
          if (page !== 0) { // 加载更多操作
            // 数组拼接
            items = items.concat(data.data);
            cachedResults.nextPage += 1;
          } else { // 刷新操作
            // 数据不变
            items = data.data;
          }
 
          cachedResults.items = items; // 视频列表数据
          cachedResults.total = data.total; // 总数
 
          setTimeout(function () {
            if (page !== 0) { // 加载更多操作
              that.setState({
                isLoadingTail: false,
                dataSource: that.state.dataSource.cloneWithRows(cachedResults.items)
              });
            } else { // 刷次操作
              that.setState({
                isRefreshing: false,
                dataSource: that.state.dataSource.cloneWithRows(cachedResults.items)
              });
            }
          }, 1000);
        }
      })
      .catch((error) => {
        if (page !== 0) { // 上拉加载更多操作
          this.setState({
            isLoadingTail: false
          });
        } else {
          this.setState({ // 刷新操作
            isRefreshing: false
          });
        }
        console.error(error);
      });
  }
 
  // 列表 Item
  _renderRow(row) {
    const { navigation } = this.props;
    return (
      <CreationItem
        navigation={navigation}
        key={row.id} // 子组件唯一性
        row={row}
      />
    )
  }
}
 
// 样式
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F5FCFF',
  },
  // 头部样式
  header: {
    paddingTop: 25,
    paddingBottom: 12,
    backgroundColor: '#ee735c',
  },
  // 头部title样式
  headerTitle: {
    color: '#fff',
    fontSize: 16,
    textAlign: 'center',
    fontWeight: '600'
  },
  // 菊花图
  loadingMore: {
    marginVertical: 20
  },
  // 文案样式
  loadingText: {
    color: '#777',
    textAlign: 'center'
  }
});