react-native 模仿原生 实现下拉刷新/上拉加载更多(RefreshListView)

1.下拉刷新/上拉加载更多 组件(RefreshListView)

src/components/RefreshListView/index.js

/**
 * 下拉刷新/上拉加载更多 组件(RefreshListView)
 */
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
  View,
  Text,
  StyleSheet,
  FlatList,
  ActivityIndicator,
  TouchableOpacity,
  ViewPropTypes,
  RefreshControl
} from 'react-native'

const RefreshState = {
  Idle: 0,
  HeaderRefreshing: 1,
  FooterRefreshing: 2,
  NoMoreData: 3,
  Failure: 4,
  EmptyData: 5,
}

class RefreshListView extends PureComponent {
  static propTypes = {
    data: PropTypes.array.isRequired,
    renderItem: PropTypes.func.isRequired,
    refreshState: PropTypes.number.isRequired,

    listRef: PropTypes.node,
    onHeaderRefresh: PropTypes.func,
    footerContainerStyle: ViewPropTypes.style,
    footerTextStyle: ViewPropTypes.style,

    disabledSeparator: PropTypes.bool,
    disabledHeaderRefresh: PropTypes.bool,
    footerRefreshingText: PropTypes.string,
    footerFailureText: PropTypes.string,
    footerNoMoreDataText: PropTypes.string,
    footerEmptyDataText: PropTypes.string,

    ListEmptyComponent: PropTypes.node,
    footerRefreshingComponent: PropTypes.node,
    footerFailureComponent: PropTypes.node,
    footerNoMoreDataComponent: PropTypes.node,
    footerEmptyDataComponent: PropTypes.node,
  }

  static defaultProps = {
    disabledHeaderRefresh: false,
    footerRefreshingText: '数据加载中…',
    footerFailureText: '点击重新加载',
    footerNoMoreDataText: '已加载全部数据',
    footerEmptyDataText: '暂时没有相关数据',
  }

  componentWillReceiveProps(nextProps) {}

  componentDidUpdate(prevProps, prevState) {}

  onHeaderRefresh = () => {
    if (this.shouldStartHeaderRefreshing()) {
      this.props.onHeaderRefresh(RefreshState.HeaderRefreshing)
    }
  }

  onEndReached = () => {
    if (this.shouldStartFooterRefreshing()) {
      this.props.onFooterRefresh && this.props.onFooterRefresh(RefreshState.FooterRefreshing)
    }
  }

  shouldStartHeaderRefreshing = () => {
    if (this.props.refreshState == RefreshState.HeaderRefreshing || this.props.refreshState == RefreshState.FooterRefreshing) {
      return false
    }
    return true
  }

  shouldStartFooterRefreshing = () => {
    const {refreshState, data} = this.props
    if (data.length == 0) {
      return false
    }
    return (refreshState == RefreshState.Idle)
  }

  renderSeparator = () => (
    <View style={{height: 1, backgroundColor: '#e0e0e0'}} />
  )

  renderFooter = () => {
    let footer = null

    let {
      footerRefreshingText,
      footerFailureText,
      footerNoMoreDataText,
      footerEmptyDataText,

      footerRefreshingComponent,
      footerFailureComponent,
      footerNoMoreDataComponent,
      footerEmptyDataComponent,
    } = this.props

    switch (this.props.refreshState) {
      case RefreshState.Idle: {
        footer = (<View style={styles.footerContainer} />)
        break
      }
      case RefreshState.Failure: {
        footer = (
          <TouchableOpacity onPress={() => {
            if (this.props.data.length == 0) {
              this.props.onHeaderRefresh && this.props.onHeaderRefresh(RefreshState.HeaderRefreshing)
            } else {
              this.props.onFooterRefresh && this.props.onFooterRefresh(RefreshState.FooterRefreshing)
            }
          }}
          >
            {footerFailureComponent ? footerFailureComponent : (
              <View style={styles.footerContainer}>
                <Text style={styles.footerText}>{footerFailureText}</Text>
              </View>
            )}
          </TouchableOpacity>
        )
        break
      }
      case RefreshState.EmptyData: {
        footer = (
          <TouchableOpacity onPress={() => {
            this.props.onHeaderRefresh && this.props.onHeaderRefresh(RefreshState.HeaderRefreshing)
          }}
          >
            {footerEmptyDataComponent ? footerEmptyDataComponent : (
              <View style={styles.footerContainer}>
                <Text style={styles.footerText}>{footerEmptyDataText}</Text>
              </View>
            )}
          </TouchableOpacity>
        )
        break
      }
      case RefreshState.FooterRefreshing: {
        footer = footerRefreshingComponent ? footerRefreshingComponent : (
          <View style={styles.footerContainer} >
            <ActivityIndicator size="small" color="#888888" />
            <Text style={[styles.footerText, {marginLeft: 7}]}>{footerRefreshingText}</Text>
          </View>
        )
        break
      }
      case RefreshState.NoMoreData: {
        footer = footerNoMoreDataComponent ? footerNoMoreDataComponent : (
          <View style={styles.footerContainer} >
            <Text style={styles.footerText}>{footerNoMoreDataText}</Text>
          </View>
        )
        break
      }
    }
    return footer
  }

