Skip to content

Commit 4723846

Browse files
authored
Merge pull request #8 from christophby/tab-layout-calc
feat: calc slideAnimation by tab layout
2 parents 57c8fb2 + 400ce01 commit 4723846

File tree

3 files changed

+46
-20
lines changed

3 files changed

+46
-20
lines changed

lib/SegmentedControl.style.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ export default StyleSheet.create<any>({
99
borderRadius: 8,
1010
backgroundColor: "#F3F5F6",
1111
},
12+
tabsContainer: {
13+
flex: 1,
14+
flexDirection: "row",
15+
},
1216
tab: {
1317
flex: 1,
1418
paddingVertical: 8, // iOS Default

lib/SegmentedControl.tsx

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
TextStyle,
1010
I18nManager,
1111
LayoutChangeEvent,
12+
LayoutRectangle,
1213
} from "react-native";
1314
import styles from "./SegmentedControl.style";
1415

@@ -19,7 +20,7 @@ interface SegmentedControlProps {
1920
activeTabColor?: string;
2021
gap?: number;
2122
style?: StyleProp<ViewStyle>;
22-
tabStyle?: StyleProp<ViewStyle>;
23+
tabStyle?: StyleProp<ViewStyle> | ((index: number) => StyleProp<ViewStyle>);
2324
textStyle?: StyleProp<TextStyle>;
2425
selectedTabStyle?: StyleProp<ViewStyle>;
2526
onChange: (index: number) => void;
@@ -40,10 +41,11 @@ const SegmentedControl: React.FC<SegmentedControlProps> = ({
4041
activeTabColor = "#fff",
4142
}) => {
4243
const [slideAnimation, _] = useState(new Animated.Value(0));
43-
const [width, setWidth] = useState<number>(0);
44-
const translateValue = width / tabs.length;
45-
const tabWidth = Math.max(width / tabs.length - gap * 2, 0);
4644
const [localCurrentIndex, setCurrentIndex] = useState<number>(initialIndex);
45+
const [tabLayouts, setTabLayouts] = useState<{
46+
[tabIndex: number]: LayoutRectangle;
47+
}>({});
48+
4749
const currentIndex = value ?? localCurrentIndex;
4850

4951
const handleTabPress = useCallback(
@@ -54,33 +56,50 @@ const SegmentedControl: React.FC<SegmentedControlProps> = ({
5456
[onChange],
5557
);
5658

57-
const handleLayout = useCallback(
58-
({ nativeEvent }: LayoutChangeEvent) => {
59-
setWidth(nativeEvent.layout.width);
60-
},
61-
[setWidth],
62-
);
63-
6459
useEffect(() => {
6560
Animated.spring(slideAnimation, {
66-
toValue: (I18nManager.isRTL ? -1 : 1) * currentIndex * translateValue,
61+
toValue:
62+
(I18nManager.isRTL ? -1 : 1) * (tabLayouts[currentIndex]?.x || 0),
6763
stiffness: 180,
6864
damping: 25,
6965
mass: 1,
7066
useNativeDriver: true,
7167
}).start();
72-
}, [currentIndex, slideAnimation, translateValue]);
68+
}, [currentIndex, slideAnimation, tabLayouts]);
69+
70+
const onLayoutTab = useCallback(
71+
(index: number, { nativeEvent }: LayoutChangeEvent) => {
72+
setTabLayouts((prev) => ({ ...prev, [index]: nativeEvent.layout }));
73+
},
74+
[],
75+
);
76+
77+
const tabSpecificStyle = useCallback(
78+
(tabIndex: number) => {
79+
if (typeof tabStyle === "function") {
80+
return tabStyle(tabIndex);
81+
}
82+
83+
return tabStyle;
84+
},
85+
[tabStyle],
86+
);
7387

7488
const renderSelectedTab = useCallback(
7589
() => (
7690
<Animated.View
7791
style={[
78-
styles.activeTab(tabWidth, gap, activeTabColor, slideAnimation),
92+
styles.activeTab(
93+
tabLayouts[currentIndex]?.width || 0,
94+
gap,
95+
activeTabColor,
96+
slideAnimation,
97+
),
7998
selectedTabStyle,
8099
]}
81100
/>
82101
),
83-
[activeTabColor, gap, selectedTabStyle, slideAnimation, tabWidth],
102+
[activeTabColor, gap, selectedTabStyle, slideAnimation, tabLayouts],
84103
);
85104

86105
const renderTab = (tab: any, index: number) => {
@@ -90,8 +109,9 @@ const SegmentedControl: React.FC<SegmentedControlProps> = ({
90109
<TouchableOpacity
91110
key={index}
92111
activeOpacity={0.5}
93-
style={[styles.tab, tabStyle]}
112+
style={[styles.tab, tabSpecificStyle(index)]}
94113
onPress={() => handleTabPress(index)}
114+
onLayout={(e) => onLayoutTab(index, e)}
95115
>
96116
{!isTabText ? (
97117
tab
@@ -112,9 +132,11 @@ const SegmentedControl: React.FC<SegmentedControlProps> = ({
112132
};
113133

114134
return (
115-
<View style={[styles.container, style]} onLayout={handleLayout}>
135+
<View style={[styles.container, style]}>
116136
{renderSelectedTab()}
117-
{tabs.map((tab, index: number) => renderTab(tab, index))}
137+
<View style={[styles.tabsContainer, { marginHorizontal: gap }]}>
138+
{tabs.map((tab, index: number) => renderTab(tab, index))}
139+
</View>
118140
</View>
119141
);
120142
};

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)