1
1
// @flow
2
2
import React , { Component } from 'react' ;
3
3
import PropTypes from 'prop-types' ;
4
- import { Animated , ScrollView , StyleSheet , View } from 'react-native' ;
4
+ import { Animated , ScrollView , StyleSheet , View , Platform } from 'react-native' ;
5
5
import type { ViewProps } from 'ViewPropTypes' ;
6
+ import type { FlatList , SectionList , ListView } from 'react-native' ;
6
7
7
- export type Props = {
8
+ type ScrollViewProps = {
9
+ onScroll ?: ?Function ,
10
+ style ?: $PropertyType < ViewProps , 'style' > ,
11
+ contentContainerStyle ?: $PropertyType < ViewProps , 'style' > ,
12
+ scrollEventThrottle : number ,
13
+ } ;
14
+
15
+ export type Props = ScrollViewProps & {
8
16
children ?: ?React$Element < any > ,
9
17
childrenStyle ?: ?any ,
10
18
overlayColor : string ,
@@ -15,11 +23,10 @@ export type Props = {
15
23
minHeight : number ,
16
24
minOverlayOpacity : number ,
17
25
renderFixedForeground : ( ) => React$Element < any > ,
18
- renderForeground : ( ) => React$Element < any > ,
26
+ renderForeground ? : ( ) => React$Element < any > ,
19
27
renderHeader : ( ) => React$Element < any > ,
20
28
renderTouchableFixedForeground ?: ?( ) => React$Element < any > ,
21
- style ?: $PropertyType < ViewProps , 'style' > ,
22
- onScroll ?: ?Function ,
29
+ ScrollViewComponent : React$ComponentType < ScrollViewProps > ,
23
30
} ;
24
31
25
32
export type DefaultProps = {
@@ -31,18 +38,20 @@ export type DefaultProps = {
31
38
minHeight : number ,
32
39
minOverlayOpacity : number ,
33
40
renderFixedForeground : ( ) => React$Element < any > ,
34
- renderForeground : ( ) => React$Element < any > ,
35
41
renderHeader : ( ) => React$Element < any > ,
42
+ ScrollViewComponent : React$ComponentType < ScrollViewProps > ,
36
43
} ;
37
44
38
45
export type State = {
39
46
scrollY : Animated . Value ,
40
47
pageY : number ,
41
48
} ;
42
49
50
+ type ScrollComponent < ItemT > = FlatList < ItemT > | SectionList < ItemT > | ListView | ScrollView ;
51
+
43
52
class ImageHeaderScrollView extends Component < Props , State > {
44
53
container : ?View ; // @see https://github.com/facebook/react-native/issues/15955
45
- scrollViewRef : ?ScrollView ; // @see https://github.com/facebook/react-native/issues/15955
54
+ scrollViewRef : ?ScrollComponent < any > ; // @see https://github.com/facebook/react-native/issues/15955
46
55
state : State ;
47
56
48
57
static defaultProps : DefaultProps = {
@@ -54,8 +63,8 @@ class ImageHeaderScrollView extends Component<Props, State> {
54
63
minHeight : 80 ,
55
64
minOverlayOpacity : 0 ,
56
65
renderFixedForeground : ( ) => < View /> ,
57
- renderForeground : ( ) => < View /> ,
58
66
renderHeader : ( ) => < View /> ,
67
+ ScrollViewComponent : ScrollView ,
59
68
} ;
60
69
61
70
constructor ( props : Props ) {
@@ -73,44 +82,6 @@ class ImageHeaderScrollView extends Component<Props, State> {
73
82
} ;
74
83
}
75
84
76
- /*
77
- * Expose `ScrollView` API so this component is composable
78
- * with any component that expects a `ScrollView`.
79
- */
80
- getScrollResponder ( ) {
81
- if ( ! this . scrollViewRef ) {
82
- return ;
83
- }
84
- return this . scrollViewRef . getScrollResponder ( ) ;
85
- }
86
- getScrollableNode ( ) {
87
- const responder = this . getScrollResponder ( ) ;
88
- if ( ! responder ) {
89
- return ;
90
- }
91
- return responder . getScrollableNode ( ) ;
92
- }
93
- getInnerViewNode ( ) {
94
- const responder = this . getScrollResponder ( ) ;
95
- if ( ! responder ) {
96
- return ;
97
- }
98
- return responder . getInnerViewNode ( ) ;
99
- }
100
- setNativeProps ( props : Props ) {
101
- if ( ! this . scrollViewRef ) {
102
- return ;
103
- }
104
- this . scrollViewRef . setNativeProps ( props ) ;
105
- }
106
- scrollTo ( ...args : * ) {
107
- const responder = this . getScrollResponder ( ) ;
108
- if ( ! responder ) {
109
- return;
110
- }
111
- responder . scrollTo ( ...args ) ;
112
- }
113
-
114
85
interpolateOnImageHeight ( outputRange : Array < number > ) {
115
86
const headerScrollDistance = this . props . maxHeight - this . props . minHeight ;
116
87
return this . state . scrollY . interpolate ( {
@@ -164,6 +135,11 @@ class ImageHeaderScrollView extends Component<Props, State> {
164
135
transform : [ { translateY : headerTranslate } ] ,
165
136
opacity : this . props . fadeOutForeground ? opacity : 1 ,
166
137
} ;
138
+
139
+ if ( ! this . props . renderForeground ) {
140
+ return < View /> ;
141
+ }
142
+
167
143
return (
168
144
< Animated . View style = { [ styles . header , headerTransformStyle ] } >
169
145
{ this . props . renderForeground ( ) }
@@ -203,10 +179,17 @@ class ImageHeaderScrollView extends Component<Props, State> {
203
179
this . container . measureInWindow ( ( x , y ) => this . setState ( ( ) => ( { pageY : y } ) ) ) ;
204
180
} ;
205
181
182
+ onScroll = ( e : * ) => {
183
+ if ( this . props . onScroll ) {
184
+ this . props . onScroll ( e ) ;
185
+ }
186
+ const scrollY = e . nativeEvent . contentOffset . y ;
187
+ this . state . scrollY . setValue ( scrollY ) ;
188
+ } ;
189
+
206
190
render ( ) {
207
191
/* eslint-disable no-unused-vars */
208
192
const {
209
- children,
210
193
childrenStyle,
211
194
overlayColor,
212
195
fadeOutForeground,
@@ -220,48 +203,178 @@ class ImageHeaderScrollView extends Component<Props, State> {
220
203
renderHeader,
221
204
renderTouchableFixedForeground,
222
205
style,
206
+ contentContainerStyle,
223
207
onScroll,
208
+ ScrollViewComponent,
224
209
...scrollViewProps
225
210
} = this . props ;
226
211
/* eslint-enable no-unused-vars */
227
212
228
- const headerScrollDistance = this . interpolateOnImageHeight ( [ maxHeight , maxHeight - minHeight ] ) ;
229
- const topMargin = this . interpolateOnImageHeight ( [ 0 , minHeight ] ) ;
230
-
231
- const childrenContainerStyle = StyleSheet . flatten ( [
232
- { transform : [ { translateY : headerScrollDistance } ] } ,
233
- { backgroundColor : 'white' , paddingBottom : maxHeight } ,
234
- childrenStyle ,
235
- ] ) ;
213
+ const inset = maxHeight - minHeight ;
236
214
237
215
return (
238
216
< View
239
- style = { styles . container }
217
+ style = { [ styles . container , { paddingTop : minHeight } ] }
240
218
ref = { ref => ( this . container = ref ) }
241
219
onLayout = { this . onContainerLayout }
242
220
>
243
221
{ this . renderHeader ( ) }
244
- < Animated . View style = { [ styles . container , { transform : [ { translateY : topMargin } ] } ] } >
245
- < ScrollView
246
- ref = { ref => ( this . scrollViewRef = ref ) }
247
- scrollEventThrottle = { 16 }
248
- { ...scrollViewProps }
249
- style = { [ styles . container , style ] }
250
- onScroll = { Animated . event (
251
- [ { nativeEvent : { contentOffset : { y : this . state . scrollY } } } ] ,
252
- {
253
- listener : onScroll ,
254
- }
255
- ) }
256
- >
257
- < Animated . View style = { childrenContainerStyle } > { children } </ Animated . View >
258
- </ ScrollView >
259
- </ Animated . View >
222
+ < ScrollViewComponent
223
+ ref = { ref => ( this . scrollViewRef = ref ) }
224
+ scrollEventThrottle = { 16 }
225
+ overScrollMode = "never"
226
+ { ...scrollViewProps }
227
+ contentContainerStyle = { [
228
+ {
229
+ backgroundColor : 'white' ,
230
+ marginTop : inset ,
231
+ paddingBottom : inset ,
232
+ } ,
233
+ contentContainerStyle ,
234
+ childrenStyle ,
235
+ ] }
236
+ style = { [ styles . container , style ] }
237
+ onScroll = { this . onScroll }
238
+ />
260
239
{ this . renderTouchableFixedForeground ( ) }
261
240
{ this . renderForeground ( ) }
262
241
</ View >
263
242
) ;
264
243
}
244
+
245
+ /*
246
+ * Expose `ScrollView` API so this component is composable
247
+ * with any component that expects a `ScrollView`.
248
+ */
249
+ getScrollableNode ( ) : any {
250
+ const responder = this . getScrollResponder ( ) ;
251
+ if ( ! responder ) {
252
+ return ;
253
+ }
254
+ return responder . getScrollableNode ( ) ;
255
+ }
256
+ getInnerViewNode ( ) : any {
257
+ const responder = this . getScrollResponder ( ) ;
258
+ if ( ! responder ) {
259
+ return ;
260
+ }
261
+ return responder . getInnerViewNode ( ) ;
262
+ }
263
+
264
+ scrollTo (
265
+ y ?: number | { x ?: number , y ?: number , animated ?: boolean } ,
266
+ x ?: number ,
267
+ animated ? : boolean
268
+ ) {
269
+ const responder = this . getScrollResponder ( ) ;
270
+ if ( ! responder ) {
271
+ return ;
272
+ }
273
+ responder . scrollTo ( y , x , animated ) ;
274
+ }
275
+
276
+ scrollToEnd ( params ?: ?{ animated ?: ?boolean } ) {
277
+ if (
278
+ this . scrollViewRef &&
279
+ this . scrollViewRef . scrollToEnd &&
280
+ typeof this . scrollViewRef . scrollToEnd === 'function'
281
+ ) {
282
+ this . scrollViewRef . scrollToEnd ( params ) ;
283
+ }
284
+ }
285
+
286
+ getScrollResponder ( ) : ?ScrollView {
287
+ if ( this . scrollViewRef && this . scrollViewRef . getScrollResponder ) {
288
+ return this . scrollViewRef . getScrollResponder ( ) ;
289
+ }
290
+ }
291
+
292
+ setNativeProps ( props : Object ) {
293
+ if ( this . scrollViewRef && this . scrollViewRef . setNativeProps ) {
294
+ this . scrollViewRef . setNativeProps ( props ) ;
295
+ }
296
+ }
297
+
298
+ recordInteraction ( ) {
299
+ if ( this . scrollViewRef && this . scrollViewRef . recordInteraction ) {
300
+ this . scrollViewRef . recordInteraction ( ) ;
301
+ }
302
+ }
303
+
304
+ flashScrollIndicators ( ) {
305
+ if ( this . scrollViewRef && this . scrollViewRef . flashScrollIndicators ) {
306
+ this . scrollViewRef . flashScrollIndicators ( ) ;
307
+ }
308
+ }
309
+
310
+ getMetrics ( ) : ?Object {
311
+ if (
312
+ this . scrollViewRef &&
313
+ this . scrollViewRef . getMetrics &&
314
+ typeof this . scrollViewRef . getMetrics === 'function'
315
+ ) {
316
+ return this . scrollViewRef . getMetrics ( ) ;
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Expose `FlatList` API so this component is composable
322
+ * with any component that expects a `FlatList`.
323
+ */
324
+ scrollToIndex ( params : {
325
+ animated ?: ?boolean ,
326
+ index : number ,
327
+ viewOffset ? : number ,
328
+ viewPosition ? : number ,
329
+ } ) {
330
+ if (
331
+ this . scrollViewRef &&
332
+ this . scrollViewRef . scrollToIndex &&
333
+ typeof this . scrollViewRef . scrollToIndex === 'function'
334
+ ) {
335
+ this . scrollViewRef . scrollToIndex ( params ) ;
336
+ }
337
+ }
338
+
339
+ scrollToItem ( params : { animated ?: ?boolean , item : any , viewPosition ?: number } ) {
340
+ if (
341
+ this . scrollViewRef &&
342
+ this . scrollViewRef . scrollToItem &&
343
+ typeof this . scrollViewRef . scrollToItem === 'function'
344
+ ) {
345
+ this . scrollViewRef . scrollToItem ( params ) ;
346
+ }
347
+ }
348
+
349
+ scrollToOffset ( params : { animated ?: ?boolean , offset : number } ) {
350
+ if (
351
+ this . scrollViewRef &&
352
+ this . scrollViewRef . scrollToOffset &&
353
+ typeof this . scrollViewRef . scrollToOffset === 'function'
354
+ ) {
355
+ this . scrollViewRef . scrollToOffset ( params ) ;
356
+ }
357
+ }
358
+
359
+ /**
360
+ * Expose `SectionList` API so this component is composable
361
+ * with any component that expects a `SectionList`.
362
+ */
363
+ scrollToLocation ( params : {
364
+ animated ?: ?boolean ,
365
+ itemIndex : number ,
366
+ sectionIndex : number ,
367
+ viewOffset ? : number ,
368
+ viewPosition ? : number ,
369
+ } ) {
370
+ if (
371
+ this . scrollViewRef &&
372
+ this . scrollViewRef . scrollToLocation &&
373
+ typeof this . scrollViewRef . scrollToLocation === 'function'
374
+ ) {
375
+ this . scrollViewRef . scrollToLocation ( params ) ;
376
+ }
377
+ }
265
378
}
266
379
267
380
ImageHeaderScrollView . childContextTypes = {
0 commit comments