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

