@@ -20,6 +20,9 @@ const SceneComponent = require('./SceneComponent');
2020const  DefaultTabBar  =  require ( './DefaultTabBar' ) ; 
2121const  ScrollableTabBar  =  require ( './ScrollableTabBar' ) ; 
2222
23+ const  AnimatedViewPagerAndroid  =  Platform . OS  ===  'android'  ?
24+   Animated . createAnimatedComponent ( ViewPagerAndroid )  :
25+   undefined ; 
2326
2427const  ScrollableTabView  =  React . createClass ( { 
2528  mixins : [ TimerMixin ,  ] , 
@@ -58,10 +61,49 @@ const ScrollableTabView = React.createClass({
5861  } , 
5962
6063  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+ 
61100    return  { 
62101      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, 
65107      sceneKeys : this . newSceneKeys ( {  currentPage : this . props . initialPage ,  } ) , 
66108    } ; 
67109  } , 
@@ -76,18 +118,27 @@ const ScrollableTabView = React.createClass({
76118    } 
77119  } , 
78120
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+ 
79130  goToPage ( pageNumber )  { 
80131    if  ( Platform . OS  ===  'ios' )  { 
81132      const  offset  =  pageNumber  *  this . state . containerWidth ; 
82133      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 ,  } ) ; 
84135      } 
85136    }  else  { 
86137      if  ( this . scrollView )  { 
87138        if  ( this . props . scrollWithoutAnimation )  { 
88-           this . scrollView . setPageWithoutAnimation ( pageNumber ) ; 
139+           this . scrollView . getNode ( ) . setPageWithoutAnimation ( pageNumber ) ; 
89140        }  else  { 
90-           this . scrollView . setPage ( pageNumber ) ; 
141+           this . scrollView . getNode ( ) . setPage ( pageNumber ) ; 
91142        } 
92143      } 
93144    } 
@@ -126,6 +177,31 @@ const ScrollableTabView = React.createClass({
126177    return  newKeys ; 
127178  } , 
128179
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+ 
129205  _shouldRenderSceneKey ( idx ,  currentPageKey )  { 
130206    let  numOfSibling  =  this . props . prerenderingSiblingsNumber ; 
131207    return  ( idx  <  ( currentPageKey  +  numOfSibling  +  1 )  && 
@@ -143,20 +219,16 @@ const ScrollableTabView = React.createClass({
143219  renderScrollableContent ( )  { 
144220    if  ( Platform . OS  ===  'ios' )  { 
145221      const  scenes  =  this . _composeScenes ( ) ; 
146-       return  < ScrollView 
222+       return  < Animated . ScrollView 
147223        horizontal 
148224        pagingEnabled 
149225        automaticallyAdjustContentInsets = { false } 
150226        contentOffset = { {  x : this . props . initialPage  *  this . state . containerWidth ,  } } 
151227        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+         ) } 
160232        onMomentumScrollBegin = { this . _onMomentumScrollBeginAndEnd } 
161233        onMomentumScrollEnd = { this . _onMomentumScrollBeginAndEnd } 
162234        scrollEventThrottle = { 16 } 
@@ -169,25 +241,33 @@ const ScrollableTabView = React.createClass({
169241        { ...this . props . contentProps } 
170242      > 
171243          { scenes } 
172-       </ ScrollView > ; 
244+       </ Animated . ScrollView > ; 
173245    }  else  { 
174246      const  scenes  =  this . _composeScenes ( ) ; 
175-       return  < ViewPagerAndroid 
247+       return  < AnimatedViewPagerAndroid 
176248        key = { this . _children ( ) . length } 
177249        style = { styles . scrollableContentAndroid } 
178250        initialPage = { this . props . initialPage } 
179251        onPageSelected = { this . _updateSelectedPage } 
180252        keyboardDismissMode = "on-drag" 
181253        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+         ) } 
186266        ref = { ( scrollView )  =>  {  this . scrollView  =  scrollView ;  } } 
187267        { ...this . props . contentProps } 
188268      > 
189269        { scenes } 
190-       </ ViewPagerAndroid > ; 
270+       </ AnimatedViewPagerAndroid > ; 
191271    } 
192272  } , 
193273
@@ -233,16 +313,34 @@ const ScrollableTabView = React.createClass({
233313    } ) ; 
234314  } , 
235315
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+     } 
239328  } , 
240329
241330  _handleLayout ( e )  { 
242331    const  {  width,  }  =  e . nativeEvent . layout ; 
243332
244333    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+       } 
246344      this . requestAnimationFrame ( ( )  =>  { 
247345        this . goToPage ( this . state . currentPage ) ; 
248346      } ) ; 
0 commit comments