@@ -20,6 +20,9 @@ const SceneComponent = require('./SceneComponent');
20
20
const DefaultTabBar = require ( './DefaultTabBar' ) ;
21
21
const ScrollableTabBar = require ( './ScrollableTabBar' ) ;
22
22
23
+ const AnimatedViewPagerAndroid = Platform . OS === 'android' ?
24
+ Animated . createAnimatedComponent ( ViewPagerAndroid ) :
25
+ undefined ;
23
26
24
27
const ScrollableTabView = React . createClass ( {
25
28
mixins : [ TimerMixin , ] ,
@@ -58,10 +61,49 @@ const ScrollableTabView = React.createClass({
58
61
} ,
59
62
60
63
getInitialState ( ) {
64
+ const containerWidth = Dimensions . get ( 'window' ) . width ;
65
+ let scrollValue ;
66
+ let scrollXIOS ;
67
+ let positionAndroid ;
68
+ let offsetAndroid ;
69
+
70
+ if ( Platform . OS === 'ios' ) {
71
+ scrollXIOS = new Animated . Value ( this . props . initialPage * containerWidth ) ;
72
+ const containerWidthAnimatedValue = new Animated . Value ( containerWidth ) ;
73
+ // Need to call __makeNative manually to avoid a native animated bug. See
74
+ // https://github.com/facebook/react-native/pull/14435
75
+ containerWidthAnimatedValue . __makeNative ( ) ;
76
+ scrollValue = Animated . divide ( scrollXIOS , containerWidthAnimatedValue ) ;
77
+
78
+ const callListeners = this . _polyfillAnimatedValue ( scrollValue ) ;
79
+ scrollXIOS . addListener (
80
+ ( { value, } ) => callListeners ( value / this . state . containerWidth )
81
+ ) ;
82
+ } else {
83
+ positionAndroid = new Animated . Value ( this . props . initialPage ) ;
84
+ offsetAndroid = new Animated . Value ( 0 ) ;
85
+ scrollValue = Animated . add ( positionAndroid , offsetAndroid ) ;
86
+
87
+ const callListeners = this . _polyfillAnimatedValue ( scrollValue ) ;
88
+ let positionAndroidValue = this . props . initialPage ;
89
+ let offsetAndroidValue = 0 ;
90
+ positionAndroid . addListener ( ( { value, } ) => {
91
+ positionAndroidValue = value ;
92
+ callListeners ( positionAndroidValue + offsetAndroidValue ) ;
93
+ } ) ;
94
+ offsetAndroid . addListener ( ( { value, } ) => {
95
+ offsetAndroidValue = value ;
96
+ callListeners ( positionAndroidValue + offsetAndroidValue ) ;
97
+ } ) ;
98
+ }
99
+
61
100
return {
62
101
currentPage : this . props . initialPage ,
63
- scrollValue : new Animated . Value ( this . props . initialPage ) ,
64
- containerWidth : Dimensions . get ( 'window' ) . width ,
102
+ scrollValue,
103
+ scrollXIOS,
104
+ positionAndroid,
105
+ offsetAndroid,
106
+ containerWidth,
65
107
sceneKeys : this . newSceneKeys ( { currentPage : this . props . initialPage , } ) ,
66
108
} ;
67
109
} ,
@@ -76,18 +118,27 @@ const ScrollableTabView = React.createClass({
76
118
}
77
119
} ,
78
120
121
+ componentWillUnmount ( ) {
122
+ if ( Platform . OS === 'ios' ) {
123
+ this . state . scrollXIOS . removeAllListeners ( ) ;
124
+ } else {
125
+ this . state . positionAndroid . removeAllListeners ( ) ;
126
+ this . state . offsetAndroid . removeAllListeners ( ) ;
127
+ }
128
+ } ,
129
+
79
130
goToPage ( pageNumber ) {
80
131
if ( Platform . OS === 'ios' ) {
81
132
const offset = pageNumber * this . state . containerWidth ;
82
133
if ( this . scrollView ) {
83
- this . scrollView . scrollTo ( { x : offset , y : 0 , animated : ! this . props . scrollWithoutAnimation , } ) ;
134
+ this . scrollView . getNode ( ) . scrollTo ( { x : offset , y : 0 , animated : ! this . props . scrollWithoutAnimation , } ) ;
84
135
}
85
136
} else {
86
137
if ( this . scrollView ) {
87
138
if ( this . props . scrollWithoutAnimation ) {
88
- this . scrollView . setPageWithoutAnimation ( pageNumber ) ;
139
+ this . scrollView . getNode ( ) . setPageWithoutAnimation ( pageNumber ) ;
89
140
} else {
90
- this . scrollView . setPage ( pageNumber ) ;
141
+ this . scrollView . getNode ( ) . setPage ( pageNumber ) ;
91
142
}
92
143
}
93
144
}
@@ -126,6 +177,31 @@ const ScrollableTabView = React.createClass({
126
177
return newKeys ;
127
178
} ,
128
179
180
+ // Animated.add and Animated.divide do not currently support listeners so
181
+ // we have to polyfill it here since a lot of code depends on being able
182
+ // to add a listener to `scrollValue`. See https://github.com/facebook/react-native/pull/12620.
183
+ _polyfillAnimatedValue ( animatedValue ) {
184
+
185
+ const listeners = new Set ( ) ;
186
+ const addListener = ( listener ) => {
187
+ listeners . add ( listener ) ;
188
+ } ;
189
+
190
+ const removeListener = ( listener ) => {
191
+ listeners . delete ( listener ) ;
192
+ } ;
193
+
194
+ const removeAllListeners = ( ) => {
195
+ listeners . clear ( ) ;
196
+ } ;
197
+
198
+ animatedValue . addListener = addListener ;
199
+ animatedValue . removeListener = removeListener ;
200
+ animatedValue . removeAllListeners = removeAllListeners ;
201
+
202
+ return ( value ) => listeners . forEach ( listener => listener ( { value, } ) ) ;
203
+ } ,
204
+
129
205
_shouldRenderSceneKey ( idx , currentPageKey ) {
130
206
let numOfSibling = this . props . prerenderingSiblingsNumber ;
131
207
return ( idx < ( currentPageKey + numOfSibling + 1 ) &&
@@ -143,20 +219,16 @@ const ScrollableTabView = React.createClass({
143
219
renderScrollableContent ( ) {
144
220
if ( Platform . OS === 'ios' ) {
145
221
const scenes = this . _composeScenes ( ) ;
146
- return < ScrollView
222
+ return < Animated . ScrollView
147
223
horizontal
148
224
pagingEnabled
149
225
automaticallyAdjustContentInsets = { false }
150
226
contentOffset = { { x : this . props . initialPage * this . state . containerWidth , } }
151
227
ref = { ( scrollView ) => { this . scrollView = scrollView ; } }
152
- onScroll = { ( e ) => {
153
- const offsetX = e . nativeEvent . contentOffset . x ;
154
- if ( offsetX === 0 && ! this . scrollOnMountCalled ) {
155
- this . scrollOnMountCalled = true ;
156
- } else {
157
- this . _updateScrollValue ( offsetX / this . state . containerWidth ) ;
158
- }
159
- } }
228
+ onScroll = { Animated . event (
229
+ [ { nativeEvent : { contentOffset : { x : this . state . scrollXIOS , } , } , } , ] ,
230
+ { useNativeDriver : true , listener : this . _onScroll , }
231
+ ) }
160
232
onMomentumScrollBegin = { this . _onMomentumScrollBeginAndEnd }
161
233
onMomentumScrollEnd = { this . _onMomentumScrollBeginAndEnd }
162
234
scrollEventThrottle = { 16 }
@@ -169,25 +241,33 @@ const ScrollableTabView = React.createClass({
169
241
{ ...this . props . contentProps }
170
242
>
171
243
{ scenes }
172
- </ ScrollView > ;
244
+ </ Animated . ScrollView > ;
173
245
} else {
174
246
const scenes = this . _composeScenes ( ) ;
175
- return < ViewPagerAndroid
247
+ return < AnimatedViewPagerAndroid
176
248
key = { this . _children ( ) . length }
177
249
style = { styles . scrollableContentAndroid }
178
250
initialPage = { this . props . initialPage }
179
251
onPageSelected = { this . _updateSelectedPage }
180
252
keyboardDismissMode = "on-drag"
181
253
scrollEnabled = { ! this . props . locked }
182
- onPageScroll = { ( e ) => {
183
- const { offset, position, } = e . nativeEvent ;
184
- this . _updateScrollValue ( position + offset ) ;
185
- } }
254
+ onPageScroll = { Animated . event (
255
+ [ {
256
+ nativeEvent : {
257
+ position : this . state . positionAndroid ,
258
+ offset : this . state . offsetAndroid ,
259
+ } ,
260
+ } , ] ,
261
+ {
262
+ useNativeDriver : true ,
263
+ listener : this . _onScroll ,
264
+ } ,
265
+ ) }
186
266
ref = { ( scrollView ) => { this . scrollView = scrollView ; } }
187
267
{ ...this . props . contentProps }
188
268
>
189
269
{ scenes }
190
- </ ViewPagerAndroid > ;
270
+ </ AnimatedViewPagerAndroid > ;
191
271
}
192
272
} ,
193
273
@@ -233,16 +313,34 @@ const ScrollableTabView = React.createClass({
233
313
} ) ;
234
314
} ,
235
315
236
- _updateScrollValue ( value ) {
237
- this . state . scrollValue . setValue ( value ) ;
238
- this . props . onScroll ( value ) ;
316
+ _onScroll ( e ) {
317
+ if ( Platform . OS === 'ios' ) {
318
+ const offsetX = e . nativeEvent . contentOffset . x ;
319
+ if ( offsetX === 0 && ! this . scrollOnMountCalled ) {
320
+ this . scrollOnMountCalled = true ;
321
+ } else {
322
+ this . props . onScroll ( offsetX / this . state . containerWidth ) ;
323
+ }
324
+ } else {
325
+ const { position, offset, } = e . nativeEvent ;
326
+ this . props . onScroll ( position + offset ) ;
327
+ }
239
328
} ,
240
329
241
330
_handleLayout ( e ) {
242
331
const { width, } = e . nativeEvent . layout ;
243
332
244
333
if ( Math . round ( width ) !== Math . round ( this . state . containerWidth ) ) {
245
- this . setState ( { containerWidth : width , } ) ;
334
+ if ( Platform . OS === 'ios' ) {
335
+ const containerWidthAnimatedValue = new Animated . Value ( width ) ;
336
+ // Need to call __makeNative manually to avoid a native animated bug. See
337
+ // https://github.com/facebook/react-native/pull/14435
338
+ containerWidthAnimatedValue . __makeNative ( ) ;
339
+ scrollValue = Animated . divide ( this . state . scrollXIOS , containerWidthAnimatedValue ) ;
340
+ this . setState ( { containerWidth : width , scrollValue, } ) ;
341
+ } else {
342
+ this . setState ( { containerWidth : width , } ) ;
343
+ }
246
344
this . requestAnimationFrame ( ( ) => {
247
345
this . goToPage ( this . state . currentPage ) ;
248
346
} ) ;
0 commit comments