  render() {
    const {renderItem, ...rest} = this.props
    return (
      <FlatList
        ref={this.props.listRef}
        {...rest}
        // 行与行之间的分隔线组件
        ItemSeparatorComponent={this.props.disabledSeparator?false:this.renderSeparator}
        // 列表为空时渲染该组件
        ListEmptyComponent={this.props.ListEmptyComponent}
        // 头部组件
        ListHeaderComponent={this.props.renderHeader}
        // 尾部组件
        ListFooterComponent={this.renderFooter}
        // 当列表被滚动到距离内容最底部不足onEndReachedThreshold的距离时调用
        onEndReached={this.onEndReached}
        // 刷新组件
        refreshControl={
          this.props.disabledHeaderRefresh?false:<RefreshControl
          colors={['#00ff00',"#9Bd35A", "#689F38",]}
          refreshing={this.props.refreshState == RefreshState.HeaderRefreshing}
          onRefresh={this.onHeaderRefresh}
        />}
        // 决定当距离内容最底部还有多远时触发onEndReached回调
        onEndReachedThreshold={0.1}
        // 根据行数据data,渲染每一行的组件
        renderItem={renderItem}
      />
    )
  }
}

const styles = StyleSheet.create({
  footerContainer: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    padding: 10,
    height: 44,
  },
  footerText: {
    fontSize: 14,
    color: '#555555',
  },
})

export {
  RefreshState,
}

export default RefreshListView;

2.页面调用

(1)定义全局变量

// 刷新状态
global.RefreshState = {
  Idle: 0, // 加载成功
  HeaderRefreshing: 1, // 开始下拉刷新
  FooterRefreshing: 2, // 开始上拉翻页
  NoMoreData: 3, // 加载全部数据
  Failure: 4, // 加载失败
  EmptyData: 5, // 服务器没有数据
}

(2)通用store

@observable
refreshState: any;

/**
 * 改变refreshState的值
 * @param refreshState
 */
@action setRefreshState(refreshState) {
  this.refreshState = refreshState
}

(3)当前 store

// 加载成功
this.setRefreshState(RefreshState.Idle);

if(!res.data.topics.length){
  // 服务器没有数据
  this.setRefreshState(RefreshState.EmptyData);
}

(4)页面

const { data, refreshState, loadData, loadMoreData } = this.store;

// 新闻列表
store = new NewsStore();
 
// 子组件渲染
_renderRow(obj) {
  let item = obj.item;
  return (
    <ListRow
      key={item.id}
      title={item.title}
      onPress={() => {
        // 跳转详情页
        Actions.homeDetailPage({detail: item})
      }}
    />
  )
}

<RefreshListView
  data={toJS(data)}
  keyExtractor={(item,index) => index.toString()}
  renderItem={this._renderRow.bind(this)}

  refreshState={refreshState}
  onHeaderRefresh={loadData.bind(this.store)}
  onFooterRefresh={loadMoreData.bind(this.store)}
/>

3.效果图

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值