|
1 | | -import React from 'react'; |
| 1 | +import { number } from 'prop-types'; |
| 2 | +import React, { useState, useEffect, useMemo } from 'react'; |
2 | 3 | import { |
3 | 4 | TouchableOpacity, |
4 | 5 | Animated, |
@@ -33,173 +34,175 @@ export interface SwitchState { |
33 | 34 | animatedStart: (checked: boolean) => void; |
34 | 35 | } |
35 | 36 |
|
36 | | -export default class Switch extends React.Component<SwitchProps, SwitchState> { |
37 | | - translateXValue: number = 2; |
38 | | - height: number = 16; |
39 | | - static defaultProps: SwitchProps = { |
40 | | - checked: false, |
41 | | - size: 'default', |
42 | | - thumbColor: '#fff', |
43 | | - color: '#4DD964', |
44 | | - onValueChange: () => {}, |
45 | | - }; |
46 | | - private animatedStart: (checked: boolean) => void; |
47 | | - constructor(props: SwitchProps) { |
48 | | - super(props); |
49 | | - this.animatedStart = (checked: boolean) => { |
50 | | - const obj = { |
51 | | - height: this.height, |
52 | | - number: 1, |
53 | | - translateXValue: this.translateXValue, |
54 | | - }; |
55 | | - if (!checked) { |
56 | | - obj.height = 2; |
57 | | - obj.number = 0; |
58 | | - obj.translateXValue = 2; |
59 | | - } |
60 | | - Animated.parallel([ |
61 | | - Animated.sequence([ |
62 | | - Animated.spring(this.state.borderValue, { |
63 | | - toValue: obj.height, |
64 | | - overshootClamping: true, |
65 | | - useNativeDriver: false, |
66 | | - }), |
67 | | - Animated.spring(this.state.bgOpacity, { |
68 | | - toValue: obj.number, |
69 | | - overshootClamping: true, |
70 | | - useNativeDriver: false, |
71 | | - }), |
72 | | - ]), |
73 | | - Animated.spring(this.state.translateXValue, { |
74 | | - toValue: obj.translateXValue, |
| 37 | +function Switch(props: SwitchProps) { |
| 38 | + const { |
| 39 | + style, |
| 40 | + size, |
| 41 | + // eslint-disable-next-line @typescript-eslint/no-unused-vars |
| 42 | + checked = false, |
| 43 | + color, |
| 44 | + disabled, |
| 45 | + thumbColor, |
| 46 | + trackStyle, |
| 47 | + thumbStyle, |
| 48 | + ...otherProps |
| 49 | + } = props; |
| 50 | + const [height, setHeight] = useState(16); |
| 51 | + const [translateXValue, setTranslateXValue] = useState(2); |
| 52 | + |
| 53 | + const animatedStart = (checked: boolean) => { |
| 54 | + const obj = { |
| 55 | + height: height, |
| 56 | + number: 1, |
| 57 | + translateXValue: translateXValue, |
| 58 | + }; |
| 59 | + |
| 60 | + if (!checked) { |
| 61 | + obj.height = 2; |
| 62 | + obj.number = 0; |
| 63 | + obj.translateXValue = 2; |
| 64 | + } |
| 65 | + |
| 66 | + Animated.parallel([ |
| 67 | + Animated.sequence([ |
| 68 | + Animated.spring(state.borderValue, { |
| 69 | + toValue: obj.height, |
75 | 70 | overshootClamping: true, |
76 | 71 | useNativeDriver: false, |
77 | 72 | }), |
78 | | - ]).start(); |
79 | | - }; |
80 | | - this.state = { |
81 | | - checked: !!this.props.checked, |
82 | | - containerSize: { width: 0, height: 0 }, |
83 | | - borderValue: new Animated.Value(0), |
84 | | - translateXValue: new Animated.Value(2), |
85 | | - bgOpacity: new Animated.Value(props.value ? 1 : 0), |
86 | | - control: 'state', |
87 | | - animatedStart: this.animatedStart, |
88 | | - }; |
89 | | - this.animatedStart(!!this.props.checked); |
90 | | - } |
91 | | - static getDerivedStateFromProps(props: SwitchProps, state: SwitchState) { |
92 | | - if (state.control === 'state') { |
93 | | - return { |
94 | | - control: 'props', |
95 | | - }; |
96 | | - } |
97 | | - if (props.checked !== state.checked) { |
98 | | - state.animatedStart(!!props.checked); |
99 | | - return { |
100 | | - checked: !!props.checked, |
101 | | - control: 'props', |
102 | | - }; |
103 | | - } |
104 | | - return null; |
105 | | - } |
106 | | - onPress = () => { |
107 | | - const checked = !this.state.checked; |
108 | | - this.setState({ checked, control: 'state' }, () => { |
109 | | - this.animatedStart(checked); |
110 | | - this.props.onValueChange!(checked); |
111 | | - }); |
| 73 | + Animated.spring(state.bgOpacity, { |
| 74 | + toValue: obj.number, |
| 75 | + overshootClamping: true, |
| 76 | + useNativeDriver: false, |
| 77 | + }), |
| 78 | + ]), |
| 79 | + Animated.spring(state.translateXValue, { |
| 80 | + toValue: obj.translateXValue, |
| 81 | + overshootClamping: true, |
| 82 | + useNativeDriver: false, |
| 83 | + }), |
| 84 | + ]).start(); |
112 | 85 | }; |
113 | | - measureContainer = (event: LayoutChangeEvent) => { |
114 | | - const { checked } = this.state; |
115 | | - const { translateXValue } = this.state; |
| 86 | + |
| 87 | + const [state, setState] = useState<SwitchState>({ |
| 88 | + checked: checked, |
| 89 | + containerSize: { width: 0, height: 0 }, |
| 90 | + borderValue: new Animated.Value(0), |
| 91 | + translateXValue: new Animated.Value(2), |
| 92 | + bgOpacity: new Animated.Value(props.value ? 1 : 0), |
| 93 | + control: 'state', |
| 94 | + animatedStart: animatedStart, |
| 95 | + }); |
| 96 | + |
| 97 | + const { containerSize } = state; |
| 98 | + |
| 99 | + const onPress = () => { |
| 100 | + const checked = !state.checked; |
| 101 | + setState({ ...state, checked, control: 'state' }); |
| 102 | + |
| 103 | + animatedStart(checked); |
| 104 | + props.onValueChange!(checked); |
| 105 | + }; |
| 106 | + |
| 107 | + const measureContainer = (event: LayoutChangeEvent) => { |
| 108 | + animatedStart(!!props.checked); |
| 109 | + let { checked, translateXValue } = state; |
| 110 | + const {} = state; |
116 | 111 | const { height: layoutHeight, width: layoutWidth } = event.nativeEvent.layout; |
117 | | - const height = layoutHeight - 4; |
| 112 | + let height = layoutHeight - 4; |
118 | 113 | const width = height; |
119 | 114 | const size = { width, height }; |
120 | | - this.height = height / 2; |
121 | | - const state = { containerSize: size }; |
122 | | - this.translateXValue = layoutWidth - 2 - width; |
| 115 | + setHeight(height / 2); |
| 116 | + setTranslateXValue(layoutWidth - 2 - width); |
123 | 117 | translateXValue.setValue(checked ? layoutWidth - 2 - width : 2); |
124 | | - this.setState({ ...state, control: 'state' }, () => { |
125 | | - this.animatedStart(!!this.props.checked); |
126 | | - }); |
| 118 | + setState({ ...state, containerSize: size, control: 'state' }); |
| 119 | + animatedStart(!!props.checked); |
127 | 120 | }; |
128 | | - render() { |
129 | | - const { |
130 | | - style, |
131 | | - size, |
132 | | - // eslint-disable-next-line @typescript-eslint/no-unused-vars |
133 | | - checked, |
134 | | - color, |
135 | | - disabled, |
136 | | - thumbColor, |
137 | | - trackStyle, |
138 | | - thumbStyle, |
139 | | - ...otherProps |
140 | | - } = this.props; |
141 | | - const { containerSize } = this.state; |
142 | | - const bgBorder = this.state.borderValue.interpolate({ |
143 | | - inputRange: [2, this.height], |
144 | | - outputRange: [2, this.height], |
145 | | - // extrapolate: 'clamp', |
146 | | - }); |
147 | | - const sizeStyl: ViewStyle = {}; |
148 | | - switch (size) { |
149 | | - case 'small': |
150 | | - sizeStyl.height = 20; |
151 | | - sizeStyl.width = 30; |
152 | | - break; |
153 | | - case 'large': |
154 | | - sizeStyl.height = 30; |
155 | | - sizeStyl.width = 48; |
156 | | - break; |
157 | | - default: |
158 | | - sizeStyl.height = 26; |
159 | | - sizeStyl.width = 38; |
160 | | - break; |
161 | | - } |
162 | | - return ( |
163 | | - <View {...otherProps} onLayout={this.measureContainer} style={[styles.warpper, sizeStyl, style]}> |
164 | | - {disabled && <View style={[styles.position, styles.disabled]} />} |
165 | | - <Animated.View style={[styles.bg, styles.position, trackStyle, { borderWidth: bgBorder }]} /> |
166 | | - <TouchableOpacity |
167 | | - // eslint-disable-next-line |
168 | | - style={[styles.position, { zIndex: 1 }]} |
169 | | - onPress={this.onPress} |
170 | | - /> |
171 | | - <Animated.View |
172 | | - style={[ |
173 | | - styles.position, |
174 | | - // eslint-disable-next-line |
175 | | - { |
176 | | - backgroundColor: this.state.checked ? color : '', |
177 | | - borderRadius: 16, |
178 | | - opacity: this.state.bgOpacity, |
179 | | - }, |
180 | | - ]} |
181 | | - /> |
182 | | - <Animated.View |
183 | | - style={[ |
184 | | - styles.grip, |
185 | | - |
186 | | - disabled ? styles.shadowDisable : styles.shadow, |
187 | | - { |
188 | | - backgroundColor: thumbColor, |
189 | | - width: containerSize.width, |
190 | | - height: containerSize.height, |
191 | | - }, |
192 | | - thumbStyle, |
193 | | - { |
194 | | - transform: [{ translateX: this.state.translateXValue }], |
195 | | - }, |
196 | | - ]} |
197 | | - /> |
198 | | - </View> |
199 | | - ); |
| 121 | + |
| 122 | + // if (state.control === 'state') { |
| 123 | + // setState({ ...state, control: 'props' }); |
| 124 | + // } |
| 125 | + // if (props.checked !== state.checked) { |
| 126 | + // state.animatedStart(!!props.checked); |
| 127 | + // setState({ |
| 128 | + // ...state, |
| 129 | + // checked: !!props.checked, |
| 130 | + // control: 'props', |
| 131 | + // }); |
| 132 | + // } |
| 133 | + |
| 134 | + const bgBorder = state.borderValue.interpolate({ |
| 135 | + inputRange: [2, height], |
| 136 | + outputRange: [2, height], |
| 137 | + // extrapolate: 'clamp', |
| 138 | + }); |
| 139 | + |
| 140 | + const sizeStyl: ViewStyle = {}; |
| 141 | + switch (size) { |
| 142 | + case 'small': |
| 143 | + sizeStyl.height = 20; |
| 144 | + sizeStyl.width = 30; |
| 145 | + break; |
| 146 | + case 'large': |
| 147 | + sizeStyl.height = 30; |
| 148 | + sizeStyl.width = 48; |
| 149 | + break; |
| 150 | + default: |
| 151 | + sizeStyl.height = 26; |
| 152 | + sizeStyl.width = 38; |
| 153 | + break; |
200 | 154 | } |
| 155 | + |
| 156 | + return ( |
| 157 | + <View {...otherProps} onLayout={measureContainer} style={[styles.warpper, sizeStyl, style]}> |
| 158 | + {disabled && <View style={[styles.position, styles.disabled]} />} |
| 159 | + <Animated.View style={[styles.bg, styles.position, trackStyle, { borderWidth: bgBorder }]} /> |
| 160 | + <TouchableOpacity |
| 161 | + // eslint-disable-next-line |
| 162 | + style={[styles.position, { zIndex: 1 }]} |
| 163 | + onPress={onPress} |
| 164 | + /> |
| 165 | + <Animated.View |
| 166 | + style={[ |
| 167 | + styles.position, |
| 168 | + // eslint-disable-next-line |
| 169 | + { |
| 170 | + backgroundColor: state.checked ? color : '', |
| 171 | + borderRadius: 16, |
| 172 | + opacity: state.bgOpacity, |
| 173 | + }, |
| 174 | + ]} |
| 175 | + /> |
| 176 | + <Animated.View |
| 177 | + style={[ |
| 178 | + styles.grip, |
| 179 | + |
| 180 | + disabled ? styles.shadowDisable : styles.shadow, |
| 181 | + { |
| 182 | + backgroundColor: thumbColor, |
| 183 | + width: containerSize.width, |
| 184 | + height: containerSize.height, |
| 185 | + }, |
| 186 | + thumbStyle, |
| 187 | + { |
| 188 | + transform: [{ translateX: state.translateXValue }], |
| 189 | + }, |
| 190 | + ]} |
| 191 | + /> |
| 192 | + </View> |
| 193 | + ); |
201 | 194 | } |
202 | 195 |
|
| 196 | +Switch.defaultProps = { |
| 197 | + checked: false, |
| 198 | + size: 'default', |
| 199 | + thumbColor: '#fff', |
| 200 | + color: '#4DD964', |
| 201 | + onValueChange: () => {}, |
| 202 | +}; |
| 203 | + |
| 204 | +export default Switch; |
| 205 | + |
203 | 206 | const styles = StyleSheet.create({ |
204 | 207 | warpper: { |
205 | 208 | position: 'relative', |
|
0 commit comments