128 lines
2.6 KiB
TypeScript
128 lines
2.6 KiB
TypeScript
import { useEffect } from 'react'
|
|
import { Pressable, StyleSheet } from 'react-native'
|
|
import Animated, {
|
|
interpolateColor,
|
|
useAnimatedStyle,
|
|
useSharedValue,
|
|
withTiming,
|
|
} from 'react-native-reanimated'
|
|
|
|
interface SwitchProps {
|
|
value: any
|
|
onChange: (val: any) => void
|
|
trueValue?: any
|
|
falseValue?: any
|
|
width?: number
|
|
height?: number
|
|
activeColor?: string
|
|
inactiveColor?: string
|
|
duration?: number
|
|
disabled?: boolean // 新增 disabled
|
|
}
|
|
|
|
export default function Switch({
|
|
value,
|
|
onChange,
|
|
trueValue,
|
|
falseValue,
|
|
width = 52,
|
|
height = 24,
|
|
activeColor = '#333',
|
|
inactiveColor = '#e5e7eb',
|
|
duration = 200,
|
|
disabled = false, // 默认可用
|
|
}: SwitchProps) {
|
|
// 默认值 fallback
|
|
const trueVal = trueValue ?? true
|
|
const falseVal = falseValue ?? false
|
|
|
|
// 当前状态是否为激活
|
|
const isActive = Object.is(value, trueVal)
|
|
|
|
// 控制动画的 sharedValue
|
|
const progress = useSharedValue(isActive ? 1 : 0)
|
|
|
|
useEffect(() => {
|
|
progress.value = withTiming(isActive ? 1 : 0, { duration })
|
|
}, [isActive, duration])
|
|
|
|
const knobSize = height - 4
|
|
const translateX = width - knobSize - 4
|
|
|
|
// 动画轨道样式
|
|
const trackStyle = useAnimatedStyle(() => {
|
|
const baseColor = interpolateColor(
|
|
progress.value,
|
|
[0, 1],
|
|
[inactiveColor, activeColor],
|
|
)
|
|
return {
|
|
backgroundColor: baseColor,
|
|
opacity: disabled ? 0.7 : 1, // disabled 半透明
|
|
}
|
|
})
|
|
|
|
// 动画滑块样式
|
|
const knobStyle = useAnimatedStyle(() => {
|
|
return {
|
|
transform: [
|
|
{
|
|
translateX: progress.value * translateX,
|
|
},
|
|
],
|
|
opacity: disabled ? 0.8 : 1, // knob 半透明
|
|
}
|
|
})
|
|
|
|
return (
|
|
<Pressable
|
|
onPress={() => {
|
|
if (disabled)
|
|
return
|
|
const nextValue = isActive ? falseVal : trueVal
|
|
onChange(nextValue)
|
|
}}
|
|
style={{ width, height }}
|
|
>
|
|
<Animated.View
|
|
style={[
|
|
styles.track,
|
|
{
|
|
width,
|
|
height,
|
|
borderRadius: height / 2,
|
|
},
|
|
trackStyle,
|
|
]}
|
|
>
|
|
<Animated.View
|
|
style={[
|
|
styles.knob,
|
|
{
|
|
width: knobSize,
|
|
height: knobSize,
|
|
borderRadius: knobSize / 2,
|
|
},
|
|
knobStyle,
|
|
]}
|
|
/>
|
|
</Animated.View>
|
|
</Pressable>
|
|
)
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
track: {
|
|
justifyContent: 'center',
|
|
padding: 2,
|
|
},
|
|
knob: {
|
|
backgroundColor: '#fff',
|
|
elevation: 2,
|
|
shadowColor: '#000',
|
|
shadowOpacity: 0.2,
|
|
shadowRadius: 2,
|
|
shadowOffset: { width: 0, height: 1 },
|
|
},
|
|
})
|