1. 程式人生 > >react native自定義實現下拉重新整理上拉載入

react native自定義實現下拉重新整理上拉載入

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&&notLast);
      }
    }
    // let ifRenderItemS=this.props.ItemSeparatorComponent&&((this.props.pageLen&&(this.state.canLoad||notLast))||(!this.props.pageLen&&notLast));
    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>
    );
  }