Skip to content

Commit f21a719

Browse files
committed
Fix/tab controller center android with rtl (#1178)
(cherry picked from commit 5a84e93)
1 parent a241931 commit f21a719

File tree

7 files changed

+98
-16
lines changed

7 files changed

+98
-16
lines changed

src/commons/withScrollReached.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,13 @@ function withScrollReached<PROPS, STATICS = {}>(WrappedComponent: React.Componen
6363
const horizontal = options.horizontal;
6464
const threshold = options.threshold || DEFAULT_THRESHOLD;
6565
const layoutSize = horizontal ? layoutWidth : layoutHeight;
66-
const offset = horizontal ? offsetX : offsetY;
66+
let offset = horizontal ? offsetX : offsetY;
6767
const contentSize = horizontal ? contentWidth : contentHeight;
68+
if (horizontal && Constants.isRTL && Constants.isAndroid) {
69+
const scrollingWidth = Math.max(0, contentSize - layoutSize);
70+
offset = scrollingWidth - offset;
71+
}
72+
6873
const closeToStart = offset <= threshold;
6974
if (closeToStart !== isScrollAtStart) {
7075
setScrollAtStart(closeToStart);

src/components/scrollBar/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ class ScrollBar extends Component<Props, State> {
171171
onLayout = (event: LayoutChangeEvent) => {
172172
this.containerWidth = event.nativeEvent.layout.width;
173173

174+
_.invoke(this.props, 'onLayout', event);
174175
// 1 - for race condition, in case onContentSizeChange() is called before
175176
// 0 - for containerWidth change, when onContentSizeChange() is called first
176177
this.setState({gradientOpacity: new Animated.Value(this.scrollContentWidth > this.containerWidth ? 1 : 0)});

src/components/tabController/FadedScrollView.tsx

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, {useCallback} from 'react';
2-
import {ViewProps, ScrollView, ScrollViewProps, NativeSyntheticEvent, NativeScrollEvent} from 'react-native';
2+
import {ViewProps, ScrollView, ScrollViewProps, NativeSyntheticEvent, NativeScrollEvent, LayoutChangeEvent} from 'react-native';
33
import Fader from '../fader';
44
import useScrollEnabler from '../../hooks/useScrollEnabler';
55
import useScrollReached from '../../hooks/useScrollReached';
@@ -15,7 +15,13 @@ type Props = FadedScrollViewProps & ForwardRefInjectedProps;
1515
const FADER_SIZE = 76;
1616

1717
const FadedScrollView = (props: Props) => {
18-
const {children, onScroll: propsOnScroll, ...other} = props;
18+
const {
19+
children,
20+
onScroll: propsOnScroll,
21+
onContentSizeChange: propsOnContentSizeChange,
22+
onLayout: propsOnLayout,
23+
...other
24+
} = props;
1925
const {onContentSizeChange, onLayout, scrollEnabled} = useScrollEnabler({horizontal: true});
2026
const {onScroll: onScrollReached, isScrollAtStart, isScrollAtEnd} = useScrollReached({
2127
horizontal: true,
@@ -31,6 +37,16 @@ const FadedScrollView = (props: Props) => {
3137
},
3238
[onScrollReached, propsOnScroll]);
3339

40+
const _onContentSizeChange = useCallback((w: number, h: number) => {
41+
propsOnContentSizeChange?.(w, h);
42+
onContentSizeChange?.(w, h);
43+
}, [propsOnContentSizeChange, onContentSizeChange]);
44+
45+
const _onLayout = useCallback((event: LayoutChangeEvent) => {
46+
propsOnLayout?.(event);
47+
onLayout?.(event);
48+
}, [propsOnLayout, onLayout]);
49+
3450
if (children) {
3551
return (
3652
<>
@@ -41,8 +57,8 @@ const FadedScrollView = (props: Props) => {
4157
decelerationRate={'fast'}
4258
{...other}
4359
scrollEnabled={scrollEnabled}
44-
onContentSizeChange={onContentSizeChange}
45-
onLayout={onLayout}
60+
onContentSizeChange={_onContentSizeChange}
61+
onLayout={_onLayout}
4662
onScroll={onScroll}
4763
ref={props.forwardedRef}
4864
>

src/components/tabController/TabBar.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ const TabBar = (props: Props) => {
198198

199199
const itemsCount = useRef<number>(items ? _.size(items) : React.Children.count(children.current));
200200

201-
const {scrollViewRef: tabBar, onItemLayout, itemsWidths, focusIndex} = useScrollToItem({
201+
const {scrollViewRef: tabBar, onItemLayout, itemsWidths, focusIndex, onContentSizeChange, onLayout} = useScrollToItem({
202202
itemsCount: itemsCount.current,
203203
selectedIndex,
204204
offsetType: centerSelected ? useScrollToItem.offsetType.CENTER : useScrollToItem.offsetType.DYNAMIC
@@ -352,6 +352,8 @@ const TabBar = (props: Props) => {
352352
contentContainerStyle={scrollViewContainerStyle}
353353
scrollEnabled // TODO:
354354
testID={testID}
355+
onContentSizeChange={onContentSizeChange}
356+
onLayout={onLayout}
355357
>
356358
<View style={indicatorContainerStyle}>{renderTabBarItems}</View>
357359
{selectedIndicator}

src/hooks/useScrollReached/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,13 @@ const useScrollReached = (props: ScrollEnablerProps = {}): ScrollEnablerResultPr
4646
} = event;
4747

4848
const layoutSize = horizontal ? layoutWidth : layoutHeight;
49-
const offset = horizontal ? offsetX : offsetY;
49+
let offset = horizontal ? offsetX : offsetY;
5050
const contentSize = horizontal ? contentWidth : contentHeight;
51+
if (horizontal && Constants.isRTL && Constants.isAndroid) {
52+
const scrollingWidth = Math.max(0, contentSize - layoutSize);
53+
offset = scrollingWidth - offset;
54+
}
55+
5156
const closeToStart = offset <= threshold;
5257
if (closeToStart !== isScrollAtStart) {
5358
setScrollAtStart(closeToStart);

src/hooks/useScrollTo/index.ts

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import _ from 'lodash';
22
import {RefObject, useCallback, useRef} from 'react';
3-
import {ScrollView, FlatList} from 'react-native';
3+
import {ScrollView, FlatList, LayoutChangeEvent} from 'react-native';
4+
import {Constants} from '../../helpers';
45

56
export type ScrollToSupportedViews = ScrollView | FlatList;
67

@@ -22,25 +23,63 @@ export type ScrollToResultProps<T extends ScrollToSupportedViews> = {
2223
scrollViewRef: RefObject<T>;
2324
/**
2425
* scrollTo callback.
25-
* scrollToOffset - the x or y to scroll to.
26+
* offset - the x or y to scroll to.
2627
* animated - should the scroll be animated (default is true)
2728
*/
28-
scrollTo: (scrollToOffset: number, animated?: boolean) => void;
29+
scrollTo: (offset: number, animated?: boolean) => void;
30+
/**
31+
* onContentSizeChange callback (should be set to your onContentSizeChange).
32+
* Needed for RTL support on Android.
33+
*/
34+
onContentSizeChange: (contentWidth: number, contentHeight: number) => void;
35+
/**
36+
* onLayout callback (should be set to your onLayout).
37+
* Needed for RTL support on Android.
38+
*/
39+
onLayout: (event: LayoutChangeEvent) => void;
2940
};
3041

3142
const useScrollTo = <T extends ScrollToSupportedViews>(props: ScrollToProps<T>): ScrollToResultProps<T> => {
3243
const {scrollViewRef: propsScrollViewRef, horizontal = true} = props;
3344
const newScrollViewRef = useRef<T>(null);
3445
const scrollViewRef = propsScrollViewRef || newScrollViewRef;
46+
const contentSize = useRef<number | undefined>(undefined);
47+
const containerSize = useRef<number | undefined>(undefined);
48+
49+
const onContentSizeChange = useCallback((contentWidth: number, contentHeight: number) => {
50+
contentSize.current = horizontal ? contentWidth : contentHeight;
51+
},
52+
[horizontal]);
53+
54+
const onLayout = useCallback((event: LayoutChangeEvent) => {
55+
const {
56+
nativeEvent: {
57+
layout: {width, height}
58+
}
59+
} = event;
60+
containerSize.current = horizontal ? width : height;
61+
},
62+
[horizontal]);
63+
64+
const scrollTo = useCallback((offset: number, animated = true) => {
65+
if (
66+
horizontal &&
67+
Constants.isRTL &&
68+
Constants.isAndroid &&
69+
!_.isUndefined(contentSize.current) &&
70+
!_.isUndefined(containerSize.current)
71+
) {
72+
const scrollingWidth = Math.max(0, contentSize.current - containerSize.current);
73+
offset = scrollingWidth - offset;
74+
}
3575

36-
const scrollTo = useCallback((scrollTo: number, animated = true) => {
3776
// @ts-ignore
3877
if (_.isFunction(scrollViewRef.current.scrollToOffset)) {
3978
// @ts-ignore
40-
scrollViewRef.current.scrollToOffset({offset: scrollTo, animated});
79+
scrollViewRef.current.scrollToOffset({offset, animated});
4180
// @ts-ignore
4281
} else if (_.isFunction(scrollViewRef.current.scrollTo)) {
43-
const scrollToXY = horizontal ? {x: scrollTo} : {y: scrollTo};
82+
const scrollToXY = horizontal ? {x: offset} : {y: offset};
4483
// @ts-ignore
4584
scrollViewRef.current.scrollTo({...scrollToXY, animated});
4685
}
@@ -49,7 +88,9 @@ const useScrollTo = <T extends ScrollToSupportedViews>(props: ScrollToProps<T>):
4988

5089
return {
5190
scrollViewRef,
52-
scrollTo
91+
scrollTo,
92+
onContentSizeChange,
93+
onLayout
5394
};
5495
};
5596

src/hooks/useScrollToItem/index.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,16 @@ export type ScrollToItemResultProps<T extends ScrollToSupportedViews> = Pick<Scr
6060
* Use in order to focus the item with the specified index (use when the selectedIndex is not changed)
6161
*/
6262
focusIndex: (index: number, animated?: boolean) => void;
63+
/**
64+
* onContentSizeChange callback (should be set to your onContentSizeChange).
65+
* Needed for RTL support on Android.
66+
*/
67+
onContentSizeChange: (contentWidth: number, contentHeight: number) => void;
68+
/**
69+
* onLayout callback (should be set to your onLayout).
70+
* Needed for RTL support on Android.
71+
*/
72+
onLayout: (event: LayoutChangeEvent) => void;
6373
};
6474

6575
const useScrollToItem = <T extends ScrollToSupportedViews>(props: ScrollToItemProps<T>): ScrollToItemResultProps<T> => {
@@ -75,7 +85,7 @@ const useScrollToItem = <T extends ScrollToSupportedViews>(props: ScrollToItemPr
7585
const itemsWidths = useRef<(number | null)[]>(_.times(itemsCount, () => null));
7686
const currentIndex = useRef<number>(selectedIndex || 0);
7787
const [offsets, setOffsets] = useState<Offsets>({CENTER: [], LEFT: [], RIGHT: []});
78-
const {scrollViewRef, scrollTo} = useScrollTo<T>({scrollViewRef: propsScrollViewRef});
88+
const {scrollViewRef, scrollTo, onContentSizeChange, onLayout} = useScrollTo<T>({scrollViewRef: propsScrollViewRef});
7989

8090
// TODO: reset?
8191
// useEffect(() => {
@@ -151,7 +161,9 @@ const useScrollToItem = <T extends ScrollToSupportedViews>(props: ScrollToItemPr
151161
scrollViewRef,
152162
onItemLayout,
153163
itemsWidths: offsets.CENTER.length > 0 ? (itemsWidths.current as number[]) : [],
154-
focusIndex
164+
focusIndex,
165+
onContentSizeChange,
166+
onLayout
155167
};
156168
};
157169

0 commit comments

Comments
 (0)