u
This commit is contained in:
1 parent
ab3eaed7ce
commit
cf23acdabc
28 files changed
+1292
-486
No files matched your search
@@ -0,0 +1,127 @@
|
||||
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 },
|
||||
},
|
||||
})
|
||||
Reference in new issue
Block a user