1
1
var React = require ( 'react-native' )
2
2
var {
3
3
PropTypes,
4
+ StyleSheet,
5
+ View,
6
+ Platform,
4
7
} = React
5
8
var ListView = require ( './ListView' )
6
9
var createElementFrom = require ( './createElementFrom' )
7
10
var RefreshingIndicator = require ( './RefreshingIndicator' )
8
11
9
12
const SCROLL_EVENT_THROTTLE = 32
10
13
const MIN_PULLDOWN_DISTANCE = 40
11
-
14
+ const REFRESHING_INDICATOR_HEIGHT = 60
12
15
const LISTVIEW_REF = 'listview'
13
16
17
+ /*
18
+ * state transitions:
19
+ * {isRefreshing: false}
20
+ * v - show loading spinner
21
+ * {isRefreshing: true, waitingForRelease: true}
22
+ * v - reset scroll position, offset scroll top
23
+ * {isRefreshing: true, waitingForRelease: false}
24
+ * v - hide loading spinner
25
+ * {isRefreshing: false}
26
+ */
27
+
14
28
var ControlledRefreshableListView = React . createClass ( {
15
29
propTypes : {
16
30
onRefresh : PropTypes . func . isRequired ,
17
31
isRefreshing : PropTypes . bool . isRequired ,
32
+ refreshPrompt : PropTypes . oneOfType ( [ PropTypes . string , PropTypes . element ] ) ,
18
33
refreshDescription : PropTypes . oneOfType ( [ PropTypes . string , PropTypes . element ] ) ,
19
- refreshingIndictatorComponent : PropTypes . oneOfType ( [ PropTypes . func , PropTypes . element ] ) ,
34
+ refreshingIndicatorComponent : PropTypes . oneOfType ( [ PropTypes . func , PropTypes . element ] ) ,
20
35
minPulldownDistance : PropTypes . number ,
21
36
ignoreInertialScroll : PropTypes . bool ,
22
37
scrollEventThrottle : PropTypes . number ,
23
38
onScroll : PropTypes . func ,
24
- renderHeader : PropTypes . func ,
25
- renderHeaderWrapper : PropTypes . func ,
26
39
onResponderGrant : PropTypes . func ,
27
40
onResponderRelease : PropTypes . func ,
41
+ renderHeaderWrapper : ( props , propName , componentName ) => {
42
+ if ( props [ propName ] ) {
43
+ return new Error ( "The 'renderHeaderWrapper' prop is no longer used" ) ;
44
+ }
45
+ } ,
46
+ refreshingIndictatorComponent : ( props , propName , componentName ) => {
47
+ if ( props [ propName ] ) {
48
+ return new Error ( "The 'refreshingIndictatorComponent' prop has been renamed to 'refreshingIndicatorComponent'" ) ;
49
+ }
50
+ } ,
28
51
} ,
29
52
getDefaultProps ( ) {
30
53
return {
31
54
minPulldownDistance : MIN_PULLDOWN_DISTANCE ,
32
55
scrollEventThrottle : SCROLL_EVENT_THROTTLE ,
33
56
ignoreInertialScroll : true ,
34
- refreshingIndictatorComponent : RefreshingIndicator ,
57
+ refreshingIndicatorComponent : RefreshingIndicator ,
58
+ }
59
+ } ,
60
+ getInitialState ( ) {
61
+ return {
62
+ waitingForRelease : false ,
63
+ }
64
+ } ,
65
+ componentWillReceiveProps ( nextProps ) {
66
+ if ( ! this . props . isRefreshing && nextProps . isRefreshing && this . isTouching ) {
67
+
68
+ this . waitingForRelease = true
69
+ this . setState ( { waitingForRelease : true } )
70
+ }
71
+ } ,
72
+ componentWillUpdate ( nextProps , nextState ) {
73
+ if ( Platform . OS === 'ios' ) {
74
+ if (
75
+ this . isReleaseUpdate ( this . props , this . state , nextProps , nextState )
76
+ ) {
77
+ this . getScrollResponder ( ) . scrollWithoutAnimationTo (
78
+ - ( this . lastContentInsetTop + REFRESHING_INDICATOR_HEIGHT ) ,
79
+ this . lastContentOffsetX
80
+ )
81
+ }
82
+ }
83
+ } ,
84
+ componentDidUpdate ( prevProps , prevState ) {
85
+ if ( Platform . OS === 'ios' ) {
86
+ if (
87
+ this . isReleaseUpdate ( prevProps , prevState , this . props , this . state )
88
+ ) {
89
+ this . getScrollResponder ( ) . scrollWithoutAnimationTo (
90
+ - ( this . lastContentInsetTop ) ,
91
+ this . lastContentOffsetX
92
+ )
93
+ }
35
94
}
36
95
} ,
37
96
handleScroll ( e ) {
38
97
var scrollY = e . nativeEvent . contentInset . top + e . nativeEvent . contentOffset . y
98
+ this . lastScrollY = scrollY
99
+ this . lastContentInsetTop = e . nativeEvent . contentInset . top
100
+ this . lastContentOffsetX = e . nativeEvent . contentOffset . x
39
101
40
102
if ( this . isTouching || ( ! this . isTouching && ! this . props . ignoreInertialScroll ) ) {
41
103
if ( scrollY < - this . props . minPulldownDistance ) {
@@ -57,50 +119,96 @@ var ControlledRefreshableListView = React.createClass({
57
119
} ,
58
120
handleResponderRelease ( ) {
59
121
this . isTouching = false
122
+ if ( this . isWaitingForRelease ( ) ) {
123
+ this . waitingForRelease = false
124
+ this . setState ( { waitingForRelease : false } )
125
+ }
60
126
if ( this . props . onResponderRelease ) {
61
127
this . props . onResponderRelease . apply ( null , arguments )
62
128
}
63
129
} ,
130
+ getContentContainerStyle ( ) {
131
+ if ( ! this . props . isRefreshing || this . isWaitingForRelease ( ) ) return null
132
+
133
+ return { marginTop : REFRESHING_INDICATOR_HEIGHT }
134
+ } ,
64
135
getScrollResponder ( ) {
65
136
return this . refs [ LISTVIEW_REF ] . getScrollResponder ( )
66
137
} ,
67
138
setNativeProps ( props ) {
68
139
this . refs [ LISTVIEW_REF ] . setNativeProps ( props )
69
140
} ,
70
- renderHeader ( ) {
71
- var description = this . props . refreshDescription
72
-
73
- var refreshingIndictator
74
- if ( this . props . isRefreshing ) {
75
- refreshingIndictator = createElementFrom ( this . props . refreshingIndictatorComponent , { description} )
76
- } else {
77
- refreshingIndictator = null
78
- }
79
-
80
- if ( this . props . renderHeaderWrapper ) {
81
- return this . props . renderHeaderWrapper ( refreshingIndictator )
82
- } else if ( this . props . renderHeader ) {
83
- console . warn ( 'renderHeader is deprecated. Use renderHeaderWrapper instead.' )
84
- return this . props . renderHeader ( refreshingIndictator )
85
- } else {
86
- return refreshingIndictator
141
+ isWaitingForRelease ( ) {
142
+ return this . waitingForRelease || this . props . waitingForRelease
143
+ } ,
144
+ isReleaseUpdate ( oldProps , oldState , newProps , newState ) {
145
+ return (
146
+ ( ! oldProps . isRefreshing && newProps . isRefreshing && ! this . waitingForRelease ) ||
147
+ ( oldProps . isRefreshing && oldState . waitingForRelease && ! newState . waitingForRelease )
148
+ )
149
+ } ,
150
+ renderRefreshingIndicator ( ) {
151
+ var { isRefreshing, refreshDescription, refreshPrompt} = this . props
152
+ var refreshingIndicatorProps = {
153
+ isRefreshing,
154
+ description : refreshDescription ,
155
+ prompt : refreshPrompt ,
156
+ pulldownDistance : - ( this . lastScrollY || 0 )
87
157
}
158
+ return createElementFrom ( this . props . refreshingIndicatorComponent , refreshingIndicatorProps )
88
159
} ,
89
160
render ( ) {
90
161
return (
91
- < ListView
92
- { ...this . props }
93
- ref = { LISTVIEW_REF }
94
- onScroll = { this . handleScroll }
95
- renderHeader = { this . renderHeader }
96
- scrollEventThrottle = { this . props . scrollEventThrottle }
97
- onResponderGrant = { this . handleResponderGrant }
98
- onResponderRelease = { this . handleResponderRelease }
99
- />
162
+ < View style = { [ stylesheet . container ] } >
163
+ < View style = { [ stylesheet . fillParent ] } >
164
+ { this . renderRefreshingIndicator ( ) }
165
+ </ View >
166
+ < View style = { [ stylesheet . fillParent ] } >
167
+ < ListView
168
+ { ...this . props }
169
+ ref = { LISTVIEW_REF }
170
+ contentContainerStyle = { this . getContentContainerStyle ( ) }
171
+ onScroll = { this . handleScroll }
172
+ scrollEventThrottle = { this . props . scrollEventThrottle }
173
+ onResponderGrant = { this . handleResponderGrant }
174
+ onResponderRelease = { this . handleResponderRelease }
175
+ />
176
+ </ View >
177
+ </ View >
100
178
)
101
179
} ,
102
180
} )
103
181
182
+ var stylesheet = StyleSheet . create ( {
183
+ container : {
184
+ // flex: 1,
185
+ } ,
186
+ fillParent : {
187
+ backgroundColor : 'transparent' ,
188
+ position : 'absolute' ,
189
+ top : 0 ,
190
+ left : 0 ,
191
+ right : 0 ,
192
+ bottom : 0 ,
193
+ } ,
194
+ // offsetParent: {
195
+ // position: 'relative',
196
+ // },
197
+ // positionTopLeft: {
198
+ // position: 'absolute',
199
+ // top: 0,
200
+ // left: 0,
201
+ // },
202
+ // fill: {
203
+ // flex: 1
204
+ // },
205
+ // center: {
206
+ // flex: 1,
207
+ // justifyContent: 'space-around',
208
+ // alignItems: 'center',
209
+ // },
210
+ } )
211
+
104
212
ControlledRefreshableListView . DataSource = ListView . DataSource
105
213
ControlledRefreshableListView . RefreshingIndicator = RefreshingIndicator
106
214
0 commit comments