react native自定義實現下拉重新整理上拉載入
阿新 • • 發佈:2018-11-09
1·定義元件
PageListView.js
/** * 上拉重新整理/下拉載入更多 元件 */ import React, { Component } from 'react'; import { Text, View, ListView, FlatList, Dimensions, PanResponder, Animated, Easing, ActivityIndicator, } from 'react-native'; let PageList=FlatList||ListView; //獲取螢幕寬高 let {width:w, height:h}=Dimensions.get('window'); //pullState對應的相應的文字說明 const pullStateTextArr={ 'noPull':'正在重新整理,請稍等...', 'pulling':'下拉重新整理...', 'pullOk':'釋放以重新整理...', 'pullRelease':'正在重新整理,請稍等...', }; //預設動畫時長 const defaultDuration=400; //1.0.3->1.1.0改動/新增: /* 1.手動處理陣列資料, 2.父元件重新載入資料後手動重新整理資料 2.隱藏當前ListView(放棄這個功能), 3.從網路獲取資料,資料為空時的渲染介面, 4.解決部分手機上介面為空,不顯示的問題,(鑑於自定義元件寬高實用性並不大,而且部分手機顯示有問題,去除自定義元件寬高,改為自適應)(問題可能原因:從flex:1快速的改變為固定寬高時介面渲染會有問題) 5.對放在scrollView中的支援 6.加入可選屬性allLen,對於分頁顯示時可以指定資料的總條數 */ export default class PageListView extends Component{ constructor(props){ super(props); this.state={ //DataSource資料來源對應的陣列資料 dataArr:[], //ListView的資料來源 dataSource: this.props.isListView?new ListView.DataSource({ rowHasChanged: (r1, r2)=>r1 !== r2 }):[], //下面兩個引數來決定是否可以呼叫載入更多的方法 //ListView/FlatView中標識是否可以載入更多(當現在獲取到的資料已經是全部了,不能再繼續獲取資料了,則設為false,當還有資料可以獲取則設為true) canLoad: true, //標識現在是否ListView/FlatView現在正在載入(根據這個值來決定是否顯示"正在載入的cell")(loadMore()方法進去後設為true,fetch載入完資料後設為false) isLoadding:false, //是否顯示下拉重新整理的cell ifShowRefresh:true, //ListView/FlatList是否可以滾動 scrollEnabled:true, //記錄當前載入到了哪一頁 page:1, //通過View自適應的寬高來決定ListView的寬高(或讓使用者來決定寬高) // width:this.props.width||0, height:this.props.height||0, width:0, // height:0, //下拉的狀態 pullState:'pullRelease', pullAni:new Animated.Value(-this.props.renderRefreshViewH), //網路獲取的資料是否為空 ifDataEmpty:false, first:true }; //建立手勢相應者 this.panResponder = PanResponder.create({ onMoveShouldSetPanResponder: this.onMoveShouldSetPanResponder, onPanResponderMove: this.onPanResponderMove, onPanResponderRelease: this.onPanResponderRelease, onPanResponderTerminate: this.onPanResponderRelease, onShouldBlockNativeResponder: ()=>false }); //下拉到什麼位置時算拉到OK的狀態 this.pullOkH=parseInt(this.props.renderRefreshViewH*1.5); //記錄ListView最後一次滾動停止時的y座標 this.lastListY=0; } static defaultProps={ //當前控制元件是否為ListView isListView:PageList===ListView, //父元件處理"渲染FlatList/ListView的每一行"的方法 renderRow:null, //父元件處理"下拉重新整理"或"一開始載入資料"的方法 refresh:null, //父元件處理"載入更多"的方法 loadMore:null, //每個分頁的資料數 pageLen:0, //總的資料條數 allLen:0, //如果父元件中包含絕對定位的View時傳入ListView的高度 //或者可以在父元件底部加入相應高度的透明View // height:0, // width:0, //如果需要在用當前後端返回的陣列資料進行處理的話,傳入回撥函式 dealWithDataArrCallBack:null, //如果在進行某個操作後需要對陣列資料進行手動處理的話,傳入回撥函式 // changeDataArr:null, //渲染每行View之間的分割線View ItemSeparatorComponent:null, //還有資料可以從後端取得時候渲染底部View的方法 renderLoadMore:null, //沒有資料(資料已經從後端全部載入完)是渲染底部View的方法 renderNoMore:null, //渲染下拉重新整理的View樣式 renderRefreshView:null, //渲染下拉重新整理的View樣式的高度 renderRefreshViewH:45, //如果網路獲取資料為空時的渲染介面 renderEmpty:null, //當前元件是否是放在scrollView中(放在ScrollView中時則不能上拉重新整理,下拉載入更多) inScrollView:false, //是否隱藏當前ListView // ifHide:false, }; //取到View自適應的寬高設定給ListView onLayout=(event)=>{ if(this.state.width&&this.state.height){return} let {width:w, height:h} = event.nativeEvent.layout; this.setState({width:w,height:h}); }; render() { if(this.state.ifDataEmpty&&this.props.renderEmpty){return this.props.renderEmpty()} if(this.props.inScrollView){return this.renderListView()} return( <View style={[{flex:1},{zIndex:-99999}]} onLayout={this.onLayout}> <Animated.View ref={aniView=>{this.aniView=aniView}} style={[{transform:[{translateY:this.state.pullAni}]},{width:this.state.width,height:this.state.height+this.props.renderRefreshViewH}]}> {this.props.renderRefreshView?this.props.renderRefreshView(this.state.pullState):this.renderRefreshView()} <View style={[{width:this.state.width,height:this.state.height}]} {...this.panResponder.panHandlers}> {this.renderListView()} </View> </Animated.View> </View> ); } //ListView/FlatList的渲染 renderListView=()=>{ if(!this.props.isListView){ if(this.props.pageLen){ return( <PageList {...this.props} style={{}}//雖然不需要樣式,但必須加,這樣才能在檢視更新時呼叫renderFooter方法 data={this.state.dataSource} //當接近ListView的底部時的操作 onEndReached={this.willReachEnd} //當距離底部多少距離時觸發上面的這個方法 注意:在FlatList中此引數是一個比值而非畫素單位。比如,0.5表示距離內容最底部的距離為當前列表可見長度的一半時觸發 onEndReachedThreshold={0.05} //渲染載入更多時,"載入中"的cell ListFooterComponent={this.renderFooter} //渲染每一行的cell怎麼樣顯示 renderItem={this.renderItem} keyExtractor={(item,index)=>index.toString()} scrollEnabled={this.state.scrollEnabled} onScroll={this.onScroll} ref={list=>{this.list=list}} /> ); }else { return( <PageList {...this.props} style={{}}//雖然不需要樣式,但必須加,這樣才能在檢視更新時呼叫renderFooter方法 data={this.state.dataSource} //渲染每一行的cell怎麼樣顯示 renderItem={this.renderItem} ItemSeparatorComponent={this.renderItemS} keyExtractor={(item,index)=>index.toString()} /> ); } }else { if(this.props.pageLen){ return ( <PageList {...this.props} style={{}}//雖然不需要樣式,但必須加,這樣才能在檢視更新時呼叫renderFooter方法 dataSource={this.state.dataSource} //當接近ListView的底部時的操作 onEndReached={this.willReachEnd} //當距離底部多少距離時觸發上面的這個方法 onEndReachedThreshold={10} //渲染載入更多時,"載入中"的cell renderFooter={this.renderFooter} //渲染每一行的cell怎麼樣顯示 renderRow={this.renderRow} //允許空的組,加上就行(不用管) enableEmptySections={true} scrollEnabled={this.state.scrollEnabled} onScroll={this.onScroll} ref={list=>{this.list=list}} /> ); }else { return( <PageList {...this.props} style={{}}//雖然不需要樣式,但必須加,這樣才能在檢視更新時呼叫renderFooter方法 dataSource={this.state.dataSource} //渲染每一行的cell怎麼樣顯示 renderRow={this.renderRow} //允許空的組,加上就行(不用管) enableEmptySections={true} /> ); } } }; componentDidMount(){ this.resetAni(); this.props.refresh((res)=>{ console.log(res,"fffffff") if(!this.dealWithArr(res)){return} let len=res.length; this.updateData(res,len); }); } //當快要接近底部時載入更多 willReachEnd=()=> { console.log(this.state.canLoad,!this.state.isLoadding) if (this.state.canLoad && !this.state.isLoadding) { this.loadMore(); } }; //載入更多 loadMore=()=>{ this.setState({isLoadding: true,first:false}); let page = this.state.page; this.props.loadMore(page,(res)=>{ let len=res.length; this.setState({isLoadding:false,page:this.state.page+1}); this.updateData(res,len,true); }); }; //重新整理 refreshCommon=(res)=>{ if(!this.dealWithArr(res)){return} let len=res.length; this.updateData(res,len); this.setState({page:1,ifShowRefresh:false,pullState:'pullRelease',first:false}); this.resetAni() }; //下拉重新整理 refresh=()=>{ this.props.refresh((res)=>{ this.refreshCommon(res) }); }; //手動重新整理 manualRefresh=(res)=>{ this.refreshCommon(res); }; //判斷傳入的資料是否為陣列,或陣列是否為空 dealWithArr=(res)=>{ let isArr=Array.isArray(res); if(!isArr){this.setState({ifDataEmpty:true});console.warn('PageListView的資料來源需要是一個數組');return false;} let len=res.length; if(!len){this.setState({ifDataEmpty:true});return false;} return true; }; //ListView渲染每一行的cell renderRow=(rowData,group,index)=>{ let {renderRow,ItemSeparatorComponent,pageLen,allLen}=this.props; let notLast=parseInt(index)!==this.state.dataArr.length-1; let ifRenderItemS=false; if(ItemSeparatorComponent){ if(allLen){ ifRenderItemS=parseInt(index)!==allLen-1; }else { ifRenderItemS=(pageLen&&(this.state.canLoad||notLast))||(!pageLen&¬Last); } } // let ifRenderItemS=this.props.ItemSeparatorComponent&&((this.props.pageLen&&(this.state.canLoad||notLast))||(!this.props.pageLen&¬Last)); return (<View>{renderRow(rowData,index)}{ifRenderItemS&&ItemSeparatorComponent()}</View>); }; //FlatList渲染每一行的cell renderItem=({item,index})=>{ return this.props.renderRow(item,index); }; //渲染cell之間的分割線元件 renderItemS=()=>{ return this.props.ItemSeparatorComponent&&this.props.ItemSeparatorComponent(); }; //正在載入的cell renderFooter=()=>{ if (!this.state.canLoad) { if(this.props.renderNoMore){ return this.props.renderNoMore(); }else { return ( <View style={{alignItems: 'center', justifyContent:'center',height:40,width:w,backgroundColor:'#eee'}}> <Text allowFontScaling={false} style={{color: '#000', fontSize: 12}}>沒有更多資料了...</Text> </View> ); } } else { if(this.props.renderLoadMore){ return this.props.renderLoadMore(); }else { return ( <View style={{alignItems: 'center', justifyContent:'center',height:40,width:w,backgroundColor:'#eee',flexDirection:'row'}}> <ActivityIndicator animating={this.state.first?true:this.state.isLoadding} color='#333' size='small' style={{marginRight:7}}/> <Text allowFontScaling={false} style={{color: '#000', fontSize: 12,}}>{this.state.first?'正在載入中,請稍等':this.state.isLoadding?'正在載入中,請稍等':'上拉載入更多'}...</Text> </View> ); } } }; //更新狀態機 updateData=(res,len,loadMore=false)=>{ let dataArr=[]; let {pageLen,allLen}=this.props; if(loadMore){ for(let i=0;i<len;i++){ this.state.dataArr.push(res[i]); } }else { this.state.dataArr=res; } !!this.props.dealWithDataArrCallBack?(dataArr=this.props.dealWithDataArrCallBack(this.state.dataArr)):dataArr=this.state.dataArr; this.setState({ dataArr:dataArr, dataSource:this.props.isListView?this.state.dataSource.cloneWithRows(dataArr):dataArr, canLoad:allLen?(allLen>this.state.dataArr):(pageLen?(len===pageLen):false), }); }; //如果在進行某個操作後需要對陣列資料進行手動處理的話,呼叫該方法(通過ref來呼叫refs={(r)=>{!this.PL&&(this.PL=r)}}) changeDataArr=(callBack)=>{ let arr=JSON.parse(JSON.stringify(this.state.dataArr)); let dataArr=callBack(arr); this.setState({ dataArr:dataArr, dataSource:this.props.isListView?this.state.dataSource.cloneWithRows(dataArr):dataArr, }); }; //ListView/FlatList滾動時的方法 onScroll=(e)=>{ this.lastListY=e.nativeEvent.contentOffset.y; this.lastListY<=0&&this.setState({scrollEnabled:false}) }; //開始移動時判斷是否設定當前的View為手勢響應者 onMoveShouldSetPanResponder=(e,gesture)=> { if(!this.props.pageLen)return false; let {dy}=gesture; let bool; if(dy<0){//向上滑 if(this.state.pullState!=='noPull'){ this.resetAni(); } !this.state.scrollEnabled&&this.setState({scrollEnabled:true}); bool=false; }else {//向下拉 if(this.state.pullState!=='noPull'){ bool=true; }else { bool=!this.state.scrollEnabled||this.lastListY<1; } } return bool; }; //手勢響應者的View移動時 onPanResponderMove=(e,gesture)=>{ this.dealWithPan(e,gesture); }; dealWithPan=(e,gesture)=>{ let {dy}=gesture; if(dy<0){//向上滑 if(this.state.pullState!=='noPull'){ this.resetAni(); }else { !this.state.scrollEnabled&&this.setState({scrollEnabled:true}) } }else {//向下拉 let pullDis=gesture.dy/2; let pullOkH=this.pullOkH; let aniY=pullDis-this.props.renderRefreshViewH; this.state.pullAni.setValue(aniY); if(pullDis>pullOkH){ this.setState({pullState:'pullOk'}) }else if(pullDis>0){ this.setState({pullState:'pulling'}) } } }; //手勢響應者被釋放時 onPanResponderRelease=(e,gesture)=>{ switch (this.state.pullState){ case 'pulling': this.resetAni(); this.setState({scrollEnabled:true}); break; case 'pullOk': this.resetAniTop(); this.setState({pullState:'pullRelease',scrollEnabled:true}); this.refresh(); break; } }; //重置位置 refreshView剛好隱藏的位置 resetAni=()=>{ this.setState({pullState:'noPull'}); // this.state.pullAni.setValue(this.defaultXY); this.resetList(); Animated.timing(this.state.pullAni, { toValue: -this.props.renderRefreshViewH, // toValue: this.defaultXY, easing: Easing.linear, duration: defaultDuration/2 }).start(); }; //重置位置 refreshView剛好顯示的位置 resetAniTop=()=>{ this.resetList(); Animated.timing(this.state.pullAni, { toValue: 0, // toValue: {x:0,y:0}, easing: Easing.linear, duration: defaultDuration/2 }).start(); }; //重置ListView/FlatList位置 resetList=()=>{ this.list&&(this.props.isListView?this.list.scrollTo({y:0}):this.list.scrollToOffset({offset:0})); }; //滾動ListView/FlatList位置 scrollList=(y)=>{ this.list&&(this.props.isListView?this.list.scrollTo({y:y}):this.list.scrollToOffset({offset:y})); }; //渲染預設的下拉重新整理View renderRefreshView=()=>{ return( <View style={{height:60,width:w,justifyContent:'center',alignItems:'center',backgroundColor:'#eee',flexDirection:'row'}}> <ActivityIndicator animating={this.state.pullState==='pullRelease'} color='#333' size='small' style={{marginRight:7}}/> <Text allowFontScaling={false} style={{color:'#333',fontSize:15}}>{pullStateTextArr[this.state.pullState]}</Text> </View> ); }; }
2·使用
引入 import PageListView from './PageListView'
方法:
// 20180730 重新整理 async _refresh(callBack){ const histo = await historys("?form.tree_node_operation="+0); console.log(this.state.userId,"this.state.userId") const datas = "?form.userId="+histo.form.userId+"&pageSize=10&curPage=0"; const result = await awaitdeteal(datas); console.log(result.form.dataList,"1111111111111111") callBack(result.form.dataList.reverse()); if(result&&result.form.dataList.length>0){ this.setState({ result:this.state.result.concat(result.form.dataList),//序列化:轉換為一個 (字串)JSON字串 }); } } // 20180730 載入更多 async _loadMore(page,callBack){ const histo = await historys("?form.tree_node_operation="+0); console.log(this.state.userId,"this.state.userId") const datas = "?form.userId="+histo.form.userId+"&pageSize=10&curPage="+page; const result = await awaitdeteal(datas); console.log(result.form.dataList,"222222222222222222") callBack(result.form.dataList); if(result&&result.form.dataList.length>0){ this.setState({ result:this.state.result.concat(result.form.dataList),//序列化:轉換為一個 (字串)JSON字串 }); } } // 20180730 子元件渲染 _renderRow(itemdata) { // <CommentItem row={itemdata} /> 下面這個是子元件的例子 return ( <View onPress={()=>this.gotoItem(itemdata)} style={{marginTop:5,paddingBottom:10,paddingTop:10,width:"90%",marginLeft:20,height:220}}> <Text style={{color:"#000000"}}>兩票型別:{itemdata.tickettypename}</Text> <Text style={{color:"#000000"}}>負責人:{itemdata.headuser}</Text> <Text style={{color:"#000000"}}>編號:{itemdata.ticketserialnum}</Text> <Text style={{color:"#000000"}}>流轉人:{itemdata.manageuser}</Text> <View><Text style={{color:"#000000"}}>內容:</Text></View> <Text numberOfLines={10} style = {{paddingBottom:15,borderColor:"#eeeeee",borderWidth:1,borderStyle:"solid",color:"#000000"}}>{itemdata.content}</Text> <Text style={{color:"#000000"}}>等待時間:{itemdata.manageTime}</Text> <Text style={{color:"#000000"}}>流轉時間:{itemdata.lastTime}</Text> </View> ) }
3具體使用
render() { let height = this.state.result.length * 100; return ( <View style={{flex:1}}> {/* 需要迴圈獲取資料 */} <PageListView height={height} pageLen={15} //定義獲取資料的長度,當獲取的長度小於這個時,顯示沒有更多資料 renderRow={this._renderRow.bind(this)} refresh={this._refresh.bind(this)} loadMore={this._loadMore.bind(this)} /> </View> ); }