实现下拉刷新的效果,同时使用FlatList, 源码:
以下代码太粗,近期抽空会出一个图文并茂的,并上传实例代码。。这里先标一下。
JJRefreshFlatList
import React, { Component} from 'react';
import PropTypes from 'prop-types';
import {
StyleSheet,
Text,
View,
FlatList,
ActivityIndicator,
Animated,
Easing,
Dimensions,
Image,
AsyncStorage,
} from 'react-native';
const {width,height}=Dimensions.get('window')
const dateKey = 'SwRefresh_date'
const ArrowImage = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAABQBAMAAAD8TNiNAAAAJ1BMVEUAAACqqqplZWVnZ2doaGhqampoaGhpaWlnZ2dmZmZlZWVmZmZnZ2duD78kAAAADHRSTlMAA6CYqZOlnI+Kg/B86E+1AAAAhklEQVQ4y+2LvQ3CQAxGLSHEBSg8AAX0jECTnhFosgcjZKr8StE3VHz5EkeRMkF0rzk/P58k9rgOW78j+TE99OoeKpEbCvcPVDJ0OvsJ9bQs6Jxs26h5HCrlr9w8vi8zHphfmI0fcvO/ZXJG8wDzcvDFO2Y/AJj9ADE7gXmlxFMIyVpJ7DECzC9J2EC2ECAAAAAASUVORK5CYII=';
const HEADER_HEIGHT = 70;
/**
* 下拉刷新默认状态文字
* @type {{pullToRefresh: string, releaseToRefresh: string, refreshing: string}}
*/
export const RefreshTitle={
pullToRefresh:'下拉以刷新',
releaseToRefresh:'松开以刷新',
refreshing:'正在刷新数据'
}
import dateFormat from './Tools'
/**
* 下拉刷新状态//0 下拉以刷新 1 松开以刷新 2 刷新中
* @type {{pullToRefresh: number, releaseToRefresh: number, refreshing: number}}
*/
export const RefreshStatus={
pullToRefresh:0,
releaseToRefresh:1,
refreshing:2
}
/**
* 上拉加载更多的各状态 1 加载中 2 加载完毕 3 加载完毕 无更多数据
* @type {loading: number, finish: number, noMoreData: number}}
*/
export const LoadMoreStatus={
loading:1,
finish:2,
noMoreData:3
}
export default class JJRefreshFlatList extends Component {
_isLoading = false
_offsetY=0
_isRefreshing=false
_dragFlag = false; //scrollview是否处于拖动状态的标志
// 构造
constructor(props) {
super(props);
// 初始状态
this.state = {
arrowAngle:new Animated.Value(0),
refresStatus:RefreshStatus.pullToRefresh,
refreshTitle:RefreshTitle.pullToRefresh,
date:'暂无更新',
loadStatus:LoadMoreStatus.finish,
showRefreshOK: false, // 显示加载完成
};
}
static propTypes={
//-----------------下拉刷新用于代码提示-------------------------------------
/**
* 刷新数据时的操作, 参数 end:function 操作结束时执行end() 以结束刷新状态
*/
onFlatListRefresh:PropTypes.func,
/**
* 需要返回一个自定义的刷新视图,
* 参数:
* refresStatus:RefreshStatus, 0 下拉以刷新 1 松开以刷新 2 刷新中
* offsetY:number 下拉的距离 用于自定义刷新
*/
customRefreshView:PropTypes.func,
/**
* 如果自定义刷新视图 需要传递这个视图的高度 默认视图情况下 此属性无效
*/
customRefreshViewHeight:PropTypes.number,
//----------------上拉加载-----------------------------
/**
* 自定义底部部刷新指示组件的渲染方法,
* 参数 下拉状态 LoadMoreStatus
*/
customLoadMoreView:PropTypes.func,
/**
* 处于pushing(加载更多时)状态时执行的方法
* 参数:end,最后执行完操作后应该调用end(isNoMoreData)。
* isNoMoreData 表示当前是否已经加载完所有数据 已无更多数据
*/
onLoadMore:PropTypes.func,
/**
* //默认样式中的上拉加载更多的提示语
*/
pusuToLoadMoreTitle:PropTypes.string,
/**
* //默认样式中正在加载的提示语
*/
loadingTitle:PropTypes.string,
/**
* //默认样式中已无更多时的提示语
*/
noMoreDataTitle:PropTypes.string,
/**
* 是否显示底部的加载更多 通常用于全部数据不足一页 底部还显示加载更多导致的难看
*/
isShowLoadMore:PropTypes.bool
}
/**
* 直接将状态置为没有更多数据状态 通常用于第一次刷新加载的后数据已全部加载 不必下拉刷新
* 也可使用 isShowLoadMore:PropTypes.bool将上拉加载组件隐藏
*/
setNoMoreData(){
this.setState({
loadStatus:LoadMoreStatus.noMoreData
})
}
/**
* 重置已无更多数据的状态 通常用于下拉刷新数据完毕后 重置状态
*/
resetStatus(){
this.setState({
loadStatus:LoadMoreStatus.finish
})
}
/**
* 手动调用刷新
*/
beginRefresh(){
let height = this.props.customRefreshView ? this.props.customRefreshViewHeight:HEADER_HEIGHT
if (!this._isRefreshing){
this._isRefreshing=true
this.setState({
refresStatus:RefreshStatus.refreshing,
refreshTitle:RefreshTitle.refreshing
})
this.refs.listView.scrollToOffset({offset:-height,animated:true});
if(this.props.onFlatListRefresh){
this.props.onFlatListRefresh(()=>{
this._onRefreshEnd()
})
}else {
this._onRefreshEnd()
}
}
}
/**
* 手动结束刷新 不推荐 推荐end()回调
*/
endRefresh(){
this._onRefreshEnd()
}
/*
* 手动结束加载
* isNoMoreData 表示当前是否已经加载完所有数据 已无更多数据
* */
endLoadMore(isNoMoreData){
this._isLoading = false
this.setState({
loadStatus:isNoMoreData ? LoadMoreStatus.noMoreData:LoadMoreStatus.finish
})
}
//----------------------------------------------
static defaultProps={
pusuToLoadMoreTitle:'上拉加载更多~~~~',
loadingTitle:'加载中~~~',
noMoreDataTitle:'已经加载到底啦( ̄︶ ̄)~~',
isShowLoadMore:true
}
render(){
return(
<FlatList
ref="listView"
showsVerticalScrollIndicator={false}
enableEmptySections={true}
{...this.props}
scrollEventThrottle={16}
onScroll={this._onscroll.bind(this)}
onScrollEndDrag={(event)=>this._onScrollEndDrag(event)}
onScrollBeginDrag={(event)=>this._onScrollBeginDrag(event)}
ListHeaderComponent={()=>{
return this._rendRefreshheader()
}}
onMomentumScrollEnd={(event)=>this._onScrollEndDrag(event,true)}
onEndReachedThreshold={2}
onEndReached={(event)=>this._onEndReached(event)}
>
</FlatList>
)
}
//-----------------------上拉加载部分-------------------------------
/**
* 渲染footer和上拉加载组件
* @returns {XML}
* @private
*/
_rendrFooter(){
if (!this.props.isShowLoadMore){
if (!this.props.renderFooter){
return (
<View style={{height:0.5}}>
<View></View>
</View>
)
}else {
return (
<View>
{this.props.renderFooter()}
</View>
)
}
}
if (this.props.customLoadMoreView) {
return (
<View>
{this.props.renderFooter?this.props.renderFooter():null}
{this.props.customLoadMoreView(this.state.loadStatus)}
</View>
)
}
if (this.state.loadStatus == LoadMoreStatus.noMoreData){
return(
<View>
{this.props.renderFooter?this.props.renderFooter():null}
<View style={footStyles.footer}>
<Text style={footStyles.footerText}>{this.props.noMoreDataTitle}</Text>
</View>
</View>
)
}else if(this.state.loadStatus == LoadMoreStatus.finish){
return(
<View>
{this.props.renderFooter?this.props.renderFooter():null}
<View style={footStyles.footer}>
{/*<ActivityIndicator/>*/}
<Text style={footStyles.footerText}>{this.props.pusuToLoadMoreTitle}</Text>
</View>
</View>
)
}else if(this.state.loadStatus == LoadMoreStatus.loading){
return(
<View>
{this.props.renderFooter?this.props.renderFooter():null}
<View style={footStyles.footer}>
<ActivityIndicator/>
<Text style={footStyles.footerText}>{this.props.loadingTitle}</Text>
</View>
</View>
)
}
}
/**
* 上刷拉操作
* @param event
* @private
*/
_onEndReached(event){
if (this.state.loadStatus == LoadMoreStatus.noMoreData || this._isLoading || !this.props.isShowLoadMore){
return
}
this._isLoading = true
this.setState({
loadStatus:1
})
this.timer = setTimeout(()=>{
if (this.props.onLoadMore){
this.props.onLoadMore((isNoMoreData)=>{
this._isLoading = false
this.setState({
loadStatus:isNoMoreData ? LoadMoreStatus.noMoreData:LoadMoreStatus.finish
})
})
}
},500)
if(this.props.onEndReached){
this.props.onEndReached(event)
}
}
componentDidMount(){
let height = this.props.customRefreshView ? this.props.customRefreshViewHeight:HEADER_HEIGHT
this.initializationTimer = setTimeout(()=>{
this.refs.listView.scrollToOffset({offset: height, animated: false})
},20)
AsyncStorage.getItem(dateKey, (error, result) => {
if (result) {
result = parseInt(result);
//将时间传入下拉刷新的state
this.setState({
date:dateFormat(new Date(result),'yyyy-MM-dd hh:mm'),
});
}
});
}
//------------------下拉刷新部分-----------------------------
/**
* 渲染头部刷新组件
* @private
*/
_rendRefreshheader(){
return(
<View>
{this._renderRefreshNormalHeader()}
{this._renderRefreshOKHeader()}
</View>
)
}
_renderRefreshNormalHeader() {
if (!this.state.showRefreshOK) {
return (
<View>
{this.props.renderHeader?this.props.renderHeader():null}
{this.props.customRefreshView?this._renderCustomHeader():this._renderDefaultHeader()}
</View>
);
} else {
return null;
}
}
_renderRefreshOKHeader() {
if (this.state.showRefreshOK) {
return(
<View style={{backgroundColor: 'gray', height: HEADER_HEIGHT, alignItems: 'center', justifyContent: 'center'}}>
<Text>加载已完成</Text>
</View>
);
} else {
return null;
}
}
/**
* 渲染自定义的刷新视图
* @returns {*}
* @private
*/
_renderCustomHeader(){
return this.props.customRefreshView(this.state.refresStatus,this._offsetY)
}
/**
* 渲染默认的刷新视图
* @returns {XML}
* @private
*/
_renderDefaultHeader(){
return(
<View style={defaultHeaderStyles.background}>
<View style={defaultHeaderStyles.status}>
{this._rendArrowOrActivity()}
<Text style={defaultHeaderStyles.statusTitle}>{this.state.refreshTitle}</Text>
</View>
<Text style={defaultHeaderStyles.date}>{'上次更新时间:'+this.state.date}</Text>
</View>
)
}
/**
* 渲染菊花或者箭头
* @returns {XML}
* @private
*/
_rendArrowOrActivity(){
if (this.state.refresStatus == RefreshStatus.refreshing){
return (
<ActivityIndicator style={{marginRight:10}}>
</ActivityIndicator>
)
}else {
return(
<Animated.Image
source={{uri:ArrowImage}}
resizeMode={'contain'}
style={[defaultHeaderStyles.arrow,
{transform:[{
rotateZ:this.state.arrowAngle.interpolate({
inputRange:[0,1],
outputRange:['0deg','-180deg']
})
}]
}]}
></Animated.Image>
)
}
}
/**
* 滑动中
* @param event
* @private
*/
_onscroll(event){
let y = event.nativeEvent.contentOffset.y;
if (y > 69 && !this._isRefreshing) {
this.setState({
showRefreshOK: false
});
}
console.log("wk", "onScroll : " + y);
let height = this.props.customRefreshView ? this.props.customRefreshViewHeight:HEADER_HEIGHT
if (y <= height){
this._offsetY = y-height
if (this._dragFlag){
if (!this._isRefreshing){
if (y <= 8){
//松开以刷新
if(this.state.refresStatus!=RefreshStatus.releaseToRefresh){
this.setState({
refresStatus:RefreshStatus.releaseToRefresh,
refreshTitle:RefreshTitle.releaseToRefresh
})
Animated.timing(this.state.arrowAngle, {
toValue: 1,
duration: 50,
easing: Easing.inOut(Easing.quad)
}).start();
}
}else {
//下拉以刷新
if (this.state.refresStatus != RefreshStatus.pullToRefresh){
this.setState({
refresStatus:RefreshStatus.pullToRefresh,
refreshTitle:RefreshTitle.pullToRefresh
})
Animated.timing(this.state.arrowAngle, {
toValue:0,
duration: 100,
easing: Easing.inOut(Easing.quad)
}).start();
}
}
}
}
}
if(this.props.onScroll){
this.props.onScroll(event)
}
}
/**
* 拖拽
* @private
*/
_onScrollEndDrag(event,inertia){
this._dragFlag = false
let y = event.nativeEvent.contentOffset.y
let height = this.props.customRefreshView ? this.props.customRefreshViewHeight:HEADER_HEIGHT
this._offsetY = y-height
if (!this._isRefreshing){
if (this.state.refresStatus == RefreshStatus.releaseToRefresh){
this._isRefreshing=true
this.setState({
refresStatus:RefreshStatus.refreshing,
refreshTitle:RefreshTitle.refreshing
})
this.refs.listView.scrollToOffset({offset:0,animated:true});
if(this.props.onFlatListRefresh){
this.props.onFlatListRefresh(()=>{
this._onRefreshEnd()
})
}else {
this._onRefreshEnd()
}
}else {
if (y <= height) {
this.refs.listView.scrollToOffset({offset: height, animated: true});
}
}
}else {
if (y <= height) {
this.refs.listView.scrollToOffset({offset:0,animated:!inertia});
}
}
if(this.props.onScrollEndDrag){
this.props.onScrollEndDrag(event)
}
}
_onScrollBeginDrag(event){
this._dragFlag = true
let height = this.props.customRefreshView ? this.props.customRefreshViewHeight:HEADER_HEIGHT
this._offsetY = event.nativeEvent.contentOffset.y - height
if (this.props.onScrollBeginDrag){
this.props.onScrollBeginDrag(event)
}
}
/**
* 刷新结束
* @private
*/
_onRefreshEnd(){
this._isRefreshing = false
let now = new Date().getTime();
//下拉以刷新
this.setState({
refresStatus:RefreshStatus.pullToRefresh,
refreshTitle:RefreshTitle.pullToRefresh,
date:dateFormat(now,'yyyy-MM-dd hh:mm')
})
// 存一下刷新时间
AsyncStorage.setItem(dateKey, now.toString());
Animated.timing(this.state.arrowAngle, {
toValue:0,
duration: 100,
easing: Easing.inOut(Easing.quad)
}).start();
this.setState({
showRefreshOK: true,
});
// 自动关闭
this.RefreshOkTimer = setTimeout(
() => {
let height = this.props.customRefreshView ? this.props.customRefreshViewHeight: HEADER_HEIGHT
this.refs.listView.scrollToOffset({offset:height,animated:true});
this.state.showRefreshOK = false;
},
1000
);
}
componentWillUnmount() {
this.RefreshOkTimer && clearTimeout(this.RefreshOkTimer);
this.timer && clearTimeout(this.timer);
this.initializationTimer && clearTimeout(this.initializationTimer)
}
shouldComponentUpdate(nextProps,nextState) {
return nextState !== this.state
}
}
//-------------------------样式-------------------------------
/**
* 默认头部
*/
const defaultHeaderStyles=StyleSheet.create({
background:{
alignItems:'center',
height:HEADER_HEIGHT,
justifyContent:'center',
},
status:{
flexDirection:'row',
alignItems:'center'
},
arrow:{
width:14,
height:23,
marginRight:10
},
statusTitle:{
fontSize:13,
color:'#333333'
},
date:{
fontSize:11,
color:'#333333',
marginTop:5
}
})
const footStyles = StyleSheet.create({
footer:{
height:30,
flexDirection:'row',
justifyContent:'center',
alignItems:'center'
},
footerText:{
fontSize:15,
color:'#999999',
marginLeft:10
}
});
使用类
import React, {Component} from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
Dimensions,
ListView,
FlatList
} from 'react-native';
import JJRefreshFlatList from './components/JJRefreshFlatList';
const {width, height} = Dimensions.get('window')
export default class index extends Component {
_page = 0
_dataSource = new ListView.DataSource({rowHasChanged: (row1, row2) => row1 !== row2})
// 构造
constructor(props) {
super(props);
// 初始状态
this.state = {
dataSource: this._dataSource.cloneWithRows([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
};
}
render() {
return this._renderJJFlatList();
}
_renderItem(rowData) {
return (
<View style={styles.cell}>
<Text>{'这是第' + rowData.index + '行'}</Text>
</View>)
}
_keyExtractor = (item, index) => item.id;
_renderJJFlatList() {
var data = [];
for (var i = 0; i < 100; i++) {
data.push({key: i, title: i + ''});
}
return (
<JJRefreshFlatList
data={data}
extraData={this.state}
keyExtractor={this._keyExtractor}
renderItem={this._renderItem.bind(this)}
ref='listView'
onFlatListRefresh={this._onListRefersh.bind(this)}
onLoadMore={this._onLoadMore.bind(this)}
/>
);
}
/**
* 模拟刷新
* @param end
* @private
*/
_onListRefersh(end) {
let timer = setTimeout(() => {
clearTimeout(timer)
this._page = 0
// let data = []
// for (let i = 0; i < 10; i++) {
// data.push(i)
// }
// this.setState({
// dataSource: this._dataSource.cloneWithRows(data)
// })
this.refs.listView.resetStatus() //重置上拉加载的状态
end()//刷新成功后需要调用end结束刷新
this.refs.listView.endRefresh() //建议使用end() 当然 这个可以在任何地方使用
}, 1500)
}
/**
* 模拟加载更多
* @param end
* @private
*/
_onLoadMore(end) {
let timer = setTimeout(() => {
clearTimeout(timer)
this._page++
let data = []
for (let i = 0; i < (this._page + 1) * 10; i++) {
data.push(i)
}
this.setState({
dataSource: this._dataSource.cloneWithRows(data)
})
//end(this._page > 2)//加载成功后需要调用end结束刷新 假设加载4页后数据全部加载完毕
this.refs.listView.endLoadMore(this._page > 2)
}, 2000)
}
componentDidMount() {
let timer = setTimeout(() => {
clearTimeout(timer)
// this.refs.scrollView.beginRefresh()
this.refs.listView.beginRefresh()
}, 500) //自动调用刷新 新增方法
}
}
const styles = StyleSheet.create({
container: {},
content: {
width: width,
height: height,
backgroundColor: 'yellow',
justifyContent: 'center',
alignItems: 'center'
},
cell: {
height: 100,
backgroundColor: 'purple',
alignItems: 'center',
justifyContent: 'center',
borderBottomColor: '#ececec',
borderBottomWidth: 1
}
})