1.封裝 Scroller 組件
src/components/Scroller/index.js
/** * 下拉刷新/上拉加載更多 組件(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' } });
3.效果圖