Skip to content

Commit ed07ac3

Browse files
authored
feat: Animate segment selection on Android (#77)
1 parent f354122 commit ed07ac3

File tree

2 files changed

+49
-13
lines changed

2 files changed

+49
-13
lines changed

js/SegmentedControl.js

+48-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
'use strict';
66

77
import * as React from 'react';
8-
import {StyleSheet, View} from 'react-native';
8+
import {Animated, Easing, StyleSheet, View} from 'react-native';
99
import {SegmentedControlTab} from './SegmentedControlTab';
1010

1111
import type {SegmentedControlProps} from './types';
@@ -27,6 +27,9 @@ const SegmentedControl = ({
2727
backgroundColor,
2828
fontSize,
2929
}: SegmentedControlProps) => {
30+
const [segmentWidth, setSegmentWidth] = React.useState(0);
31+
const animation = React.useRef(new Animated.Value(0)).current;
32+
3033
const handleChange = (index: number) => {
3134
// mocks iOS's nativeEvent
3235
const event: any = {
@@ -38,14 +41,49 @@ const SegmentedControl = ({
3841
onChange && onChange(event);
3942
onValueChange && onValueChange(values[index]);
4043
};
44+
45+
React.useEffect(() => {
46+
if (animation && segmentWidth) {
47+
Animated.timing(animation, {
48+
toValue: segmentWidth * (selectedIndex || 0),
49+
duration: 300,
50+
easing: Easing.out(Easing.quad),
51+
useNativeDriver: true,
52+
}).start();
53+
}
54+
}, [animation, segmentWidth, selectedIndex]);
55+
4156
return (
4257
<View
4358
style={[
4459
styles.default,
4560
style,
4661
backgroundColor && {backgroundColor},
4762
!enabled && styles.disabled,
48-
]}>
63+
]}
64+
onLayout={({
65+
nativeEvent: {
66+
layout: {width},
67+
},
68+
}) => {
69+
const newSegmentWidth = values.length ? width / values.length : 0;
70+
if (newSegmentWidth !== segmentWidth) {
71+
animation.setValue(newSegmentWidth * (selectedIndex || 0));
72+
setSegmentWidth(newSegmentWidth);
73+
}
74+
}}>
75+
{selectedIndex != null && segmentWidth ? (
76+
<Animated.View
77+
style={[
78+
styles.slider,
79+
{
80+
transform: [{translateX: animation}],
81+
width: segmentWidth,
82+
backgroundColor: tintColor || 'white',
83+
},
84+
]}
85+
/>
86+
) : null}
4987
{values &&
5088
values.map((value, index) => {
5189
return (
@@ -70,6 +108,8 @@ const SegmentedControl = ({
70108

71109
const styles = StyleSheet.create({
72110
default: {
111+
overflow: 'hidden',
112+
position: 'relative',
73113
flexDirection: 'row',
74114
justifyContent: 'space-evenly',
75115
alignContent: 'center',
@@ -80,6 +120,12 @@ const styles = StyleSheet.create({
80120
disabled: {
81121
opacity: 0.4,
82122
},
123+
slider: {
124+
position: 'absolute',
125+
left: 0,
126+
height: '100%',
127+
borderRadius: 5,
128+
},
83129
});
84130

85131
export default SegmentedControl;

js/SegmentedControlTab.js

+1-11
Original file line numberDiff line numberDiff line change
@@ -42,22 +42,12 @@ export const SegmentedControlTab = ({
4242
};
4343
const color = getColor();
4444

45-
const getBackgroundColor = () => {
46-
if (selected && tintColor) {
47-
return tintColor;
48-
}
49-
return 'white';
50-
};
5145
return (
5246
<TouchableOpacity
5347
style={styles.container}
5448
disabled={!enabled}
5549
onPress={onSelect}>
56-
<View
57-
style={[
58-
styles.default,
59-
selected && {backgroundColor: getBackgroundColor()},
60-
]}>
50+
<View style={[styles.default]}>
6151
<Text
6252
style={[
6353
{color},

0 commit comments

Comments
 (0)