I rewrote this library to make it more flexible. If one wanted to add X number of switches, that would be pretty easy to do.
import React, {useEffect, useRef, useState} from "react";
import {
Animated,
Dimensions,
PanResponder,
View,
StyleProp,
ViewStyle,
EasingFunction,
} from "react-native";
interface ISwitchComponent {
currentIndex?: number;
animatedValue?: Animated.AnimatedInterpolation;
}
// Taken from here: https://github.com/victorkvarghese/rn-slider-switch
// And rewritten to follow functional components but also to allow to overwrite all of the hardcoded things.
interface IMultiSwitch {
currentIndex: number;
animate?: boolean;
duration?: number;
easing?: EasingFunction;
width?: number;
height?: number;
switcherWidth?: number,
borderWidth?: number,
disableScroll?: (bool: boolean) => any;
onIndexChanged?: (index: number) => any;
disableSwitch?: boolean;
containerStyle?: StyleProp<ViewStyle>;
tabStyle?: StyleProp<ViewStyle>;
useNativeDriver?: boolean;
children?: any[];
SwitchComponent: React.ComponentType<ISwitchComponent>;
MultiSwitchHeader?: JSX.Element;
MultiSwitchFooter?: JSX.Element;
}
const MultiSwitch = ({
children,
currentIndex,
animate = true,
duration = 100,
easing,
width = Dimensions.get("window").width - 30,
height = 55,
borderWidth = 1,
switcherWidth = width / children.length,
disableScroll,
onIndexChanged,
disableSwitch,
containerStyle,
tabStyle,
useNativeDriver = true,
SwitchComponent,
MultiSwitchHeader,
MultiSwitchFooter,
}: IMultiSwitch) => {
const [posValue, setPosValue] = useState(0);
const isParentScrollDisabledRef = useRef(false);
const position = useRef(new Animated.Value(0)).current;
const currentAnimationRef = useRef<Animated.CompositeAnimation>();
const animateToIndex = (index: number) => {
if (disableSwitch) return;
currentAnimationRef.current?.stop(); // stop previous animation if was running
const toValue = index * switcherWidth;
if (animate) {
currentAnimationRef.current = Animated.timing(position, {toValue, duration, easing, useNativeDriver});
currentAnimationRef.current.start(() => setPosValue(toValue));
} else {
position.setValue(toValue);
setPosValue(toValue)
}
onIndexChanged?.(index);
};
const animatedValue = position.interpolate({
inputRange: [0, width - switcherWidth],
outputRange: [0, 1],
});
useEffect(() => { animateToIndex(currentIndex); }, [currentIndex]);
const panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onStartShouldSetPanResponderCapture: () => true,
onMoveShouldSetPanResponder: () => true,
onMoveShouldSetPanResponderCapture: () => true,
onPanResponderGrant: () => {
// disable parent scroll if slider is inside a scrollview
if (!isParentScrollDisabledRef.current) {
disableScroll?.(true);
isParentScrollDisabledRef.current = true;
}
},
onPanResponderMove: (evt, gestureState) => {
if (!disableSwitch) {
let finalValue = gestureState.dx + posValue;
if (finalValue >= 0 && finalValue <= (width - switcherWidth))
position.setValue(posValue + gestureState.dx);
}
},
onPanResponderTerminationRequest: () => true,
onPanResponderRelease: (evt, gestureState) => {
if (!disableSwitch) {
let finalValue = gestureState.dx + posValue;
isParentScrollDisabledRef.current = false;
disableScroll?.(false);
animateToIndex(Math.round(finalValue / switcherWidth));
}
},
onPanResponderTerminate: () => {},
// Returns whether this component should block native components from becoming the JS
// responder. Returns true by default. Is currently only supported on android.
onShouldBlockNativeResponder: () => true,
});
const selectorHeight = height - 2 * borderWidth;
return <View style={[
{
width,
height,
flexDirection: "row",
backgroundColor: "#efefef",
alignItems: "center",
justifyContent: "center",
borderWidth,
borderColor: "#efefef",
overflow: "hidden",
borderRadius: height / 2,
},
containerStyle,
]}>
{MultiSwitchHeader}
{children}
<Animated.View
{...panResponder.panHandlers}
style={[
{
flexDirection: "row",
position: "absolute",
top: 0,
left: 0,
backgroundColor: "#FFF",
borderRadius: selectorHeight / 2,
height: selectorHeight,
alignItems: "center",
justifyContent: "center",
width: switcherWidth,
elevation: 4,
shadowOpacity: 0.31,
shadowRadius: 10,
shadowColor: "#A69E9E",
transform: [{ translateX: position }]
},
tabStyle,
]}
>
<SwitchComponent {...{animatedValue, currentIndex}} />
</Animated.View>
{MultiSwitchFooter}
</View>
};
export {
ISwitchComponent,
IMultiSwitch,
MultiSwitch,
}