react-native 模仿原生 實現下拉刷新/上拉加載更多(RefreshListView)

1.下拉刷新/上拉加載更多 組件(RefreshListView)javascript

src/components/RefreshListView/index.jsjava

/**
 * 下拉刷新/上拉加載更多 組件(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.頁面調用node

(1)定義全局變量react

// 刷新狀態
global.RefreshState = {
  Idle: 0, // 加載成功
  HeaderRefreshing: 1, // 開始下拉刷新
  FooterRefreshing: 2, // 開始上拉翻頁
  NoMoreData: 3, // 加載所有數據
  Failure: 4, // 加載失敗
  EmptyData: 5, // 服務器沒有數據
}

(2)通用storereact-native

@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)頁面flex

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.效果圖ui

相關文章
相關標籤/搜索