├── Images └── pullview.gif ├── pull ├── i18n │ ├── All.js │ └── index.js ├── style │ └── index.js ├── PullLayout.js ├── LoadingSpinner.js ├── PullView.js ├── PullList.js ├── index.js └── Pullable.js └── README.md /Images/pullview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuyunqiang/react-native-pullview/HEAD/Images/pullview.gif -------------------------------------------------------------------------------- /pull/i18n/All.js: -------------------------------------------------------------------------------- 1 | export default { 2 | "": { 3 | pulling: "pulling...", 4 | pullok: "pull ok......", 5 | pullrelease: "refreshing......" 6 | }, 7 | "zh_CN": { 8 | pulling: "下拉刷新...", 9 | pullok: "松开刷新......", 10 | pullrelease: "玩命刷新中......" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /pull/style/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { 4 | StyleSheet 5 | } from 'react-native'; 6 | 7 | export default StyleSheet.create({ 8 | wrap: { 9 | flex: 1, 10 | flexGrow: 1, 11 | flexDirection: 'column', 12 | zIndex:-999, 13 | }, 14 | hide: { 15 | position: 'absolute', 16 | left: 10000 17 | }, 18 | show: { 19 | position: 'relative', 20 | left: 0 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /pull/i18n/index.js: -------------------------------------------------------------------------------- 1 | import { Platform, NativeModules } from 'react-native'; 2 | import all from './All.js'; 3 | 4 | function getLocale() { 5 | try { 6 | if (Platform.OS === 'android') { 7 | return NativeModules.I18nManager.localeIdentifier; 8 | } else { 9 | return NativeModules.SettingsManager.settings.AppleLocale; 10 | } 11 | } catch (e) { 12 | return null; 13 | } finally {} 14 | } 15 | function getI18N(v) { 16 | let locale = getLocale(); 17 | if(v[locale]) { 18 | return v[locale]; 19 | } else if(v[""]) { 20 | return v[""]; 21 | } else { 22 | return v["default"]; 23 | } 24 | } 25 | var i18n = getI18N(all); 26 | export default i18n; 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PullView 2 | scrollview&&FlatList Pull refresh and loadmore 3 | 4 | 参考react-native-pull和RefreshListDemo。
5 | android&&ios都可以使用。
6 | # new 7 | android可以使用原生的下拉刷新效果会更好 如下使用:
8 | /** 9 | * PullScroll => scrollview 10 | * PullList =>flatlist 11 | * Android_Native 是否使用android原生下拉刷新组件 true开启 12 | * ****/ 13 | 14 | 如果开启原生属性 需要android引入原生模块
15 | 下拉刷新数据传送的方式有两种
16 | ### method:1 17 | view实例的方式 Key有没有都可以 也不需要js监听事件 只需要复写onPullRelease即可以使用 18 | debug测试可以使用但是在release模式下会有收不到消息的情况,官方原因并不稳定
19 | ### method:2 20 | 原生广播的方式想rn发送数据 ### 因此Key必须有切唯一不重复 ### 需要rn端写事件监听 稳定暂时未发现bug
21 | 具体建议参考:[RNApp](https://github.com/wuyunqiang/RNApp)
22 | ![iosrnapp.gif](https://upload-images.jianshu.io/upload_images/3353755-e6a3dd8b7f3f54e3.gif?imageMogr2/auto-orient/strip) 23 | ![androidgif.gif](https://upload-images.jianshu.io/upload_images/3353755-6dcb27c69b2b345d.gif?imageMogr2/auto-orient/strip) 24 | ``` 25 | 31 | {this.renderView()} 32 | 33 | 34 | 35 | 36 | this.pullList = list} 41 | onEndReachedThreshold={20} 42 | onPullRelease={this.onPullRelease} 43 | onEndReached={this.loadMore} 44 | renderItem={this.renderRowView} 45 | getItemLayout={(data, index) => ({length:230, offset:230 * index, index})} 46 | numColumns={1} 47 | ItemSeparatorComponent={() => { 48 | return null; 49 | }} 50 | initialNumToRender={5} 51 | renderLoading = {()=>{return null;}} 52 | /> 53 | ``` 54 | -------------------------------------------------------------------------------- /pull/PullLayout.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by wuyunqiang on 2018/1/16. 3 | */ 4 | import React, { Component } from 'react'; 5 | import { 6 | AppRegistry, 7 | Platform, 8 | StyleSheet, 9 | Text, 10 | View, 11 | ScrollView, 12 | Image, 13 | UIManager, 14 | TouchableOpacity, 15 | NativeModules, 16 | ImageBackground, 17 | DeviceEventEmitter, 18 | requireNativeComponent, 19 | } from 'react-native'; 20 | const ReactNative = require('ReactNative'); 21 | import PropTypes from 'prop-types'; 22 | var PullLayout = requireNativeComponent('PullLayout', App); 23 | export default class App extends Component { 24 | constructor(props){ 25 | super(props); 26 | } 27 | 28 | //数据获取后回调 刷新结束 29 | finishRefresh = (key)=>{ 30 | Log("结束下拉"+key); 31 | UIManager.dispatchViewManagerCommand(ReactNative.findNodeHandle(this), 32 | UIManager.PullLayout.Commands.FinishRefresh,[key]) 33 | }; 34 | 35 | resolve = ()=>{ 36 | this.finishRefresh(this.props.Key); 37 | }; 38 | 39 | onPullRelease = ()=>{ 40 | this.props.onPullRelease(this.resolve) 41 | }; 42 | 43 | render() { 44 | console.log('PullLayout this.props',this.props) 45 | return ( 46 | {this.pullLayout = pull}} 49 | style={[{flex: 1,backgroundColor:'white',},this.props.style]} 50 | EnableOverScrollDrag = {true} 51 | EnableOverScrollBounce = {false} 52 | DisableContentWhenRefresh = {false} 53 | onRefreshReleased={this.onPullRelease} 54 | {...this.props} 55 | > 56 | 57 | {this.props.children} 58 | 59 | 60 | ) 61 | } 62 | } 63 | 64 | PullLayout.propTypes = { 65 | ...View.propTypes, 66 | Key:PropTypes.string.isRequired,//必须 否则监听回调可能无法被调用 67 | Method:PropTypes.number.isRequired,//使用哪种方式发送消息 1实例方式 2广播消息的方式 Key针对第二种方式 68 | onRefreshReleased:PropTypes.func,//网络请求加载数据 69 | EnableOverScrollDrag:PropTypes.bool,//设置是否启用越界回弹 70 | EnableOverScrollBounce:PropTypes.bool,//设置是否启用越界拖动(仿苹果效果) 71 | DragRate:PropTypes.number, //显示拖动高度/真实拖动高度(默认0.5,阻尼效果) 72 | HeaderMaxDragRate:PropTypes.number,//设置下拉最大高度和Header高度的比率(将会影响可以下拉的最大高度) 73 | HeaderTriggerRate:PropTypes.number,//设置 触发刷新距离 与 HeaderHieght 的比率 74 | ReboundDuration:PropTypes.number,//设置回弹动画时长 75 | EnableRefresh:PropTypes.bool,//是否启用下拉刷新(默认启用) 76 | EnableHeaderTranslationContent:PropTypes.bool,//设置是否启在下拉Header的同时下拉内容 77 | DisableContentWhenRefresh:PropTypes.bool,//设置是否开启在刷新时候禁止操作内容视图 78 | EnablePureScrollMode:PropTypes.bool,//设置是否开启纯滚动模式 79 | EnableNestedScroll:PropTypes.bool,//设置是会否启用嵌套滚动功能(默认关闭+智能开启) 80 | }; -------------------------------------------------------------------------------- /pull/LoadingSpinner.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import {Dimensions, ActivityIndicator,View, Text} from "react-native"; 3 | const {width, height} = Dimensions.get('window'); 4 | export default class LoadingSpinner extends Component { 5 | 6 | static defaultProps = { 7 | width: width, 8 | height: height, 9 | spinnerColor: 'white', 10 | textColor: 'white', 11 | text: '努力加载中...', 12 | backgroundColor:'transparent' 13 | }; 14 | 15 | render() { 16 | if(this.props.type==='normal'){ 17 | return ( 18 | 19 | 20 | 21 | {this.props.text} 22 | 23 | 24 | ) 25 | }else if(this.props.type==='bottom'){ 26 | return ( 27 | 28 | 29 | {this.props.text} 30 | 31 | ) 32 | }else if(this.props.type==='allLoaded'){ 33 | return (没有更多数据了) 34 | } else if(this.props.type==='home'){ 35 | return ( 36 | 37 | ); 38 | 39 | }else{ 40 | return ( 41 | 52 | 58 | 59 | {this.props.text} 60 | 61 | 62 | ); 63 | } 64 | 65 | } 66 | } 67 | 68 | const styles = { 69 | allLoaded:{ 70 | marginLeft: SCALE(20), 71 | marginRight: SCALE(20), 72 | justifyContent:'center', 73 | alignItems:'center', 74 | height:SCALE(100), 75 | backgroundColor:Color.f3f3f3, 76 | }, 77 | statusText:{ 78 | backgroundColor:'transparent', 79 | fontSize:FONT(13), 80 | color:Color.C333333, 81 | } 82 | }; 83 | -------------------------------------------------------------------------------- /pull/PullView.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { Component } from 'react'; 4 | import { 5 | Platform, 6 | ScrollView, 7 | } from 'react-native'; 8 | import Pullable from './Pullable'; 9 | 10 | /** 11 | 支持android&ios可以下拉刷新的PullView组件 12 | Demo: 13 | import {PullView} from 'react-native-pullview'; 14 | 15 | {}} topIndicatorHeight={60} 17 | > 18 | 19 | Demo2: 20 | topIndicatorRender(pulling, pullok, pullrelease) { 21 | return 22 | 23 | {pulling ? 下拉刷新2... : null} 24 | {pullok ? 松开刷新2...... : null} 25 | {pullrelease ? 玩命刷新中2...... : null} 26 | ; 27 | } 28 | 29 | 30 | 31 | 32 | Demo3: 33 | 34 | onRefresh() { 35 | this.setState({refreshing: true}); 36 | return new Promise((resolve) => { 37 | setTimeout(() => {resolve()}, 9000); 38 | }).then(() => { 39 | this.setState({refreshing: false}) 40 | }) 41 | // setTimeout(() => { 42 | // this.setState({refreshing: false}); 43 | // }, 3000); 44 | } 45 | 46 | 47 | 48 | */ 49 | 50 | export default class extends Pullable { 51 | constructor(props) { 52 | super(props); 53 | this.state = { 54 | ...this.BaseState, 55 | }; 56 | const defaultFlag = {pulling: false, pullok: false, pullrelease: false}; 57 | this.setFlag(defaultFlag); 58 | Log("this.state",this.state) 59 | this.scrollTo = this.scrollTo.bind(this); 60 | this.scrollToEnd = this.scrollToEnd.bind(this); 61 | this.type='View'; 62 | } 63 | 64 | scrollTo(...args) { 65 | this.scroll&&this.scroll.scrollTo(...args); 66 | } 67 | 68 | scrollToEnd(args) { 69 | this.scroll&&this.scroll.scrollTo(args); 70 | } 71 | 72 | onContentSizeChange = (contentWidth, contentHeight)=>{ 73 | Log('contentWidth',contentWidth); 74 | Log('contentHeight',contentHeight); 75 | Log('Height',HEIGHT); 76 | this.type='View'; 77 | if(contentHeight {this.scroll = c;}} 88 | refreshControl={refreshControl} 89 | onScroll={this.onScroll} 90 | scrollEnabled={Platform.OS==='android'?this.state.scrollEnabled:true} 91 | scrollEventThrottle={16} 92 | onContentSizeChange = {this.onContentSizeChange} 93 | {...this.props} 94 | > 95 | {this.props.children} 96 | 97 | ); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /pull/PullList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by guzhenfu on 17/5/9. 3 | */ 4 | 5 | import React, { Component } from 'react'; 6 | import { 7 | InteractionManager, 8 | FlatList, 9 | Text, 10 | ActivityIndicator, 11 | View, 12 | Dimensions, 13 | TouchableHighlight, 14 | StyleSheet, 15 | Image, 16 | TouchableOpacity, 17 | NetInfo 18 | } from 'react-native'; 19 | import Pullable from './Pullable'; 20 | import LoadingSpinner from './LoadingSpinner'; 21 | const LoadingState = 1; //初始loading页面 22 | const EmptyState = 2; //空页面 23 | const ErrorState = 3; //加载数据错误 24 | const ListState = 4; //正常加载 25 | const MoreState = 5; //正在加载更多 26 | const NoMoreState = 6; //没有更多了 27 | const NoMoreErrorState = 7; //加载更多出错 28 | 29 | export default class PullList extends Pullable { 30 | 31 | constructor(props) { 32 | super(props); 33 | this.state = { 34 | ...this.BaseState, 35 | data:[], 36 | }; 37 | this.currentState = LoadingState; 38 | this.page = 0; 39 | this.type='List'; 40 | } 41 | 42 | componentDidMount() { 43 | this.mounted = true; 44 | InteractionManager.runAfterInteractions(()=>{ 45 | this.loadMore() 46 | // this.props.onPullRelease(this.resolveHandler); 47 | }) 48 | } 49 | 50 | componentWillMount() { 51 | this.mounted = false; 52 | } 53 | 54 | setPage = (page) => { 55 | this.page = page; 56 | }; 57 | 58 | getPage = () => { 59 | return this.page; 60 | }; 61 | 62 | renderSeparatorView = ()=>{ 63 | if(this.props.ItemSeparatorComponent){ 64 | return this.props.ItemSeparatorComponent(); 65 | } 66 | return () 67 | }; 68 | 69 | /** 70 | * 刷新 71 | */ 72 | refresh = () => { 73 | Log('zhixngzheli'); 74 | if (this.mounted) { 75 | this.setPage(1); 76 | this.props.onPullRelease&&this.props.onPullRelease(this.resolveHandler); 77 | } 78 | }; 79 | 80 | getMetrics = (args)=> { 81 | this.scroll&&this.scroll.getMetrics(args); 82 | }; 83 | 84 | scrollToOffset = (...args)=> { 85 | this.scroll&&this.scroll.scrollToOffset(...args); 86 | }; 87 | 88 | scrollToEnd = (args)=> { 89 | this.scroll&&this.scroll.scrollToEnd(args); 90 | }; 91 | 92 | /** 93 | * 对外提供API,设置列表数据 94 | */ 95 | setData = (_data)=>{ 96 | this.setPage(1); 97 | Log('下拉刷新设置数据 setData'); 98 | if (_data.length == 0){ 99 | this.currentState = EmptyState; 100 | }else{ 101 | this.currentState = ListState; 102 | } 103 | this.setState({ 104 | data: _data, 105 | }) 106 | 107 | }; 108 | 109 | /** 110 | * 对外提供API, loadMore 调用 111 | */ 112 | addData = (_data)=>{ 113 | if (_data.length == 0){ 114 | this.currentState = NoMoreState; 115 | 116 | }else{ 117 | this.currentState = MoreState; 118 | } 119 | this.setState({ 120 | data: this.state.data.concat(_data), 121 | }) 122 | }; 123 | 124 | 125 | 126 | /** 127 | * 对外提供API, 出错重新加载数据 128 | */ 129 | reloadData = ()=>{ 130 | this.currentState = LoadingState; 131 | this.props.onPullRelease&&this.props.onPullRelease(this.resolveHandler); 132 | // this.forceUpdate(); 133 | }; 134 | 135 | /** 136 | * 对外提供API, 加载更多 137 | */ 138 | resumeMoreDataFromError = ()=>{ 139 | this.currentState = MoreState; 140 | this.page++; 141 | this.props.onEndReached&&this.props.onEndReached(this.getPage()); 142 | 143 | Log('this.page',this.page) 144 | }; 145 | 146 | /** 147 | * 加载loading页面 148 | * @returns {XML} 149 | * @private 150 | */ 151 | _renderLoading = ()=> { 152 | return ( 153 | ) 154 | // return ( 155 | // 157 | // 158 | // 159 | // ); 160 | }; 161 | 162 | /** 163 | * 加载 空页面 164 | */ 165 | _renderEmpty = ()=>{ 166 | Log('没有数据'); 167 | return ( 168 | 169 | 170 | 171 | 172 | 暂无数据 173 | 174 | 175 | 176 | ) 177 | }; 178 | 179 | /** 180 | * 加载 出错页 181 | */ 182 | _renderError = ()=>{ 183 | return ( 184 | 185 | 186 | 187 | 188 | 网络无法连接,点击屏幕重试 189 | 190 | 191 | 192 | ) 193 | }; 194 | 195 | //加载更过 196 | loadMore = ()=>{ 197 | if(this.currentState==NoMoreState){ 198 | return; 199 | } 200 | this.page++; 201 | this.props.onEndReached&&this.props.onEndReached(this.getPage()) 202 | 203 | }; 204 | 205 | /** 206 | * 加载列表数据 207 | * @returns {XML} 208 | * @private 209 | */ 210 | _renderList = ()=>{ 211 | return ( 212 | {this.scroll = c;}} 215 | onScroll={this.onScroll} 216 | scrollEnabled={this.state.scrollEnabled} 217 | refreshing={false} 218 | keyExtractor={(item, index) => {return index.toString()}} 219 | onEndReachedThreshold={0.5} 220 | data={this.state.data} 221 | ListFooterComponent={this._renderFoot} 222 | windowSize={10} 223 | updateCellsBatchingPeriod={1} 224 | maxToRenderPerBatch={10} 225 | disableVirtualization={false} 226 | {...this.props} 227 | ItemSeparatorComponent={this.renderSeparatorView} 228 | onEndReached = {this.loadMore}/> 229 | ); 230 | }; 231 | 232 | renderNoMore = ()=>{ 233 | return ( 234 | 没有更多数据 235 | ) 236 | }; 237 | 238 | renderMoreError = ()=>{ 239 | return ( 240 | {this.resumeMoreDataFromError()}}> 244 | 网络错误, 点击重新加载 245 | 246 | ); 247 | }; 248 | 249 | renderMore = ()=>{ 250 | return ( 256 | 257 | ); 258 | }; 259 | 260 | /** 261 | * 加载更多 UI渲染 262 | * @returns {XML} 263 | * @private 264 | */ 265 | _renderFoot = ()=>{ 266 | if (this.currentState === NoMoreState){ 267 | return this.props.renderNoMore?this.props.renderNoMore():this.renderNoMore(); 268 | }else if(this.currentState === NoMoreErrorState){ 269 | return this.props.renderMoreError?this.props.renderMoreError():this.renderMoreError(); 270 | }else if(this.currentState >= ListState){ 271 | return this.props.renderMore?this.props.renderMore():this.renderMore(); 272 | }else{ 273 | return null; 274 | } 275 | }; 276 | 277 | /** 278 | * 类似 render() 方法,具体在父类里面 279 | * @returns {*} 280 | */ 281 | getScrollable = ()=> { 282 | if (this.currentState === LoadingState){ 283 | return this.props.renderLoading?this.props.renderLoading():this._renderLoading(); 284 | }else if(this.currentState === EmptyState){ 285 | if(this.props.renderEmpty){ 286 | return this.props.renderEmpty(this.reloadData) 287 | }else{ 288 | return this._renderEmpty(); 289 | } 290 | // return this.props.renderEmpty || this._renderEmpty; 291 | }else{ 292 | this.type='List'; 293 | return this._renderList() 294 | } 295 | } 296 | } 297 | 298 | const styles = StyleSheet.create({ 299 | contain:{ 300 | flex:1, 301 | justifyContent: 'center', 302 | alignItems: 'center', 303 | backgroundColor: Color.f3f3f3 304 | }, 305 | footer:{ 306 | height: 50, 307 | flex: 1, 308 | alignItems: 'center', 309 | justifyContent: 'center', 310 | borderTopWidth: 1, 311 | borderColor: "#CED0CE" 312 | } 313 | }); -------------------------------------------------------------------------------- /pull/index.js: -------------------------------------------------------------------------------- 1 | import React,{Component}from 'react'; 2 | import { 3 | StyleSheet, 4 | Image, 5 | Text, 6 | Linking, 7 | View, 8 | Dimensions, 9 | Animated, 10 | Easing, 11 | ScrollView, 12 | PanResponder, 13 | ActivityIndicator, 14 | TouchableOpacity, 15 | StatusBar, 16 | Platform, 17 | NativeModules, 18 | ImageBackground, 19 | InteractionManager, 20 | TouchableHighlight, 21 | FlatList, 22 | DeviceEventEmitter 23 | } from 'react-native'; 24 | import PullLayout from './PullLayout' 25 | import PullListView from './PullList' 26 | import PullView from './PullView' 27 | 28 | import LoadingSpinner from './LoadingSpinner'; 29 | const LoadingState = 1; //初始loading页面 30 | const EmptyState = 2; //空页面 31 | const ErrorState = 3; //加载数据错误 32 | const ListState = 4; //正常加载 33 | const MoreState = 5; //正在加载更多 34 | const NoMoreState = 6; //没有更多了 35 | const NoMoreErrorState = 7; //加载更多出错 36 | 37 | 38 | /** 39 | * 使用方式 40 | * PullList =>flatlist 41 | * **/ 42 | {/* this.pullList = list}*/} 46 | {/*topIndicatorRender={this.renderTopIndicator}*/} 47 | {/*topIndicatorHeight={header}*/} 48 | {/*onEndReachedThreshold={20}*/} 49 | {/*onPullRelease={this.onPullRelease}*/} 50 | {/*onEndReached={this.loadMore}*/} 51 | {/*renderItem={this.renderRowView}*/} 52 | {/*getItemLayout={(data, index) => ({length: SCALE(390), offset: SCALE(390) * index, index})}*/} 53 | {/*numColumns={1}*/} 54 | {/*ItemSeparatorComponent={() => {*/} 55 | {/*return null;*/} 56 | {/*}}*/} 57 | {/*initialNumToRender={5}*/} 58 | {/*renderLoading = {()=>{return null;}}*/} 59 | {/*/>*/} 60 | 61 | /** 62 | * PullScroll =>scrollview 63 | * **/ 64 | {/**/} 69 | {/* super.navigate(...params)}/>*/} 74 | {/* super.navigate(...params)}/>*/} 75 | {/* super.navigate(...params)}/>*/} 76 | {/**/} 77 | 78 | /** 79 | * PullScroll => scrollview 80 | * PullList =>flatlist 81 | * Key 每一个实例唯一不能重复 82 | * Android_Native 是否使用android原生下拉刷新组件 true开启 83 | * ****/ 84 | class Pull extends Component { 85 | 86 | constructor(){ 87 | super(); 88 | this.state = { 89 | data:[], 90 | 91 | }; 92 | this.currentState=LoadingState; 93 | this.page = 0; 94 | this.type='List'; 95 | } 96 | 97 | componentWillMount() { 98 | } 99 | componentDidMount() { 100 | if(Platform.OS=='android'&&this.props.Android_Native){ 101 | this.subscription = DeviceEventEmitter.addListener(this.props.Key+"onRefreshReleased",this.refreshReleased); 102 | InteractionManager.runAfterInteractions(()=>{ 103 | this.loadMore() 104 | }) 105 | } 106 | // this.mounted = true; 107 | // this.setPage(1); 108 | 109 | } 110 | 111 | refreshReleased = ()=>{ 112 | this.refresh(); 113 | } 114 | 115 | componentWillUnmount() { 116 | if(Platform.OS=='android'&&this.props.Android_Native){ 117 | this.pullLayout&&this.pullLayout.finishRefresh(this.props.Key); 118 | this.subscription&&this.subscription.remove(); 119 | } 120 | 121 | } 122 | 123 | 124 | setPage = (page) => { 125 | if(Platform.OS=='ios'||!this.props.Android_Native){ 126 | this.PullListView&&this.PullListView.setPage(page) 127 | return; 128 | } 129 | this.page = page; 130 | }; 131 | 132 | getPage = () => { 133 | if(Platform.OS=='ios'||!this.props.Android_Native){ 134 | return this.PullListView&&this.PullListView.getPage(); 135 | 136 | } 137 | return this.page; 138 | }; 139 | 140 | renderSeparatorView = ()=>{ 141 | 142 | if(Platform.OS=='ios'||!this.props.Android_Native){ 143 | return this.PullListView&&this.PullListView.renderSeparatorView(); 144 | 145 | } 146 | if(this.props.ItemSeparatorComponent){ 147 | return this.props.ItemSeparatorComponent(); 148 | } 149 | return () 150 | }; 151 | 152 | /** 153 | * 刷新 154 | */ 155 | refresh = () => { 156 | if(Platform.OS=='ios'||!this.props.Android_Native){ 157 | this.PullListView&&this.PullListView.refresh(); 158 | return; 159 | } 160 | Log('zhixngzheli'); 161 | this.setPage(1); 162 | this.props.onPullRelease&&this.props.onPullRelease(this.resolveHandler); 163 | }; 164 | 165 | resolveHandler = ()=>{ 166 | Log('使用广播方式传送数据') 167 | this.pullLayout&&this.pullLayout.finishRefresh(this.props.Key); 168 | } 169 | 170 | getMetrics = (args)=> { 171 | if(Platform.OS=='ios'||!this.props.Android_Native){ 172 | this.PullListView&&this.PullListView.getMetrics(); 173 | return; 174 | } 175 | this.scroll&&this.scroll.getMetrics(args); 176 | }; 177 | 178 | scrollToOffset = (...args)=> { 179 | if(Platform.OS=='ios'||!this.props.Android_Native){ 180 | this.PullListView&&this.PullListView.scrollToOffset(...args); 181 | return; 182 | } 183 | this.scroll&&this.scroll.scrollToOffset(...args); 184 | }; 185 | 186 | scrollToEnd = (args)=> { 187 | if(Platform.OS=='ios'||!this.props.Android_Native){ 188 | this.PullListView&&this.PullListView.scrollToEnd(args); 189 | return; 190 | } 191 | this.scroll&&this.scroll.scrollToEnd(args); 192 | }; 193 | 194 | /** 195 | * 对外提供API,设置列表数据 196 | */ 197 | setData = (_data)=>{ 198 | if(Platform.OS=='ios'||!this.props.Android_Native){ 199 | this.PullListView&&this.PullListView.setData(_data) 200 | return; 201 | } 202 | this.setPage(1); 203 | Log('setData'); 204 | if (_data.length == 0){ 205 | this.currentState=EmptyState, 206 | this.setState({ 207 | data: ['EmptyState'],//只有下拉刷新才会出现此状态 208 | }); 209 | }else{ 210 | this.currentState=ListState; 211 | this.setState({ 212 | data: _data, 213 | }); 214 | } 215 | }; 216 | 217 | /** 218 | * 对外提供API, loadMore 调用 219 | */ 220 | addData = (_data)=>{ 221 | if(Platform.OS=='ios'||!this.props.Android_Native){ 222 | this.PullListView&&this.PullListView.addData(_data) 223 | return; 224 | } 225 | 226 | let data = this.state.data; 227 | if(this.state.data[this.state.data.length-1]=='EmptyState'){ 228 | data.pop(); 229 | } 230 | if (_data.length == 0){ 231 | this.currentState=NoMoreState; 232 | this.setState({ 233 | data: data.concat(_data), 234 | }) 235 | }else{ 236 | this.currentState = MoreState; 237 | this.setState({ 238 | 239 | data: data.concat(_data), 240 | }); 241 | } 242 | 243 | }; 244 | 245 | 246 | 247 | /** 248 | * 对外提供API, 出错重新加载数据 249 | */ 250 | reloadData = ()=>{ 251 | if(Platform.OS=='ios'||!this.props.Android_Native){ 252 | this.PullListView&&this.PullListView.reloadData() 253 | return; 254 | } 255 | this.currentState=LoadingState; 256 | this.props.onPullRelease&&this.props.onPullRelease(this.resolveHandler); 257 | // this.forceUpdate(); 258 | }; 259 | 260 | 261 | //加载更多 262 | loadMore = ()=>{ 263 | if(Platform.OS=='ios'||!this.props.Android_Native){ 264 | this.PullListView&&this.PullListView.loadMore() 265 | return; 266 | } 267 | if(this.currentState==NoMoreState){ 268 | return; 269 | } 270 | this.page++; 271 | this.props.onEndReached&&this.props.onEndReached(this.getPage()); 272 | 273 | } 274 | 275 | /** 276 | * 对外提供API, 加载更多 277 | */ 278 | resumeMoreDataFromError = ()=>{ 279 | if(Platform.OS=='ios'||!this.props.Android_Native){ 280 | this.PullListView&&this.PullListView.resumeMoreDataFromError() 281 | return; 282 | } 283 | this.currentState=MoreState; 284 | this.page++; 285 | Log('this.page',this.page) 286 | this.props.onEndReached&&this.props.onEndReached(this.getPage()); 287 | }; 288 | 289 | /** 290 | * 加载loading页面 291 | * @returns {XML} 292 | * @private 293 | */ 294 | _renderLoading = ()=> { 295 | if(Platform.OS=='ios'||!this.props.Android_Native){ 296 | return this.PullListView&&this.PullListView._renderLoading() 297 | } 298 | return ( 299 | ) 300 | // return ( 301 | // 303 | // 304 | // 305 | // ); 306 | }; 307 | 308 | /** 309 | * 加载 空页面 310 | */ 311 | _renderEmpty = ()=>{ 312 | if(Platform.OS=='ios'||!this.props.Android_Native){ 313 | return this.PullListView&&this.PullListView._renderEmpty() 314 | } 315 | Log('没有数据'); 316 | return ( 317 | 318 | 319 | 320 | 321 | 322 | 暂无数据 323 | 324 | 325 | 326 | 327 | ) 328 | }; 329 | 330 | /** 331 | * 加载 出错页 332 | */ 333 | _renderError = ()=>{ 334 | if(Platform.OS=='ios'||!this.props.Android_Native){ 335 | return this.PullListView&&this.PullListView._renderError() 336 | } 337 | return ( 338 | 339 | 340 | 341 | 342 | 网络无法连接,点击屏幕重试 343 | 344 | 345 | 346 | ) 347 | }; 348 | 349 | renderNoMore = ()=>{ 350 | if(Platform.OS=='ios'||!this.props.Android_Native){ 351 | return this.PullListView&&this.PullListView.renderNoMore() 352 | } 353 | return ( 354 | 没有更多数据 355 | ) 356 | }; 357 | 358 | renderMoreError = ()=>{ 359 | if(Platform.OS=='ios'||!this.props.Android_Native){ 360 | return this.PullListView&&this.PullListView.renderMoreError() 361 | } 362 | return ( 363 | {this.resumeMoreDataFromError()}}> 367 | 网络错误, 点击重新加载 368 | 369 | ); 370 | }; 371 | 372 | renderRowView = ({item, index, separators})=>{ 373 | if (item === 'LoadingState'){ 374 | return this._renderLoading(); 375 | }else if(item === 'EmptyState'){ 376 | if(this.props.renderEmpty){ 377 | return this.props.renderEmpty(this.reloadData) 378 | }else{ 379 | return this._renderEmpty(); 380 | } 381 | }else{ 382 | return this.props.renderItem&&this.props.renderItem({item, index, separators}) 383 | } 384 | } 385 | 386 | renderMore = ()=>{ 387 | 388 | if(Platform.OS=='ios'||!this.props.Android_Native){ 389 | return this.PullListView&&this.PullListView.renderMore() 390 | } 391 | return ( 397 | 398 | ); 399 | }; 400 | 401 | 402 | /** 403 | * 类似 render() 方法,具体在父类里面 404 | * @returns {*} 405 | */ 406 | // getScrollable = ()=> { 407 | // if (this.currentState === LoadingState){ 408 | // return this.props.renderLoading || this._renderLoading(); 409 | // }else if(this.currentState === EmptyState){ 410 | // if(this.props.renderEmpty){ 411 | // return this.props.renderEmpty(this.reloadData) 412 | // }else{ 413 | // return this._renderEmpty(); 414 | // } 415 | // // return this.props.renderEmpty || this._renderEmpty; 416 | // }else{ 417 | // this.type='List'; 418 | // return this._renderList() 419 | // } 420 | // } 421 | 422 | /** 423 | * 加载更多 UI渲染 424 | * @returns {XML} 425 | * @private 426 | */ 427 | _renderFoot = ()=>{ 428 | if(Platform.OS=='ios'||!this.props.Android_Native){ 429 | return this.PullListView&&this.PullListView._renderFoot() 430 | } 431 | Log('执行了这里ListFooterComponent') 432 | if (this.currentState === NoMoreState){ 433 | return this.props.renderNoMore?this.props.renderNoMore():this.renderNoMore(); 434 | }else if(this.currentState === NoMoreErrorState){ 435 | return this.props.renderMoreError?this.props.renderMoreError():this.renderMoreError(); 436 | }else if(this.currentState >= ListState){ 437 | return this.props.renderMore?this.props.renderMore():this.renderMore(); 438 | }else{ 439 | return null; 440 | } 441 | }; 442 | 443 | render(){ 444 | if (Platform.OS == 'android' && this.props.Android_Native) { 445 | return ( { 448 | this.pullLayout = pull 449 | }} 450 | style={{flex: 1, backgroundColor: 'white',}}> 451 | { 454 | this.scroll = c; 455 | }} 456 | refreshing={false} 457 | keyExtractor={(item, index) => { 458 | return index.toString() 459 | }} 460 | onEndReachedThreshold={20} 461 | data={this.state.data} 462 | windowSize={10} 463 | initialNumToRender={5} 464 | updateCellsBatchingPeriod={10} 465 | maxToRenderPerBatch={10} 466 | {...this.props} 467 | renderItem={this.renderRowView} 468 | onEndReached={this.loadMore} 469 | ListFooterComponent={this._renderFoot} 470 | /> 471 | ) 472 | } 473 | return ( { 476 | this.PullListView = pull 477 | }} 478 | />) 479 | } 480 | } 481 | 482 | class PullScroll extends Component{ 483 | 484 | componentDidMount() { 485 | if(Platform.OS=='android'){ 486 | this.subscription = DeviceEventEmitter.addListener(this.props.Key+"onRefreshReleased",this.refreshReleased); 487 | } 488 | } 489 | 490 | refreshReleased = ()=>{ 491 | this.refresh(); 492 | } 493 | 494 | /** 495 | * 刷新 496 | */ 497 | refresh = () => { 498 | this.props.onPullRelease&&this.props.onPullRelease(this.resolveHandler); 499 | }; 500 | 501 | resolveHandler = ()=>{ 502 | Log('使用广播方式传送数据') 503 | this.pullLayout&&this.pullLayout.finishRefresh(this.props.Key); 504 | } 505 | 506 | componentWillUnmount() { 507 | if(Platform.OS=='android'){ 508 | this.pullLayout&&this.pullLayout.finishRefresh(this.props.Key); 509 | } 510 | this.subscription&&this.subscription.remove(); 511 | } 512 | 513 | render(){ 514 | if (Platform.OS == 'android' && this.props.Android_Native) { 515 | console.log('pull index this.props.children',this.props.children) 516 | return ( { 519 | this.pullLayout = pull 520 | }} 521 | style={{flex: 1, backgroundColor: 'white',}} 522 | {...this.props}> 523 | 524 | {this.props.children} 525 | 526 | ) 527 | } 528 | return ( 530 | {this.props.children} 531 | ) 532 | } 533 | 534 | } 535 | 536 | 537 | 538 | const styles = StyleSheet.create({ 539 | contain:{ 540 | flex:1, 541 | width:WIDTH, 542 | height:HEIGHT, 543 | justifyContent: 'center', 544 | alignItems: 'center', 545 | backgroundColor: Color.f3f3f3 546 | }, 547 | footer:{ 548 | height: 50, 549 | flex: 1, 550 | alignItems: 'center', 551 | justifyContent: 'center', 552 | borderTopWidth: 1, 553 | borderColor: "#CED0CE" 554 | } 555 | }); 556 | 557 | export { 558 | Pull as PullList, 559 | PullScroll 560 | }; 561 | 562 | 563 | 564 | -------------------------------------------------------------------------------- /pull/Pullable.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { Component } from 'react'; 3 | import { 4 | View, 5 | Text, 6 | RefreshControl, 7 | PanResponder, 8 | Animated, 9 | Easing, 10 | Dimensions, 11 | ActivityIndicator, 12 | StyleSheet, 13 | Platform 14 | } from 'react-native'; 15 | // const padding = 2; //scrollview与外面容器的距离 16 | const pullOkMargin = 100; //下拉到ok状态时topindicator距离顶部的距离 17 | const defaultDuration = 300; 18 | const defaultTopIndicatorHeight = 80; //顶部刷新指示器的高度 19 | const defaultFlag = {pulling: false, pullok: false, pullrelease: false}; 20 | const flagPulling = {pulling: true, pullok: false, pullrelease: false}; 21 | const flagPullok = {pulling: false, pullok: true, pullrelease: false}; 22 | const flagPullrelease = {pulling: false, pullok: false, pullrelease: true}; 23 | const isDownGesture = (x, y) => { 24 | return y > 0 && (y > Math.abs(x)); 25 | }; 26 | const isUpGesture = (x, y) => { 27 | return y < 0 && (Math.abs(x) < Math.abs(y)); 28 | }; 29 | const isVerticalGesture = (x, y) => { 30 | return (Math.abs(x) < Math.abs(y)); 31 | }; 32 | 33 | 34 | export default class extends Component { 35 | 36 | constructor(props) { 37 | super(props); 38 | this.pullable = this.props.refreshControl == null; 39 | this.defaultScrollEnabled = false; //定义onPull***属性时scrollEnabled为false 40 | this.topIndicatorHeight = this.props.topIndicatorHeight ? this.props.topIndicatorHeight : defaultTopIndicatorHeight; 41 | this.defaultXY = {x: 0, y: this.topIndicatorHeight * -1}; 42 | this.pullOkMargin = this.props.pullOkMargin ? this.props.pullOkMargin : pullOkMargin; 43 | this.duration = this.props.duration ? this.props.duration : defaultDuration; 44 | this.BaseState = Object.assign({}, props, { 45 | arrowAngle: new Animated.Value(0), 46 | pullPan: new Animated.ValueXY(this.defaultXY), 47 | scrollEnabled: this.defaultScrollEnabled, 48 | flag: defaultFlag, 49 | height: 0, 50 | prArrowDeg:new Animated.Value(0), 51 | }); 52 | // this.scrollEnabled = this.defaultScrollEnabled; 53 | this.gesturePosition = {x: 0, y: 0}; 54 | this.onScroll = this.onScroll.bind(this); 55 | this.onLayout = this.onLayout.bind(this); 56 | this.BeginRefresh = this.BeginRefresh.bind(this); 57 | this.StopRefresh = this.StopRefresh.bind(this); 58 | this.isPullState = this.isPullState.bind(this); 59 | this.resetDefaultXYHandler = this.resetDefaultXYHandler.bind(this); 60 | this.resolveHandler = this.resolveHandler.bind(this); 61 | this.setFlag = this.setFlag.bind(this); 62 | this.renderTopIndicator = this.renderTopIndicator.bind(this); 63 | this.renderSpinner = this.renderSpinner.bind(this); 64 | this.defaultTopIndicatorRender = this.defaultTopIndicatorRender.bind(this); 65 | this.panResponder = PanResponder.create({ 66 | onStartShouldSetPanResponder: this.onShouldSetPanResponder.bind(this), 67 | onMoveShouldSetPanResponder: this.onShouldSetPanResponder.bind(this), 68 | onPanResponderGrant: () => {}, 69 | onPanResponderMove: this.onPanResponderMove.bind(this), 70 | onPanResponderRelease: this.onPanResponderRelease.bind(this), 71 | onPanResponderTerminate: this.onPanResponderRelease.bind(this), 72 | }); 73 | this.IOS = (Platform.OS==='ios'?true:false); 74 | this.flag = defaultFlag; 75 | this.storyTimeKey = "story_time_key"; 76 | this.base64Icon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAABQBAMAAAD8TNiNAAAAJ1BMVEUAAACqqqplZWVnZ2doaGhqampoaGhpaWlnZ2dmZmZlZWVmZmZnZ2duD78kAAAADHRSTlMAA6CYqZOlnI+Kg/B86E+1AAAAhklEQVQ4y+2LvQ3CQAxGLSHEBSg8AAX0jECTnhFosgcjZKr8StE3VHz5EkeRMkF0rzk/P58k9rgOW78j+TE99OoeKpEbCvcPVDJ0OvsJ9bQs6Jxs26h5HCrlr9w8vi8zHphfmI0fcvO/ZXJG8wDzcvDFO2Y/AJj9ADE7gXmlxFMIyVpJ7DECzC9J2EC2ECAAAAAASUVORK5CYII='; 77 | } 78 | 79 | onShouldSetPanResponder(e, gesture) { 80 | if(this.type==='View'){ 81 | if (this.IOS&&(!this.pullable || isUpGesture(gesture.dx, gesture.dy)||!isVerticalGesture(gesture.dx, gesture.dy))) { //不使用pullable,或非向上 或向下手势不响应 82 | return false; 83 | }else{ 84 | if (!this.IOS&&(!this.pullable || !isVerticalGesture(gesture.dx, gesture.dy))) { //不使用pullable,或非向上 或向下手势不响应 85 | return false; 86 | } 87 | } 88 | }else{ 89 | if (!this.pullable || !isVerticalGesture(gesture.dx, gesture.dy)) { //不使用pullable,或非向上 或向下手势不响应 90 | return false; 91 | } 92 | } 93 | 94 | if (!this.state.scrollEnabled) { 95 | this.lastY = this.state.pullPan.y._value; 96 | return true; 97 | } else { 98 | return false; 99 | } 100 | } 101 | 102 | BeginRefresh(){ 103 | Log('BeginRefresh'); 104 | this.setFlag(flagPullrelease); 105 | this.state.pullPan.setValue({x: this.defaultXY.x, y: this.topIndicatorHeight}); 106 | 107 | } 108 | 109 | StopRefresh(){ 110 | Log('StopRefresh'); 111 | this.resetDefaultXYHandler(); 112 | } 113 | 114 | onPanResponderMove(e, gesture) { 115 | this.gesturePosition = {x: this.defaultXY.x, y: gesture.dy}; 116 | if (isUpGesture(gesture.dx, gesture.dy)) { //向上滑动 117 | if(this.isPullState()) { 118 | this.resetDefaultXYHandler(); 119 | } else if(this.props.onPushing && this.props.onPushing(this.gesturePosition)) { 120 | // do nothing, handling by this.props.onPushing 121 | } else { 122 | if(this.type==='View'){ 123 | this.scroll&&this.scroll.scrollTo({x:0, y: gesture.dy * -1,animated:true}); 124 | }else{ 125 | this.scroll&&this.scroll.scrollToOffset({animated: true, 126 | offset: gesture.dy * -1}); 127 | } 128 | } 129 | return; 130 | } else if (isDownGesture(gesture.dx, gesture.dy)) { //下拉 131 | this.state.pullPan.setValue({x: this.defaultXY.x, y: this.lastY + gesture.dy / 2}); 132 | if (gesture.dy < this.topIndicatorHeight + this.pullOkMargin) { //正在下拉 133 | if (!this.flag.pulling) { 134 | this.props.onPulling && this.props.onPulling(); 135 | } 136 | this.setFlag(flagPulling); 137 | } else { //下拉到位 138 | if (!this.state.pullok) { 139 | this.props.onPullOk && this.props.onPullOk(); 140 | } 141 | this.setFlag(flagPullok); 142 | } 143 | } 144 | } 145 | 146 | onPanResponderRelease(e, gesture) { 147 | if (this.flag.pulling) { //没有下拉到位 148 | this.resetDefaultXYHandler(); //重置状态 149 | } 150 | if (this.flag.pullok) { 151 | if (!this.flag.pullrelease) { 152 | if (this.props.onPullRelease) { 153 | this.props.onPullRelease(this.resolveHandler); 154 | } else { 155 | setTimeout(() => {this.resetDefaultXYHandler()}, 3000); 156 | } 157 | } 158 | this.setFlag(flagPullrelease); //完成下拉,已松开 159 | Animated.timing(this.state.pullPan, { 160 | toValue: {x: 0, y: 0}, 161 | easing: Easing.linear, 162 | duration: this.duration 163 | }).start(); 164 | } 165 | } 166 | 167 | onScroll(e) { 168 | if (e.nativeEvent.contentOffset.y <= 0) { 169 | // Log('onScroll e.nativeEvent.contentOffset.y',e.nativeEvent.contentOffset.y); 170 | // this.scrollEnabled = this.defaultScrollEnabled; 171 | this.state.scrollEnabled===this.defaultScrollEnabled?"":this.setState({scrollEnabled: this.defaultScrollEnabled}); 172 | } else if(!this.isPullState()) { 173 | // Log('onScroll e.nativeEvent.contentOffset.y',e.nativeEvent.contentOffset.y); 174 | // this.scrollEnabled = true; 175 | this.state.scrollEnabled?"":this.setState({scrollEnabled: true}); 176 | } 177 | } 178 | 179 | isPullState() { 180 | return this.flag.pulling || this.flag.pullok || this.flag.pullrelease; 181 | } 182 | 183 | setFlag(flag) { 184 | if (this.flag != flag) { 185 | this.flag = flag; 186 | Log('设置inderTop',this.flag); 187 | this.renderTopIndicator(); 188 | } 189 | } 190 | 191 | /** 数据加载完成后调用此方法进行重置归位 192 | */ 193 | resolveHandler() { 194 | if (this.flag.pullrelease) { //仅触摸松开时才触发 195 | this.resetDefaultXYHandler(); 196 | } 197 | } 198 | 199 | resetDefaultXYHandler() { 200 | this.flag = defaultFlag; 201 | Animated.timing(this.state.pullPan, { 202 | toValue: {x: 0, y: this.topIndicatorHeight * -1}, 203 | easing: Easing.linear, 204 | duration: this.duration 205 | }).start(); 206 | } 207 | 208 | componentWillUpdate(nextProps, nextState) { 209 | if (nextProps.isPullEnd && this.state.pullrelease) { 210 | this.resetDefaultXYHandler(); 211 | } 212 | } 213 | 214 | onLayout(e) { 215 | if (this.state.width != e.nativeEvent.layout.width || this.state.height != e.nativeEvent.layout.height) { 216 | this.scrollContainer.setNativeProps({style: {width: e.nativeEvent.layout.width, height: e.nativeEvent.layout.height}}); 217 | } 218 | } 219 | 220 | render() { 221 | // let refreshControl = this.props.refreshControl; 222 | return ( 223 | 224 | {this.ani = c;}} 225 | style={[this.state.pullPan.getLayout()]}> 226 | {this.renderTopIndicator()} 227 | {this.scrollContainer = c;}} 228 | {...this.panResponder.panHandlers} 229 | style={{width: this.state.width, height: this.state.height}}> 230 | {this.getScrollable()} 231 | 232 | 233 | 234 | ); 235 | } 236 | 237 | renderTopIndicator() { 238 | let { pulling, pullok, pullrelease } = this.flag; 239 | if (this.props.topIndicatorRender == null) { 240 | return this.defaultTopIndicatorRender(pulling, pullok, pullrelease, this.gesturePosition); 241 | } else { 242 | return this.props.topIndicatorRender(pulling, pullok, pullrelease, this.gesturePosition); 243 | } 244 | } 245 | 246 | /** 247 | 使用setNativeProps解决卡顿问题 248 | make changes directly to a component without using state/props to trigger a re-render of the entire subtree 249 | */ 250 | // defaultTopIndicatorRender(pulling, pullok, pullrelease, gesturePosition) { 251 | // 252 | // this.transform = [{rotate:this.state.prArrowDeg.interpolate({ 253 | // inputRange: [0,1], 254 | // outputRange: ['0deg', '-180deg'] 255 | // })}]; 256 | // 257 | // if (pulling) { 258 | // Animated.timing(this.state.prArrowDeg, { 259 | // toValue: 0, 260 | // duration: 100, 261 | // easing: Easing.inOut(Easing.quad) 262 | // }).start(); 263 | // this.txtPulling && this.txtPulling.setNativeProps({style: styles.show}); 264 | // this.txtPullok && this.txtPullok.setNativeProps({style: styles.hide}); 265 | // this.txtPullrelease && this.txtPullrelease.setNativeProps({style: styles.hide}); 266 | // } else if (pullok) { 267 | // Animated.timing(this.state.prArrowDeg, { 268 | // toValue: 1, 269 | // duration: 100, 270 | // easing: Easing.inOut(Easing.quad) 271 | // }).start(); 272 | // this.txtPulling && this.txtPulling.setNativeProps({style: styles.hide}); 273 | // this.txtPullok && this.txtPullok.setNativeProps({style: styles.show}); 274 | // this.txtPullrelease && this.txtPullrelease.setNativeProps({style: styles.hide}); 275 | // } else if (pullrelease) { 276 | // this.txtPulling && this.txtPulling.setNativeProps({style: styles.hide}); 277 | // this.txtPullok && this.txtPullok.setNativeProps({style: styles.hide}); 278 | // this.txtPullrelease && this.txtPullrelease.setNativeProps({style: styles.show}); 279 | // } 280 | // return ( 281 | // 282 | // {this.txtPulling = c;}} style={styles.hide}> 283 | // 286 | // {"下拉可以刷新"} 287 | // 288 | // 289 | // {this.txtPullok = c;}} style={styles.hide}> 290 | // 291 | // 294 | // {"释放立即刷新"} 295 | // 296 | // 297 | // {this.txtPullrelease = c;}} style={styles.hide}> 298 | // 299 | // {"刷新数据中..."} 300 | // 301 | // 302 | // 下拉刷新时间: {dateFormat(new Date().getTime(),'yyyy-MM-dd hh:mm')} 303 | // 304 | // ); 305 | // } 306 | 307 | renderSpinner(status) { 308 | if (status === "txtPullrelease") { 309 | return ( 310 | 311 | ) 312 | } 313 | return ( 314 | 328 | ) 329 | } 330 | 331 | defaultTopIndicatorRender(pulling, pullok, pullrelease, gesturePosition) { 332 | Log('pulling, pullok, pullrelease',pulling, pullok, pullrelease); 333 | if (pulling) { 334 | Animated.timing(this.state.arrowAngle, { 335 | toValue: 0, 336 | duration: 50, 337 | easing: Easing.inOut(Easing.quad) 338 | }).start(); 339 | this.txtPulling && this.txtPulling.setNativeProps({style: styles.show}); 340 | this.txtPullok && this.txtPullok.setNativeProps({style: styles.hide}); 341 | this.txtPullrelease && this.txtPullrelease.setNativeProps({style: styles.hide}); 342 | } else if (pullok) { 343 | Animated.timing(this.state.arrowAngle, { 344 | toValue: 1, 345 | duration: 50, 346 | easing: Easing.inOut(Easing.quad) 347 | }).start(); 348 | this.txtPulling && this.txtPulling.setNativeProps({style: styles.hide}); 349 | this.txtPullok && this.txtPullok.setNativeProps({style: styles.show}); 350 | this.txtPullrelease && this.txtPullrelease.setNativeProps({style: styles.hide}); 351 | } else if (pullrelease) { 352 | Animated.timing(this.state.arrowAngle, { 353 | toValue: 1, 354 | duration: 50, 355 | easing: Easing.inOut(Easing.quad) 356 | }).start(); 357 | this.txtPulling && this.txtPulling.setNativeProps({style: styles.hide}); 358 | this.txtPullok && this.txtPullok.setNativeProps({style: styles.hide}); 359 | this.txtPullrelease && this.txtPullrelease.setNativeProps({style: styles.show}); 360 | } 361 | return ( 362 | {this.PullAll = c;}} style={defaultHeaderStyles.header}> 363 | {this.txtPulling = c;}} style={styles.hide}> 364 | {this.renderSpinner("txtPulling")} 365 | {"下拉可以刷新"} 366 | 367 | {this.txtPullok = c;}} style={styles.hide}> 368 | {this.renderSpinner("txtPullok")} 369 | {"释放立即刷新"} 370 | 371 | {this.txtPullrelease = c;}} style={styles.hide}> 372 | {this.renderSpinner("txtPullrelease")} 373 | {"刷新数据中..."} 374 | 375 | 376 | ); 377 | } 378 | } 379 | 380 | const dateFormat = function(dateTime, fmt) { 381 | let date = new Date(dateTime); 382 | fmt = fmt || 'yyyy-MM-dd'; 383 | let o = { 384 | "M+": date.getMonth() + 1, //月份 385 | "d+": date.getDate(), //日 386 | "h+": date.getHours(), //小时 387 | "m+": date.getMinutes(), //分 388 | "s+": date.getSeconds(), //秒 389 | "q+": Math.floor((date.getMonth() + 3) / 3), //季度 390 | "S": date.getMilliseconds() //毫秒 391 | }; 392 | if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length)); 393 | for (let k in o) 394 | if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); 395 | return fmt; 396 | } 397 | 398 | 399 | 400 | const styles = StyleSheet.create({ 401 | wrap:{ 402 | flex: 1, 403 | }, 404 | headWrap:{ 405 | height: defaultTopIndicatorHeight, 406 | justifyContent: 'center', 407 | alignItems: 'center', 408 | }, 409 | hide: { 410 | position: 'absolute', 411 | left: 10000 412 | }, 413 | show:{ 414 | position: 'relative', 415 | left: 0, 416 | flexDirection: 'row', 417 | width: Dimensions.get('window').width, 418 | height: 40, 419 | justifyContent: 'center', 420 | alignItems: 'center', 421 | }, 422 | arrow:{ 423 | height: 30, 424 | width: 30, 425 | marginRight: 20, 426 | }, 427 | arrowText:{ 428 | marginLeft: 20, 429 | } 430 | }); 431 | const defaultHeaderStyles = StyleSheet.create({ 432 | header: { 433 | height: 80, 434 | alignItems: 'center', 435 | justifyContent: 'center' 436 | }, 437 | status: { 438 | flexDirection: 'row', 439 | alignItems: 'center' 440 | }, 441 | arrow: { 442 | width: 23, 443 | height: 23, 444 | marginRight: 10, 445 | opacity: 0.4 446 | }, 447 | statusTitle: { 448 | fontSize: 13, 449 | color: '#333333' 450 | }, 451 | date: { 452 | fontSize: 11, 453 | color: '#333333', 454 | marginTop: 5 455 | } 456 | }); --------------------------------------------------------------------------------