diff --git a/.circleci/config.yml b/.circleci/config.yml index b090ef56..13fa387e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -20,9 +20,7 @@ jobs: keys: - v1-dependencies-example-{{ checksum "example/package.json" }} - v1-dependencies-example- - - run: | - yarn install --cwd example - yarn install + - run: yarn bootstrap - save_cache: key: v1-dependencies-{{ checksum "package.json" }} paths: node_modules diff --git a/example/src/CustomIndicatorExample.tsx b/example/src/CustomIndicatorExample.tsx index 9e25e4ea..2b0f93e6 100644 --- a/example/src/CustomIndicatorExample.tsx +++ b/example/src/CustomIndicatorExample.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { View, Text, StyleSheet } from 'react-native'; +import { View, Text, StyleSheet, I18nManager } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import { TabView, @@ -85,7 +85,9 @@ export default class CustomIndicatorExample extends React.Component<{}, State> { const translateX = Animated.interpolate(position, { inputRange: inputRange, - outputRange: inputRange.map(x => Math.round(x) * width), + outputRange: inputRange.map( + x => Math.round(x) * width * (I18nManager.isRTL ? -1 : 1) + ), }); const backgroundColor = Animated.interpolate(position, { diff --git a/package.json b/package.json index 95051c29..23a8fe41 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "lint": "eslint --ext .js,.ts,.tsx .", "release": "release-it", "example": "yarn --cwd example", - "bootstrap": "yarn && yarn example", + "bootstrap": "yarn example && yarn", "prepare": "bob build" }, "publishConfig": { diff --git a/src/TabBar.tsx b/src/TabBar.tsx index c799cf0e..8a856756 100644 --- a/src/TabBar.tsx +++ b/src/TabBar.tsx @@ -7,6 +7,8 @@ import { ViewStyle, TextStyle, LayoutChangeEvent, + I18nManager, + Platform, } from 'react-native'; import Animated from 'react-native-reanimated'; import TabBarItem from './TabBarItem'; @@ -98,7 +100,6 @@ export default class TabBar extends React.Component< private scrollAmount = new Animated.Value(0); private scrollView: ScrollView | undefined; - private isMomentumScroll: boolean = false; private getTabWidth = (props: Props, state: State) => { const { layout } = state; @@ -126,6 +127,9 @@ export default class TabBar extends React.Component< return layout.width / navigationState.routes.length; }; + private getMaxScrollDistance = (tabBarWidth: number, layoutWidth: number) => + tabBarWidth - layoutWidth; + private normalizeScrollValue = ( props: Props, state: State, @@ -138,9 +142,16 @@ export default class TabBar extends React.Component< tabWidth * navigationState.routes.length, layout.width ); - const maxDistance = tabBarWidth - layout.width; + const maxDistance = this.getMaxScrollDistance(tabBarWidth, layout.width); + const scrollValue = Math.max(Math.min(value, maxDistance), 0); + + if (Platform.OS === 'android' && I18nManager.isRTL) { + // On Android, scroll value is not applied in reverse in RTL + // so we need to manually adjust it to apply correct value + return maxDistance - scrollValue; + } - return Math.max(Math.min(value, maxDistance), 0); + return scrollValue; }; private getScrollAmount = (props: Props, state: State, i: number) => { @@ -162,32 +173,6 @@ export default class TabBar extends React.Component< } }; - private handleBeginDrag = () => { - // onScrollBeginDrag fires when user touches the ScrollView - this.isMomentumScroll = false; - }; - - private handleEndDrag = () => { - // onScrollEndDrag fires when user lifts his finger - // onMomentumScrollBegin fires after touch end - // run the logic in next frame so we get onMomentumScrollBegin first - requestAnimationFrame(() => { - if (this.isMomentumScroll) { - return; - } - }); - }; - - private handleMomentumScrollBegin = () => { - // onMomentumScrollBegin fires on flick, as well as programmatic scroll - this.isMomentumScroll = true; - }; - - private handleMomentumScrollEnd = () => { - // onMomentumScrollEnd fires when the scroll finishes - this.isMomentumScroll = false; - }; - private handleLayout = (e: LayoutChangeEvent) => { const { height, width } = e.nativeEvent.layout; @@ -206,8 +191,14 @@ export default class TabBar extends React.Component< }); }; - private getTranslateX = memoize((scrollAmount: Animated.Node) => - Animated.multiply(scrollAmount, -1) + private getTranslateX = memoize( + (scrollAmount: Animated.Node, maxScrollDistance: number) => + Animated.multiply( + Platform.OS === 'android' && I18nManager.isRTL + ? Animated.sub(maxScrollDistance, scrollAmount) + : scrollAmount, + I18nManager.isRTL ? 1 : -1 + ) ); render() { @@ -240,7 +231,10 @@ export default class TabBar extends React.Component< const { routes } = navigationState; const tabWidth = this.getTabWidth(this.props, this.state); const tabBarWidth = tabWidth * routes.length; - const translateX = this.getTranslateX(this.scrollAmount); + const translateX = this.getTranslateX( + this.scrollAmount, + this.getMaxScrollDistance(tabBarWidth, layout.width) + ); return ( extends React.Component< ], { useNativeDriver: true } )} - onScrollBeginDrag={this.handleBeginDrag} - onScrollEndDrag={this.handleEndDrag} - onMomentumScrollBegin={this.handleMomentumScrollBegin} - onMomentumScrollEnd={this.handleMomentumScrollEnd} ref={el => { // @ts-ignore this.scrollView = el && el.getNode();