Skip to content

Instantly share code, notes, and snippets.

@Aryk
Last active September 2, 2023 02:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Aryk/380dffa6464603ec7b1e0c3598edff65 to your computer and use it in GitHub Desktop.
Save Aryk/380dffa6464603ec7b1e0c3598edff65 to your computer and use it in GitHub Desktop.
A custom extensible switch built in reanimated v2
import React, {useEffect} from "react";
import {StyleProp, StyleSheet, TouchableOpacity, TouchableOpacityProps, ViewProps, ViewStyle} from "react-native";
import Animated, {
interpolate,
useAnimatedStyle,
useSharedValue,
withTiming,
Easing,
Extrapolate,
interpolateColor, AnimateProps,
} from "react-native-reanimated";
const AnimatedTouchableOpacity = Animated.createAnimatedComponent<TouchableOpacityProps>(TouchableOpacity);
interface ICustomSwitchToggle extends Pick<
ICustomSwitch,
"toggleColorOff" |
"toggleColorOn" |
"trackColorOff" |
"trackColorOn"
> {
style?: AnimateProps<ViewProps>["style"];
animatedValue?: Animated.SharedValue<number>;
width?: number;
height?: number;
}
const CustomSwitchToggle = ({style}: ICustomSwitchToggle) => <Animated.View style={style} />;
// From here: https://nextcodingjs.com/create-custom-toggle-switch-button/, but added:
//
// 1. Made it a controlled component, so you can turn it off/on from some other place.
// 2. Used "onValueChange" which is more in line with react switch
// 3. Allow ability to render whatever you want on the inside of the switch, and tap into the animatedValue so you can do opacity animations.
// 4. Allow for non-circular toggle.
// 5. Fixed a few positioning bugs, so it automatically adapts to varying border widths
// 6. Allow for animating the track color
// 7. Added typescript types
//
// Hopefully this is useful, and if you like to build high quality stuff, we're hiring! aryk@tribefy.com.
interface ICustomSwitch {
value: boolean;
onValueChange: (value: boolean) => any;
containerStyle?: StyleProp<ViewStyle>;
toggleColorOff?: string;
toggleColorOn?: string;
trackColorOff?: string;
trackColorOn?: string;
height?: number;
width?: number;
padding?: number;
borderWidth?: number;
borderColor?: string;
duration?: number;
toggleHeight?: number;
toggleWidth?: number;
ToggleComponent?: React.ElementType<ICustomSwitchToggle>;
}
const CustomSwitch = ({
onValueChange,
value = false,
containerStyle,
toggleColorOff = "#c3c3c3",
toggleColorOn = "#008ECC",
trackColorOff = "#333333",
trackColorOn = "#CCCCCC",
height = 40,
width = height * 1.875,
padding = height * 0.09,
borderWidth = 1,
borderColor = toggleColorOff,
duration = 400,
toggleHeight = height - (padding + borderWidth) * 2,
toggleWidth = toggleHeight,
ToggleComponent = CustomSwitchToggle,
}: ICustomSwitch) => {
const calculateAnimatedValue = (v: boolean) => v ? 1 : 0;
const animatedValue = useSharedValue(calculateAnimatedValue(value));
const setAnimatedValue = (newValue: boolean) => {
animatedValue.value = withTiming(
calculateAnimatedValue(newValue),
{
duration,
easing: Easing.bezier(0.4, 0.0, 0.2, 1),
},
);
}
useEffect(
() => {
setAnimatedValue(value);
},
[value],
);
const onPress = () => {
const newValue = animatedValue.value === 0;
setAnimatedValue(newValue);
onValueChange(newValue);
};
const switchAnimatedStyle = useAnimatedStyle(
() => ({
transform: [
{
translateX: interpolate(
animatedValue.value,
[0, 1],
[padding, width - toggleWidth - padding - 2 * borderWidth],
Extrapolate.CLAMP,
),
},
],
backgroundColor: interpolateColor(
animatedValue.value,
[0, 1],
[toggleColorOff, toggleColorOn],
),
}),
[
padding,
width,
toggleWidth,
padding,
borderWidth,
toggleColorOff,
toggleColorOn,
]
);
const containerAnimatedStyle = useAnimatedStyle(
() => ({
backgroundColor: interpolateColor(
animatedValue.value,
[0, 1],
[trackColorOff, trackColorOn],
),
}),
[
trackColorOff,
trackColorOn,
]
);
return <AnimatedTouchableOpacity
onPress={onPress}
activeOpacity={1}
style={[
styles.containerStyle,
{height, width, borderWidth, borderColor},
containerAnimatedStyle,
containerStyle
]}
>
<ToggleComponent
{...{
toggleColorOff,
toggleColorOn,
trackColorOff,
trackColorOn,
animatedValue,
}}
width={toggleWidth}
height={toggleHeight}
style={[
styles.switchButton,
{left: 0, height: toggleHeight, width: toggleWidth},
switchAnimatedStyle,
]}
/>
</AnimatedTouchableOpacity>;
};
const styles = StyleSheet.create({
containerStyle: {
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white',
borderRadius: 500,
},
switchButton: {
backgroundColor: 'red',
position: 'absolute',
borderRadius: 100,
},
});
export {
ICustomSwitchToggle,
CustomSwitchToggle,
ICustomSwitch,
CustomSwitch,
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment