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