Skip to content
This repository has been archived by the owner on Nov 27, 2022. It is now read-only.

Commit

Permalink
fix: fix scrollable tab bar in RTL
Browse files Browse the repository at this point in the history
closes #184, closes #742
  • Loading branch information
satya164 committed Jun 18, 2019
1 parent c604ddb commit 575db5e
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 42 deletions.
4 changes: 1 addition & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions example/src/CustomIndicatorExample.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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, {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
62 changes: 26 additions & 36 deletions src/TabBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
ViewStyle,
TextStyle,
LayoutChangeEvent,
I18nManager,
Platform,
} from 'react-native';
import Animated from 'react-native-reanimated';
import TabBarItem from './TabBarItem';
Expand Down Expand Up @@ -98,7 +100,6 @@ export default class TabBar<T extends Route> extends React.Component<
private scrollAmount = new Animated.Value(0);

private scrollView: ScrollView | undefined;
private isMomentumScroll: boolean = false;

private getTabWidth = (props: Props<T>, state: State) => {
const { layout } = state;
Expand Down Expand Up @@ -126,6 +127,9 @@ export default class TabBar<T extends Route> extends React.Component<
return layout.width / navigationState.routes.length;
};

private getMaxScrollDistance = (tabBarWidth: number, layoutWidth: number) =>
tabBarWidth - layoutWidth;

private normalizeScrollValue = (
props: Props<T>,
state: State,
Expand All @@ -138,9 +142,16 @@ export default class TabBar<T extends Route> 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<T>, state: State, i: number) => {
Expand All @@ -162,32 +173,6 @@ export default class TabBar<T extends Route> 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;

Expand All @@ -206,8 +191,14 @@ export default class TabBar<T extends Route> extends React.Component<
});
};

private getTranslateX = memoize((scrollAmount: Animated.Node<number>) =>
Animated.multiply(scrollAmount, -1)
private getTranslateX = memoize(
(scrollAmount: Animated.Node<number>, maxScrollDistance: number) =>
Animated.multiply(
Platform.OS === 'android' && I18nManager.isRTL
? Animated.sub(maxScrollDistance, scrollAmount)
: scrollAmount,
I18nManager.isRTL ? 1 : -1
)
);

render() {
Expand Down Expand Up @@ -240,7 +231,10 @@ export default class TabBar<T extends Route> 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 (
<Animated.View
Expand Down Expand Up @@ -292,10 +286,6 @@ export default class TabBar<T extends Route> 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();
Expand Down

0 comments on commit 575db5e

Please sign in to comment.