├── index.js ├── .DS_Store ├── package.json ├── LICENSE ├── README.md └── PageListView.js /index.js: -------------------------------------------------------------------------------- 1 | import PageListView from './PageListView'; 2 | export default PageListView; -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geek-prince/react-native-page-listview/HEAD/.DS_Store -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-page-listview", 3 | "version": "1.2.0", 4 | "description": "对ListView/FlatList的封装,可以很方便的分页加载网络数据,还支持自定义下拉刷新View和上拉加载更多的View", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "dependencies": { 10 | "react-native-gp-style": "^1.0.1" 11 | }, 12 | "keywords": [ 13 | "react", 14 | "native", 15 | "react-native", 16 | "rn", 17 | "RN", 18 | "react", 19 | "native", 20 | "listview", 21 | "ListView", 22 | "FlatList", 23 | "flatlist", 24 | "page", 25 | "page-listview", 26 | "page-flatlist", 27 | "refresh", 28 | "refresh-listview", 29 | "refresh-flatlist" 30 | ], 31 | "repository": { 32 | "type": "git", 33 | "url": "https://github.com/geek-prince/react-native-page-listview.git" 34 | }, 35 | "author": "geek-prince", 36 | "license": "MIT" 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2018 geek-prince 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-page-listview 2 | 3 | 对ListView/FlatList的封装,可以很方便的分页加载网络数据,还支持自定义下拉刷新View和上拉加载更多的View.兼容高版本FlatList和低版本ListVIew.组件会根据你使用的react-native的版本自动选择(高版本使用FlatList,低版本使用ListView) 4 | 5 | github地址: https://github.com/geek-prince/react-native-page-listview 6 | 7 | npm地址: https://www.npmjs.com/package/react-native-page-listview 8 | 9 | #1.1.0->1.2.0改动/新增: 10 | ``` 11 | 1.修复从数据为空的界面切换为有数据的界面时的bug 12 | 2.支持无数据情况下的下拉刷新 13 | 3.让用户自己决定是否要显示下拉刷新界面(根据大家反馈希望可以自定义是否显示下拉刷新,所以就加入了)在组件数组中加入refreshEnable属性(布尔值)表示 14 | ``` 15 | 16 | ## 安装 17 | `npm install react-native-page-listview --save` 18 | 19 | ## 如何使用 20 | 21 | `下面说明中的'组件'指的就是当前这个'react-native-page-listview'组件.` 22 | 23 | 首先导入组件 24 | 25 | `import PageListView from 'react-native-page-listview';` 26 | 27 | ### 1.不分页,不从网络获取数据(用于本地数组数据的展示) 28 | 29 | 这时你只需要给组件传递一个数组 30 | 31 | `let arr=[你要在ListView上展示的数据数组]` 32 | 33 | 在render方法中使用该组件 34 | 35 | ``` 36 | 40 | ``` 41 | 42 | `renderRow`方法中需要你指定每一行数据的展示View,与`ListView/FlatList`的`renderRow/renderItem`方法相同 43 | 44 | ``` 45 | renderRow=(rowData,index)=>{ 46 | return(你的View展示); 47 | } 48 | ``` 49 | 50 | `refresh`方法中指定需要展示数据的数组 51 | 52 | ``` 53 | refresh=(callBack)=>{ 54 | callBack(arr); 55 | } 56 | ``` 57 | 58 | ### 2.不分页,从网络获取数据(用于网络数组数据不多,后端接口没有用分页时) 59 | 60 | 这时与上面使用方法一致,只需要更改一下`refresh`方法 61 | 62 | ``` 63 | refresh=(callBack)=>{ 64 | fetch(url) 65 | .then((response)=>response.json()) 66 | .then((responseData)=>{ 67 | //根据接口返回结果得到数据数组 68 | let arr=responseData.result; 69 | callBack(arr); 70 | }); 71 | } 72 | ``` 73 | 74 | 以上这两种方式渲染结果如下(没有下拉刷新和上拉更多): 75 | 76 | ![(没有分页的渲染效果)](http://github.jikeclub.com/pageListView/s1.gif) 77 | 78 | github上加载不出来或显示有问题,请点击这里: http://github.jikeclub.com/pageListView/s1.gif 79 | 80 | ### 3.从网络获取数据并分页,不自定义上拉刷新,下拉加载更多View(用于数据较多,需要分页请求数据时) 81 | 82 | 这时你需要给组件一下几个属性`pageLen`,`renderRow`,`refresh`,`loadMore`. 83 | 84 | ``` 85 | 91 | ``` 92 | 93 | `pageLen`指定你每次调用后端分页接口可以获得多少条数据. 94 | `renderRow`使用方法和上面相同,渲染每一行的展示. 95 | `refresh`方法会在你组件一开始加载和你下拉刷新时调用,所以你在这个方法中需要将你从后端分页接口的第一页请求返回的数据通过回调传给组件. 96 | 97 | ``` 98 | refresh=(callBack)=>{ 99 | fetch(分页接口url+'?page=1') 100 | .then((response)=>response.json()) 101 | .then((responseData)=>{ 102 | //根据接口返回结果得到数据数组 103 | let arr=responseData.result; 104 | callBack(arr); 105 | }); 106 | } 107 | ``` 108 | 109 | `loadMore`方法会在你下拉加载更多时调用,这时除了`callBack`还会传给你另一个参数`page`表示即将要加载的分页数据是第几页,这时你只需要根据`page`把相应第几页的数组数据通过回调传给组件就行. 110 | 111 | ``` 112 | loadMore=(page,callback)=>{ 113 | fetch(分页接口url+'?page='+page) 114 | .then((response)=>response.json()) 115 | .then((responseData)=>{ 116 | //根据接口返回结果得到数据数组 117 | let arr=responseData.result; 118 | callBack(arr); 119 | }); 120 | }; 121 | ``` 122 | 123 | 这种情况下显示的渲染效果为: 124 | 125 | ![(有分页不自定义View的渲染效果)](http://github.jikeclub.com/pageListView/s2.gif) 126 | 127 | github上加载不出来或显示有问题,请点击这里: http://github.jikeclub.com/pageListView/s2.gif 128 | 129 | ### 4.从网络获取数据并分页,并且自定义下拉刷新,上拉加载更多View 130 | 131 | 渲染下拉刷新View使用`renderRefreshView`,且此时需要给定它的高度`renderRefreshViewH`,渲染加载更多View使用`renderLoadMore`,渲染没有更多数据的View使用`renderNoMore`. 132 | 133 | ``` 134 | 145 | ``` 146 | 147 | ``` 148 | renderRefreshView=()=>{ 149 | return( 150 | //你对渲染下拉刷新View的代码 151 | ); 152 | }; 153 | ``` 154 | 155 | ``` 156 | renderLoadMore=()=>{ 157 | return( 158 | //你对渲染加载更多View的代码 159 | ); 160 | }; 161 | ``` 162 | 163 | ``` 164 | renderNoMore=()=>{ 165 | return( 166 | //你对渲染没有更多数据时View的代码 167 | ); 168 | }; 169 | ``` 170 | 171 | 这种情况下显示的渲染效果为: 172 | 173 | ![(有分页自定义View的渲染效果)](http://github.jikeclub.com/pageListView/s3.gif) 174 | 175 | github上加载不出来或显示有问题,请点击这里: http://github.jikeclub.com/pageListView/s3.gif 176 | 177 | ## 拓展 178 | 179 | ### 下拉刷新进阶 180 | 181 | 如果你想实现更好看更绚丽的下拉刷新效果,可以像下面这样使用`renderRefreshView`. 182 | 183 | `pullState`会根据你下拉的状态给你返回相应的字符串: 184 | 185 | * `''` : 没有下拉动作时的状态 186 | * `'pulling'` : 正在下拉并且还没有拉到指定位置时的状态 187 | * `'pullOk'` : 正在下拉并且拉到指定位置时并且没有松手的状态 188 | * `'pullRelease'` : 下拉到指定位置后并且松手后的状态 189 | 190 | ``` 191 | renderRefreshView=(pullState)=>{ 192 | switch (pullState){ 193 | case 'pullOk': 194 | return 195 | //下拉刷新,下拉到指定的位置时,你渲染的View 196 | ; 197 | break; 198 | case 'pullRelease': 199 | return 200 | //下拉刷新,下拉到指定的位置后,并且你松手后,你渲染的View 201 | ; 202 | break; 203 | case '': 204 | case 'pulling': 205 | default: 206 | return 207 | //下拉刷新,你正在下拉时还没有拉到指定位置时(或者默认情况下),你渲染的View 208 | ; 209 | break; 210 | } 211 | }; 212 | ``` 213 | 214 | 这种情况下显示的渲染效果为: 215 | 216 | ![(有分页自定义复杂下拉刷新View的渲染效果)](http://github.jikeclub.com/pageListView/s4.gif) 217 | 218 | github上加载不出来或显示有问题,请点击这里: http://github.jikeclub.com/pageListView/s4.gif 219 | 220 | ### 对数据数组进行处理 221 | 222 | 有时候我们不一定会直接渲染从后端取回来的数据,需要对数据进行一些处理,这时可以在组件中加入`dealWithDataArrCallBack`属性来对数组数据进行一些处理.下面是把从后端取回来的数组进行顺序的颠倒. 223 | 224 | ``` 225 | {return arr.reverse()}} 228 | /> 229 | ``` 230 | 231 | 上面对数组的操作只会在分页请求数据的"上拉刷新"和"下拉加载更多"时触发.有的时候我们可能要在某个点击事件等操作之后对数据数组进行操作,修改.这个时候就可以通过主动调用组件的`changeDataArr`方法来实现.这时需要先对组件进行ref引用 232 | 233 | ``` 234 | {!this.PL&&(this.PL=r)}} 237 | /> 238 | ``` 239 | 240 | 然后在执行某个操作时需要修改数组数据时通过主动调用`changeDataArr`方法来实现 241 | 242 | ``` 243 | this.PL1.changeDataArr((arr)=>{return arr.reverse()}); 244 | ``` 245 | 246 | ### 手动刷新数据 247 | 248 | 这种情况通常用于组件显示数据分类中的某一类的情况,然后父组件中更改了筛选的分类,父组件中获得数组数据需要手动把数据传给组件,并刷新组件,这个时候可以用到`manualRefresh`这个方法,这个方法也需要先像上面一样获取到组件的ref引用.然后在父组件获得数据数组`res`后 249 | 250 | ``` 251 | this.PL.manualRefresh(res); 252 | ``` 253 | 手动刷新组件 254 | 255 | ### 没有一条数据时的渲染 256 | 257 | 如果获取到的数据数组为空,可以通过`renderEmpty`方法来渲染这种情况下要显示的View 258 | 259 | ``` 260 | 264 | renderEmpty=()=>{return(你的View渲染代码);} 265 | ``` 266 | 267 | ### 小功能 268 | 269 | 另外,`FlatList`中有个属性为`ItemSeparatorComponent`是设置每一行View之间分割的View的,自己觉得不错就写到组件里了,兼容`ListView`. 270 | 271 | 如果需要把组件放到scrollView中时加入`inScrollView={true}`的属性,但此时便不能使用上拉刷新,下拉加载更多. 272 | 273 | 如果你需要自己指定是否启用下拉刷新,这时只需要加入属性`refreshEnable={true}`加启用下拉刷新功能了. 274 | 275 | ## 属性一览表: 276 | 277 | | props | 作用 |默认值| 278 | | :-------------: |:-------------:|:-------------:| 279 | |renderRow|处理"渲染FlatList/ListView的每一行"的方法|null| 280 | |refresh|处理"下拉刷新"或"一开始加载数据"的方法|null| 281 | |loadMore|处理"加载更多"的方法|null| 282 | |pageLen|每个分页的数据数|0| 283 | |allLen|总的数据条数|0| 284 | |dealWithDataArrCallBack|如果需要在用当前后端返回的数组数据进行处理的话,传入回调函数|null| 285 | |renderLoadMore|还有数据可以从后端取得时候渲染底部View的方法|null| 286 | |renderNoMore|没有数据(数据已经从后端全部加载完)是渲染底部View的方法|null| 287 | |renderRefreshView|渲染下拉刷新的View样式|null| 288 | |renderRefreshViewH|渲染下拉刷新的View样式的高度|60| 289 | |renderEmpty|如果网络获取数据为空时的渲染界面|null| 290 | |ItemSeparatorComponent|渲染每行View之间的分割线View|null| 291 | |inScrollView|当前组件是否是放在scrollView中(放在ScrollView中时则不能上拉刷新,下拉加载更多)|false| 292 | |refreshEnable|当前是否要启用下拉刷新功能(如果用户在父组件给出该参数就以该参数的值为准,没有给出,就按默认)|默认有分页就启用true,没有分页就不启用false| 293 | |FlatList/ListView自身的属性|是基于FlatList/ListView,所以可以直接使用他们自身的属性| 294 | 295 | `注意:如果屏幕下方有绝对定位的View时,这时组件自适应宽高,下面的一部分内容会被遮挡,这时一个很好的解决方法是在组件下方渲染一个与绝对定位等高的透明View来解决().` 296 | 297 | 如果大家觉得我的组件好用的话,帮到你的话,欢迎大家Star,Fork,如果有什么问题的话也可以在github中想我提出,谢谢大家的支持. 298 | 299 | -------------------------------------------------------------------------------- /PageListView.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import {Text,View,ListView,FlatList,Dimensions,PanResponder,Animated,Easing,ActivityIndicator} from 'react-native'; 3 | import Styles from 'react-native-gp-style'; 4 | let {s,sf}=new Styles(0); 5 | 6 | let PageList=FlatList||ListView; 7 | //获取屏幕宽高 8 | const {width:w, height:h}=Dimensions.get('window'); 9 | 10 | //pullState对应的相应的文字说明 11 | const pullStateTextArr={ 12 | 'noPull':'', 13 | 'pulling':'下拉刷新...', 14 | 'pullOk':'释放以刷新...', 15 | 'pullRelease':'正在刷新,请稍等...', 16 | }; 17 | //默认动画时长 18 | const defaultDuration=400; 19 | 20 | //1.1.0->1.2.0改动/修复/新增: 21 | /* 22 | 1.修复从数据为空的界面切换为有数据的界面时的bug 23 | 2.支持无数据情况下的下拉刷新 24 | 3.让用户自己决定是否要显示下拉刷新界面(根据大家反馈希望可以自定义是否显示下拉刷新,所以就加入了) 25 | *.缩减renderListView方法中的代码,把style样式的编写方式改为react-native-gp-style 26 | */ 27 | 28 | export default class PageListView extends Component{ 29 | constructor(props){ 30 | super(props); 31 | let {refreshEnable,pageLen}=this.props; 32 | this.refreshEnable=refreshEnable!==undefined?refreshEnable:!!pageLen; 33 | this.state={ 34 | //DataSource数据源对应的数组数据 35 | dataArr:[], 36 | //ListView的数据源 37 | dataSource: this.props.isListView?new ListView.DataSource({ 38 | rowHasChanged: (r1, r2)=>r1 !== r2 39 | }):[], 40 | //下面两个参数来决定是否可以调用加载更多的方法 41 | //ListView/FlatView中标识是否可以加载更多(当现在获取到的数据已经是全部了,不能再继续获取数据了,则设为false,当还有数据可以获取则设为true) 42 | canLoad: false, 43 | //标识现在是否ListView/FlatView现在正在加载(根据这个值来决定是否显示"正在加载的cell")(loadMore()方法进去后设为true,fetch加载完数据后设为false) 44 | isLoadding:false, 45 | //是否显示下拉刷新的cell 46 | ifShowRefresh:false, 47 | //ListView/FlatList是否可以滚动 48 | scrollEnabled:true, 49 | //记录当前加载到了哪一页 50 | page:2, 51 | 52 | //通过View自适应的宽高来决定ListView的宽高(或让用户来决定宽高) 53 | // width:this.props.width||0, 54 | // height:this.props.height||0, 55 | width:0, 56 | height:0, 57 | 58 | //下拉的状态 59 | pullState:'noPull', 60 | pullAni:new Animated.Value(-this.props.renderRefreshViewH), 61 | 62 | //网络获取的数据是否为空 63 | ifDataEmpty:false, 64 | }; 65 | //创建手势相应者 66 | this.refreshEnable&&(this.panResponder = PanResponder.create({ 67 | onMoveShouldSetPanResponder: this.onMoveShouldSetPanResponder, 68 | onPanResponderMove: this.onPanResponderMove, 69 | onPanResponderRelease: this.onPanResponderRelease, 70 | onPanResponderTerminate: this.onPanResponderRelease, 71 | onShouldBlockNativeResponder: ()=>false 72 | })); 73 | //下拉到什么位置时算拉到OK的状态 74 | this.pullOkH=parseInt(this.props.renderRefreshViewH*1.5); 75 | //记录ListView最后一次滚动停止时的y坐标 76 | this.lastListY=0; 77 | } 78 | static defaultProps={ 79 | //当前控件是否为ListView 80 | isListView:PageList===ListView, 81 | //父组件处理"渲染FlatList/ListView的每一行"的方法 82 | renderRow:null, 83 | //父组件处理"下拉刷新"或"一开始加载数据"的方法 84 | refresh:null, 85 | //父组件处理"加载更多"的方法 86 | loadMore:null, 87 | //每个分页的数据数 88 | pageLen:0, 89 | //总的数据条数 90 | allLen:0, 91 | 92 | //如果父组件中包含绝对定位的View时传入ListView的高度 93 | //或者可以在父组件底部加入相应高度的透明View 94 | // height:0, 95 | // width:0, 96 | 97 | //如果需要在用当前后端返回的数组数据进行处理的话,传入回调函数 98 | dealWithDataArrCallBack:null, 99 | //如果在进行某个操作后需要对数组数据进行手动处理的话,传入回调函数 100 | // changeDataArr:null, 101 | //渲染每行View之间的分割线View 102 | ItemSeparatorComponent:null, 103 | //还有数据可以从后端取得时候渲染底部View的方法 104 | renderLoadMore:null, 105 | //没有数据(数据已经从后端全部加载完)是渲染底部View的方法 106 | renderNoMore:null, 107 | //渲染下拉刷新的View样式 108 | renderRefreshView:null, 109 | //渲染下拉刷新的View样式的高度 110 | renderRefreshViewH:60, 111 | 112 | //如果网络获取数据为空时的渲染界面 113 | renderEmpty:null, 114 | 115 | //当前组件是否是放在scrollView中(放在ScrollView中时则不能上拉刷新,下拉加载更多) 116 | inScrollView:false, 117 | 118 | //当前是否要启用下拉刷新功能(如果用户在父组件给出该参数就以该参数的值为准,没有给出,就按默认有分页就启用,没有分页就不启用) 119 | // refreshEnable:true, 120 | 121 | //是否隐藏当前ListView 122 | // ifHide:false, 123 | }; 124 | 125 | //取到View自适应的宽高设置给ListView 126 | onLayout=(event)=>{ 127 | if(this.state.width&&this.state.height){return} 128 | let {width, height} = event.nativeEvent.layout; 129 | this.setState({width,height}); 130 | }; 131 | 132 | render() { 133 | let {renderEmpty,renderRefreshViewH,renderRefreshView,inScrollView}=this.props; 134 | let {ifDataEmpty,width:WS,height:HS,pullState,pullAni}=this.state; 135 | if(inScrollView){return this.renderListView()} 136 | return( 137 | !!this.refreshEnable? 138 | 139 | {this.aniView=aniView}} style={[{transform:[{translateY:pullAni}]},sf.swh(WS,HS+renderRefreshViewH)]}> 140 | {renderRefreshView?renderRefreshView(pullState):this.renderRefreshView()} 141 | 142 | {ifDataEmpty&&renderEmpty?renderEmpty():this.renderListView()} 143 | 144 | 145 | : 146 | {ifDataEmpty&&renderEmpty?renderEmpty():this.renderListView()} 147 | 148 | ); 149 | } 150 | 151 | //ListView/FlatList的渲染 152 | renderListView=()=>{ 153 | let {dataSource,scrollEnabled}=this.state; 154 | let {isListView,pageLen}=this.props; 155 | let {renderFooter,renderRow,renderItem,renderItemS,onEndReached,onScroll}=this; 156 | let props={...this.props,style:{}}; 157 | if(!isListView){ 158 | props={...props,renderItem,data:dataSource,ItemSeparatorComponent:renderItemS,keyExtractor:(item,index)=>index.toString(),scrollEnabled,onScroll,}; 159 | if(pageLen){ 160 | props={...props,onEndReached,onEndReachedThreshold:0.05,ListFooterComponent:renderFooter,ref:list=>{this.list=list}}; 161 | } 162 | }else { 163 | props={...props,dataSource,renderRow,enableEmptySections:true,scrollEnabled,onScroll}; 164 | if(pageLen){ 165 | props={...props,onEndReached,onEndReachedThreshold:10,renderFooter,ref:list=>{this.list=list}}; 166 | } 167 | } 168 | return 169 | }; 170 | 171 | 172 | componentDidMount(){ 173 | this.resetAni(); 174 | this.props.refresh((res)=>{ 175 | if(!this.dealWithArr(res)){return} 176 | let len=res.length; 177 | this.updateData(res,len); 178 | }); 179 | // console.log(this.state.scrollEnabled); 180 | } 181 | 182 | //当快要接近底部时加载更多 183 | onEndReached=()=> { 184 | if (this.state.canLoad && !this.state.isLoadding) { 185 | this.loadMore(); 186 | } 187 | }; 188 | //加载更多 189 | loadMore=()=>{ 190 | this.setState({isLoadding: true}); 191 | let page = this.state.page; 192 | this.props.loadMore(page,(res)=>{ 193 | let len=res.length; 194 | this.setState({isLoadding:false,page:this.state.page+1}); 195 | this.updateData(res,len,true); 196 | }); 197 | }; 198 | 199 | //刷新 200 | refreshCommon=(res)=>{ 201 | this.setState({page:2,ifShowRefresh:false,pullState:'noPull'}); 202 | this.resetAni(); 203 | if(!this.dealWithArr(res)){return} 204 | let len=res.length; 205 | this.updateData(res,len); 206 | }; 207 | //下拉刷新 208 | refresh=()=>{ 209 | this.props.refresh((res)=>{ 210 | this.refreshCommon(res) 211 | }); 212 | }; 213 | //手动刷新(父组件中通过该组件的ref调用) 214 | manualRefresh=(res)=>{ 215 | this.refreshCommon(res); 216 | }; 217 | 218 | //判断传入的数据是否为数组,或数组是否为空 219 | dealWithArr=(res)=>{ 220 | let isArr=Array.isArray(res); 221 | if(!isArr){this.setState({ifDataEmpty:true});console.warn('PageListView的数据源需要是一个数组');return false;} 222 | let len=res.length; 223 | if(!len){this.setState({ifDataEmpty:true});return false;} 224 | this.setState({ifDataEmpty:false}); 225 | return true; 226 | }; 227 | 228 | //ListView渲染每一行的cell 229 | renderRow=(rowData,group,index)=>{ 230 | let {renderRow,ItemSeparatorComponent,pageLen,allLen}=this.props; 231 | let notLast=parseInt(index)!==this.state.dataArr.length-1; 232 | let ifRenderItemS=false; 233 | if(ItemSeparatorComponent){ 234 | if(allLen){ 235 | ifRenderItemS=parseInt(index)!==allLen-1; 236 | }else { 237 | ifRenderItemS=(pageLen&&(this.state.canLoad||notLast))||(!pageLen&¬Last); 238 | } 239 | } 240 | // let ifRenderItemS=this.props.ItemSeparatorComponent&&((this.props.pageLen&&(this.state.canLoad||notLast))||(!this.props.pageLen&¬Last)); 241 | return ({renderRow(rowData,index)}{ifRenderItemS&&ItemSeparatorComponent()}); 242 | }; 243 | //FlatList渲染每一行的cell 244 | renderItem=({item,index})=>{ 245 | return this.props.renderRow(item,index); 246 | }; 247 | 248 | //渲染cell之间的分割线组件 249 | renderItemS=()=>{ 250 | return this.props.ItemSeparatorComponent&&this.props.ItemSeparatorComponent(); 251 | }; 252 | 253 | //正在加载的cell 254 | renderFooter=()=>{ 255 | if (!this.state.canLoad) { 256 | if(this.props.renderNoMore){ 257 | return this.props.renderNoMore(); 258 | }else { 259 | return ( 260 | 261 | 没有更多数据了... 262 | 263 | ); 264 | } 265 | } else { 266 | if(this.props.renderLoadMore){ 267 | return this.props.renderLoadMore(); 268 | }else { 269 | return ( 270 | 271 | 272 | {this.state.isLoadding?'正在加载中,请稍等':'上拉加载更多'}... 273 | 274 | ); 275 | } 276 | } 277 | }; 278 | 279 | //更新状态机 280 | updateData=(res,len,loadMore=false)=>{ 281 | let dataArr=[]; 282 | let {pageLen,allLen}=this.props; 283 | if(loadMore){ 284 | for(let i=0;ithis.state.dataArr):(pageLen?(len===pageLen):false), 295 | }); 296 | }; 297 | 298 | //如果在进行某个操作后需要对数组数据进行手动处理的话,调用该方法(通过ref来调用refs={(r)=>{!this.PL&&(this.PL=r)}}) 299 | changeDataArr=(callBack)=>{ 300 | let arr=JSON.parse(JSON.stringify(this.state.dataArr)); 301 | let dataArr=callBack(arr); 302 | this.setState({ 303 | dataArr:dataArr, 304 | dataSource:this.props.isListView?this.state.dataSource.cloneWithRows(dataArr):dataArr, 305 | }); 306 | }; 307 | 308 | //ListView/FlatList滚动时的方法 309 | onScroll=(e)=>{ 310 | if(!this.refreshEnable){return} 311 | this.lastListY=e.nativeEvent.contentOffset.y; 312 | this.lastListY<=0&&this.setState({scrollEnabled:false}) 313 | }; 314 | 315 | //开始移动时判断是否设置当前的View为手势响应者 316 | onMoveShouldSetPanResponder=(e,gesture)=> { 317 | // if(!this.props.pageLen)return false; 318 | let {dy}=gesture; 319 | let bool; 320 | if(dy<0){//向上滑 321 | if(this.state.pullState!=='noPull'){ 322 | this.resetAni(); 323 | } 324 | !this.state.scrollEnabled&&this.setState({scrollEnabled:true}); 325 | bool=false; 326 | }else {//向下拉 327 | if(this.state.pullState!=='noPull'){ 328 | bool=true; 329 | }else { 330 | bool=!this.state.scrollEnabled||this.lastListY<1; 331 | } 332 | } 333 | return bool; 334 | }; 335 | 336 | //手势响应者的View移动时 337 | onPanResponderMove=(e,gesture)=>{ 338 | this.dealWithPan(e,gesture); 339 | }; 340 | dealWithPan=(e,gesture)=>{ 341 | let {dy}=gesture; 342 | if(dy<0){//向上滑 343 | if(this.state.pullState!=='noPull'){ 344 | this.resetAni(); 345 | }else { 346 | !this.state.scrollEnabled&&this.setState({scrollEnabled:true}) 347 | } 348 | }else {//向下拉 349 | let pullDis=gesture.dy/2; 350 | let pullOkH=this.pullOkH; 351 | let aniY=pullDis-this.props.renderRefreshViewH; 352 | this.state.pullAni.setValue(aniY); 353 | if(pullDis>pullOkH){ 354 | this.setState({pullState:'pullOk'}) 355 | }else if(pullDis>0){ 356 | this.setState({pullState:'pulling'}) 357 | } 358 | } 359 | }; 360 | 361 | //手势响应者被释放时 362 | onPanResponderRelease=(e,gesture)=>{ 363 | switch (this.state.pullState){ 364 | case 'pulling': 365 | this.resetAni(); 366 | this.setState({scrollEnabled:true}); 367 | break; 368 | case 'pullOk': 369 | this.resetAniTop(); 370 | this.setState({pullState:'pullRelease',scrollEnabled:true}); 371 | this.refresh(); 372 | break; 373 | } 374 | }; 375 | 376 | //重置位置 refreshView刚好隐藏的位置 377 | resetAni=()=>{ 378 | if(!this.refreshEnable){return} 379 | this.setState({pullState:'noPull'}); 380 | // this.state.pullAni.setValue(this.defaultXY); 381 | this.resetList(); 382 | Animated.timing(this.state.pullAni, { 383 | toValue: -this.props.renderRefreshViewH, 384 | // toValue: this.defaultXY, 385 | easing: Easing.linear, 386 | duration: defaultDuration/2 387 | }).start(); 388 | }; 389 | //重置位置 refreshView刚好显示的位置 390 | resetAniTop=()=>{ 391 | this.resetList(); 392 | Animated.timing(this.state.pullAni, { 393 | toValue: 0, 394 | // toValue: {x:0,y:0}, 395 | easing: Easing.linear, 396 | duration: defaultDuration/2 397 | }).start(); 398 | }; 399 | //重置ListView/FlatList位置 400 | resetList=()=>{ 401 | this.list&&(this.props.isListView?this.list.scrollTo({y:0}):this.list.scrollToOffset({offset:0})); 402 | }; 403 | //滚动ListView/FlatList位置 404 | scrollList=(y)=>{ 405 | this.list&&(this.props.isListView?this.list.scrollTo({y:y}):this.list.scrollToOffset({offset:y})); 406 | }; 407 | 408 | //渲染默认的下拉刷新View 409 | renderRefreshView=()=>{ 410 | return( 411 | 412 | 413 | {pullStateTextArr[this.state.pullState]} 414 | 415 | ); 416 | }; 417 | 418 | } 419 | 420 | /* 421 | 待解决问题: 422 | 1.点击无响应:部分手机真机上点击不能触发onPress事件的bug 423 | bug可能出现出现原因: 424 | 1.应该是当界面滚动到最上方时外层View变成了手势响应者,(此时ListView和FlatList的"scrollEnabled"为false),此时当点击TouchableOpacity的组件时,点击事件被拦截成了手势响应者View的移动事件,所以没有触发onPress事件 425 | 2.因为模拟器上都能正常运行,而是部分手机上会出现这个问题.所以也有可能是因为两者运行的js解析器不一样的关系. 426 | 开发过程中使用 Chrome 调试时,所有的 JavaScript 代码都运行在 Chrome 中,并且通过 WebSocket 与原生代码通信。此时的运行环境是[V8 引擎] 427 | 而在手机上时React Native 使用的是[JavaScriptCore],也就是 Safari 所使用的 JavaScript 引擎。 428 | 解决方法: 429 | 尝试1(失败):将"onStartShouldSetPanResponderCapture"设置为false,这样View刚被触碰时就不会成为手势相应者,便可以实现点击事件."onStartShouldSetPanResponderCapture"会在"捕获期"触发事件(即在点击区域的子组件也有手势事件时也会触发该方法),而"onStartShouldSetPanResponder"方法只有在点击区域的子组件没有手势事件时才触发 430 | 2.修复界面其他组件尺寸改变时,该组件没有跟着一起改变自适应尺寸 431 | */ 432 | --------------------------------------------------------------------------------