11// @flow
22import React , { Component } from 'react' ;
33import PropTypes from 'prop-types' ;
4- import { Animated , ScrollView , StyleSheet , View } from 'react-native' ;
4+ import { Animated , ScrollView , StyleSheet , View , Platform } from 'react-native' ;
55import type { ViewProps } from 'ViewPropTypes' ;
6+ import type { FlatList , SectionList , ListView } from 'react-native' ;
67
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 & {
816 children ?: ?React$Element < any > ,
917 childrenStyle ?: ?any ,
1018 overlayColor : string ,
@@ -15,11 +23,10 @@ export type Props = {
1523 minHeight : number ,
1624 minOverlayOpacity : number ,
1725 renderFixedForeground : ( ) => React$Element < any > ,
18- renderForeground : ( ) => React$Element < any > ,
26+ renderForeground ? : ( ) => React$Element < any > ,
1927 renderHeader : ( ) => React$Element < any > ,
2028 renderTouchableFixedForeground ?: ?( ) => React$Element < any > ,
21- style ?: $PropertyType < ViewProps , 'style' > ,
22- onScroll ?: ?Function ,
29+ ScrollViewComponent : React$ComponentType < ScrollViewProps > ,
2330} ;
2431
2532export type DefaultProps = {
@@ -31,18 +38,20 @@ export type DefaultProps = {
3138 minHeight : number ,
3239 minOverlayOpacity : number ,
3340 renderFixedForeground : ( ) => React$Element < any > ,
34- renderForeground : ( ) => React$Element < any > ,
3541 renderHeader : ( ) => React$Element < any > ,
42+ ScrollViewComponent : React$ComponentType < ScrollViewProps > ,
3643} ;
3744
3845export type State = {
3946 scrollY : Animated . Value ,
4047 pageY : number ,
4148} ;
4249
50+ type ScrollComponent < ItemT > = FlatList < ItemT > | SectionList < ItemT > | ListView | ScrollView ;
51+
4352class ImageHeaderScrollView extends Component < Props , State > {
4453 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
4655 state : State ;
4756
4857 static defaultProps : DefaultProps = {
@@ -54,8 +63,8 @@ class ImageHeaderScrollView extends Component<Props, State> {
5463 minHeight : 80 ,
5564 minOverlayOpacity : 0 ,
5665 renderFixedForeground : ( ) => < View /> ,
57- renderForeground : ( ) => < View /> ,
5866 renderHeader : ( ) => < View /> ,
67+ ScrollViewComponent : ScrollView ,
5968 } ;
6069
6170 constructor ( props : Props ) {
@@ -73,44 +82,6 @@ class ImageHeaderScrollView extends Component<Props, State> {
7382 } ;
7483 }
7584
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-
11485 interpolateOnImageHeight ( outputRange : Array < number > ) {
11586 const headerScrollDistance = this . props . maxHeight - this . props . minHeight ;
11687 return this . state . scrollY . interpolate ( {
@@ -164,6 +135,11 @@ class ImageHeaderScrollView extends Component<Props, State> {
164135 transform : [ { translateY : headerTranslate } ] ,
165136 opacity : this . props . fadeOutForeground ? opacity : 1 ,
166137 } ;
138+
139+ if ( ! this . props . renderForeground ) {
140+ return < View /> ;
141+ }
142+
167143 return (
168144 < Animated . View style = { [ styles . header , headerTransformStyle ] } >
169145 { this . props . renderForeground ( ) }
@@ -203,10 +179,17 @@ class ImageHeaderScrollView extends Component<Props, State> {
203179 this . container . measureInWindow ( ( x , y ) => this . setState ( ( ) => ( { pageY : y } ) ) ) ;
204180 } ;
205181
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+
206190 render ( ) {
207191 /* eslint-disable no-unused-vars */
208192 const {
209- children,
210193 childrenStyle,
211194 overlayColor,
212195 fadeOutForeground,
@@ -220,48 +203,178 @@ class ImageHeaderScrollView extends Component<Props, State> {
220203 renderHeader,
221204 renderTouchableFixedForeground,
222205 style,
206+ contentContainerStyle,
223207 onScroll,
208+ ScrollViewComponent,
224209 ...scrollViewProps
225210 } = this . props ;
226211 /* eslint-enable no-unused-vars */
227212
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 ;
236214
237215 return (
238216 < View
239- style = { styles . container }
217+ style = { [ styles . container , { paddingTop : minHeight } ] }
240218 ref = { ref => ( this . container = ref ) }
241219 onLayout = { this . onContainerLayout }
242220 >
243221 { 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+ />
260239 { this . renderTouchableFixedForeground ( ) }
261240 { this . renderForeground ( ) }
262241 </ View >
263242 ) ;
264243 }
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+ }
265378}
266379
267380ImageHeaderScrollView . childContextTypes = {
0 commit comments