UNPKG

react-native-swrefresh-hm

Version:

react native pull refresh push loadMore scrollView,listView下拉刷新下拉加载控件,支持自定义,简单好用

648 lines (580 loc) 16.8 kB
/** * Created by sww on 2016/10/21. */ import React, { Component,PropTypes} from 'react'; import { StyleSheet, Text, View, ListView, 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='; /** * 下拉刷新默认状态文字 * @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 } /** *===============================================SwRefreshListView===================================== */ export class SwRefreshListView extends ListView{ _isLoading = false _offsetY=0 _isRefreshing=false _dragFlag = false; //scrollview是否处于拖动状态的标志 // 构造 constructor(props) { super(props); // 初始状态 this.state = { isShowHeader: false, arrowAngle:new Animated.Value(0), refresStatus:RefreshStatus.pullToRefresh, refreshTitle:RefreshTitle.pullToRefresh, date:'暂无更新', loadStatus:LoadMoreStatus.finish, }; } static propTypes={ //-----------------下拉刷新用于代码提示------------------------------------- /** * 刷新数据时的操作, 参数 end:function 操作结束时执行end() 以结束刷新状态 */ onRefresh:PropTypes.func, /** * 需要返回一个自定义的刷新视图, * 参数: * refresStatus:RefreshStatus, 0 下拉以刷新 1 松开以刷新 2 刷新中 * offsetY:number 下拉的距离 用于自定义刷新 */ customRefreshView:PropTypes.func, /** * 如果自定义刷新视图 需要传递这个视图的高度 默认视图情况下 此属性无效 */ customRefreshViewHeight:PropTypes.number, containerHeight: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 }) } //--------------------1.0.7新增公开方法------------------ /** * 手动调用刷新 */ beginRefresh(){ let height = this.props.customRefreshView ? this.props.customRefreshViewHeight:70 if (!this._isRefreshing){ this._isRefreshing=true this.setState({ refresStatus:RefreshStatus.refreshing, refreshTitle:RefreshTitle.refreshing }) this.refs.listView.scrollTo({x:0,y:-height,animated:true}); if(this.props.onRefresh){ this.props.onRefresh(()=>{ 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 } listViewHeight(e) { const { containerHeight } = this.props; if (this.state.androidBom>0) { return; } const onLayoutHeight = e.layout.height; this.setState({ isShowHeader: true, }, () => { setTimeout(()=>{ this.refs.listView.scrollTo({x: 0, y: 70, animated: true}) }, 100) let androidBom = containerHeight - onLayoutHeight; console.log(androidBom) if (androidBom === 40) { androidBom = 0; } if (androidBom>0) { this.setState({ androidBom: androidBom, }, () => { setTimeout(()=>{ this.refs.listView.scrollTo({x: 0, y: 70, animated: true}) }, 100) }); } this.setState({ onLayoutHeight, }); }) } render(){ // console.log(this.props.containerHeight) // console.log(this.state.androidBom) // console.log(this.state.onLayoutHeight) return( <View> <ListView ref="listView" onLayout={({ nativeEvent: e }) => { this.listViewHeight(e); }} contentContainerStyle={{ paddingBottom: this.state.androidBom || 0}} showsVerticalScrollIndicator={false} enableEmptySections={true} {...this.props} scrollEventThrottle={16} onScroll={this._onscroll.bind(this)} onScrollEndDrag={(event)=>this._onScrollEndDrag(event)} onScrollBeginDrag={(event)=>this._onScrollBeginDrag(event)} renderHeader={()=>{ return this._rendRefreshheader() }} onMomentumScrollEnd={(event)=>this._onScrollEndDrag(event,true)} onEndReachedThreshold={2} onEndReached={(event)=>this._onEndReached(event)} renderFooter={ ()=>this._rendrFooter() } > </ListView> </View> ) } //-----------------------上拉加载部分------------------------------- /** * 渲染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:70 this.initializationTimer = setTimeout(()=>{ // this.refs.listView.scrollTo({x: 0, y: height, animated: true}) },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(){ if (!this.state.isShowHeader){ return ( <View style={{height:0.5}}> <View></View> </View> ) } else { return( <View> {this.props.customRefreshView?this._renderCustomHeader():this._renderDefaultHeader()} {this.props.renderHeader?this.props.renderHeader():null} </View> ) } } /** * 渲染自定义的刷新视图 * @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 let height = this.props.customRefreshView ? this.props.customRefreshViewHeight:70 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:70 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.scrollTo({x:0,y:0,animated:true}); if(this.props.onRefresh){ this.props.onRefresh(()=>{ this._onRefreshEnd() }) }else { this._onRefreshEnd() } }else { if (y <= height) { this.refs.listView.scrollTo({x: 0, y: height, animated: true}); } } }else { if (y <= height) { this.refs.listView.scrollTo({x:0,y:0,animated:!inertia}); } } if(this.props.onScrollEndDrag){ this.props.onScrollEndDrag(event) } } _onScrollBeginDrag(event){ this._dragFlag = true let height = this.props.customRefreshView ? this.props.customRefreshViewHeight:70 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(); let height = this.props.customRefreshView ? this.props.customRefreshViewHeight:70 this.refs.listView.scrollTo({x:0,y:height,animated:true}); } componentDidUnMount() { 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:70, 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 } });