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. UVA 1593: Alignment of Code(模拟 Grade D)

    题意: 格式化代码.每个单词对齐,至少隔开一个空格. 思路: 模拟.求出每个单词最大长度,然后按行输出. 代码: #include <cstdio> #include <cstdli ...

  2. shell 练习 (免密钥登陆脚本)

    脚本说明 本地服务器ip 10.0.0.5 远程服务器地址 10.0.0.223 #!/bin/bashremote_ip=$ if [ ! -n "$1" ] ;then ech ...

  3. 【原创】BI解决方案选型之ETL数据整合工具对比

    一.背景 在企业BI平台建设过程中,数据整合始终是一切的基础,简单BI项目可以通过存储过程来实现,而复杂.全面.多方异构数据来源等就大大增加了复杂性,存储过程的可管理性.可维护性.容错性等就无法很好的 ...

  4. Laravel使用Eloquent ORM操作数据库

    1.定义模型 <?php namespace App; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ p ...

  5. JDK7集合框架源码阅读(七) ArrayDeque

    基于版本jdk1.7.0_80 java.util.ArrayDeque 代码如下 /* * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to li ...

  6. 51nod 算法马拉松 34 Problem D 区间求和2 (FFT加速卷积)

    题目链接  51nod 算法马拉松 34  Problem D 在这个题中$2$这个质数比较特殊,所以我们先特判$2$的情况,然后仅考虑大于等于$3$的奇数即可. 首先考虑任意一个点对$(i, j)$ ...

  7. HDU-6315 Naive Operations//2018 Multi-University Training Contest 2___1007 (线段树,区间除法)

    原题地址 Naive Operations Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 502768/502768 K (Java/ ...

  8. 理解竞争条件( Race condition)漏洞

    这几天一个叫做"Dirty COW"的linux内核竞争条件漏洞蛮火的,相关公司不但给这个漏洞起了个洋气的名字,还给它设计了logo(见下图),首页,Twitter账号以及网店.恰 ...

  9. RMI,socket,rpc,hessian,http比较

    SOCKET使用时可以指定协议TCP,UDP等: RIM使用JRMP协议,JRMP又是基于TCP/IP: RPC底层使用SOCKET接口,定义了一套远程调用方法: HTTP是建立在TCP上,不是使用S ...

  10. 使用CXF开发RESTFul服务

    相信大家在阅读CXF官方文档(http://cxf.apache.org/docs/index.html)时,总是一知半解.这里向大家推荐一本PacktPub.Apache.CXF.Web.Servi ...