1.PageListView 组件封装

src/components/PageListView/index.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: false,
//标识现在是否ListView/FlatView现在正在加载(根据这个值来决定是否显示"正在加载的cell")(loadMore()方法进去后设为true,fetch加载完数据后设为false)
isLoadding:false,
//是否显示下拉刷新的cell
ifShowRefresh:false,
//ListView/FlatList是否可以滚动
scrollEnabled:true,
//记录当前加载到了哪一页
page:2, //通过View自适应的宽高来决定ListView的宽高(或让用户来决定宽高)
// width:this.props.width||0,
// height:this.props.height||0,
width:0,
height:0, //下拉的状态
pullState:'noPull',
pullAni:new Animated.Value(-this.props.renderRefreshViewH), //网络获取的数据是否为空
ifDataEmpty:false,
};
//创建手势相应者
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:60, //如果网络获取数据为空时的渲染界面
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)=>{
if(!this.dealWithArr(res)){return}
let len=res.length;
this.updateData(res,len);
});
} //当快要接近底部时加载更多
willReachEnd=()=> {
if (this.state.canLoad && !this.state.isLoadding) {
this.loadMore();
}
}; //加载更多
loadMore=()=>{
this.setState({isLoadding: true});
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:2,ifShowRefresh:false,pullState:'noPull'});
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.isLoadding} color='#333' size='small' style={{marginRight:7}}/>
<Text allowFontScaling={false} style={{color: '#000', fontSize: 12,}}>{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.页面调用

<PageListView
pageLen={10}
renderRow={this._renderRow.bind(this)}
refresh={this._refresh.bind(this)}
loadMore={this._loadMore.bind(this)}
/> // 20180730 刷新
_refresh(callBack){
// fetch(分页接口url+'?page=1')
// .then((response)=>response.json())
// .then((responseData)=>{
// //根据接口返回结果得到数据数组
// let arr=responseData.result;
// callBack(arr);
// }); request
.get(config.api.base + config.api.comment, {
accessToken: 'abc',
page: 1,
creation: '123'
})
.then((data) => {
//根据接口返回结果得到数据数组
let arr = data.data;
callBack(arr);
})
.catch((error) => {
console.log('请求失败!');
})
} // 20180730 加载更多
_loadMore(page,callBack){
// fetch(分页接口url+'?page='+page)
// .then((response)=>response.json())
// .then((responseData)=>{
// //根据接口返回结果得到数据数组
// let arr=responseData.result;
// callBack(arr);
// }); request
.get(config.api.base + config.api.comment, {
accessToken: 'abc',
page: page,
creation: '123'
})
.then((data) => {
//根据接口返回结果得到数据数组
let arr = data.data;
callBack(arr);
})
.catch((error) => {
console.log(error);
})
} // 20180730 子组件渲染
_renderRow(row) {
return (
<CommentItem row={row} />
)
}

3.效果图

react-native ListView 封装 实现 下拉刷新/上拉加载更多的更多相关文章

  1. SwipeRefreshLayout实现下拉刷新上滑加载

    1. 效果图 2.RefreshLayout.java package myapplication.com.myapplication; import android.content.Context; ...

  2. Android 下拉刷新上啦加载SmartRefreshLayout + RecyclerView

    在弄android刷新的时候,可算是耗费了一番功夫,最后发觉有现成的控件,并且非常好用,这里记录一下. 原文是 https://blog.csdn.net/huangxin112/article/de ...

  3. juery下拉刷新,div加载更多元素并添加点击事件(二)

    buffer.append("<div class='col-xs-3 "+companyId+"' style='padding-left: 10px; padd ...

  4. 移动端下拉刷新上拉加载-mescroll.js插件

    最近无意间看到有这么一个上拉刷新下拉加载的插件 -- mescroll.js,个人感觉挺好用的,官网地址是:http://www.mescroll.com 然后我就看了一下文档,简单的写了一个小dem ...

  5. Android如何定制一个下拉刷新,上滑加载更多的容器

    前言 下拉刷新和上滑加载更多,是一种比较常用的列表数据交互方式. android提供了原生的下拉刷新容器 SwipeRefreshLayout,可惜样式不能定制. 于是打算自己实现一个专用的.但是下拉 ...

  6. Android 自定义 ListView 上下拉动“刷新最新”和“加载更多”歌曲列表

    本文内容 环境 测试数据 项目结构 演示 参考资料 本文演示,上拉刷新最新的歌曲列表,和下拉加载更多的歌曲列表.所谓"刷新最新"和"加载更多"是指日期.演示代码 ...

  7. RecyclerView下拉刷新上拉加载(三)—对Adapter的封装

    RecyclerView下拉刷新上拉加载(一) http://blog.csdn.net/baiyuliang2013/article/details/51506036 RecyclerView下拉刷 ...

  8. react-native-page-listview使用方法(自定义FlatList/ListView下拉刷新,上拉加载更多,方便的实现分页)

    react-native-page-listview 对ListView/FlatList的封装,可以很方便的分页加载网络数据,还支持自定义下拉刷新View和上拉加载更多的View.兼容高版本Flat ...

  9. ListView实现Item上下拖动交换位置 并且实现下拉刷新 上拉加载更多

    ListView实现Item上下拖动交换位置  并且实现下拉刷新  上拉加载更多 package com.example.ListViewDragItem; import android.app.Ac ...

  10. ListView下拉刷新上拉加载更多实现

    这篇文章将带大家了解listview下拉刷新和上拉加载更多的实现过程,先看效果(注:图片中listview中的阴影可以加上属性android:fadingEdge="none"去掉 ...

随机推荐

  1. 斗地主(NOIP2015)

    原题传送门 神奇的题目.. 一开始我准备打暴力直接搜答案. 然后发现.. 无限TLE.. 因为O((logN)^14*T)BOOM.. 然后Zxyer告诉可以只DFS顺子...其他的可以一步搞出来.. ...

  2. snmp 默认团体名检测利用

    [root@izj6c2n1nth4tg8emd8h58z tmp]# lsb_release LSB Version:    :core-4.1-amd64:core-4.1-noarch[root ...

  3. C 语言中char* 和const char*的区别

    const char *p = "123"; p[1] = '3'; // 会报错p = "456"; // 不会报错 const char * 只是说指针指向 ...

  4. 华为上机测试题(MP3光标移动-java)

    PS:此题满分,可参考 描述: MP3 Player因为屏幕较小,显示歌曲列表的时候每屏只能显示几首歌曲,用户要通过上下键才能浏览所有的歌曲.为了简化处理,假设每屏只能显示4首歌曲,光标初始的位置为第 ...

  5. 【原创】DQS安装失败——系统重新引导是否处于挂起状态

    问题:         安装完SQL Server 2012后,准备安装DQS服务,但是总是提示:操作“检查系统重新引导是否处于挂起状态”已完成,但有错误,正在中止安装.非常无奈,因为都根据其要求重启 ...

  6. AC日记——Count on a tree II spoj

    Count on a tree II 思路: 树上莫队: 先分块,然后,就好办了: 来,上代码: #include <cmath> #include <cstdio> #inc ...

  7. (8)java基础知识-字符编码

    这块比较乱待整理... http://www.regexlab.com/zh/encoding.htm 字符编码 将各种文字.图形.标点.数字整合在一个集合叫做字符集. 把这些字符集按照不用规则进行编 ...

  8. Codeforces Beta Round #25 (Div. 2 Only) A. IQ test【双标记/求给定数中唯一的奇数或偶数】

    A. IQ test time limit per test 2 seconds memory limit per test 256 megabytes input standard input ou ...

  9. flow JavaScript 静态类型检查工具

    内置类型 flow 内置类型有 boolean, number, string, null, void, any, mixed, literal type. 其中 boolean, number, s ...

  10. C++ 二位数组做参数传递

    指针的强大功能,,,,简直牛逼!!! #include<iostream> #include<cstdio> #include<map> using namespa ...