背景
React Native 自带的触摸组件TouchableOpacity TouchableHighlight 能够实现的触摸反馈只有高亮和透明度变化
当遇到需要更多丰富的触摸反馈时显得捉襟见肘。
这里来尝试封装一个能支持更丰富的反馈动效的touch 组件
需要准备的
- react-native ≥ 0.63
- 这里需要用到
Pressable组件
- 这里需要用到
- react-native-reanimated @1.x.x
- react-native-redash @9.6.0
直接上代码
import React from 'react'
import {
GestureResponderEvent,
PressableProps,
Pressable,
StyleProp,
ViewStyle,
StyleSheet
} from 'react-native'
import Animated, {
block,
Clock,
clockRunning,
cond,
defined,
set,
stopClock,
and,
useValue
} from 'react-native-reanimated'
import { timing } from 'react-native-redash'
interface TouchWithFeedbackProps extends PressableProps {
getAnimationStyle?: (
animationValue: Animated.Value<number>
) => StyleProp<Animated.AnimateStyle<ViewStyle>>
}
type Binary = 0 | 1
export const TouchWithFeedback = (props: TouchWithFeedbackProps) => {
const { style, ...rest } = props
const activeValue = 1
const inactiveValue = 0
const TRUE = 1
const FALSE = 0
const animationDuration = useValue<number>(150) //ms
const animationValue = useValue<number>(inactiveValue)
const animationState = useValue<Binary>(FALSE)
const activeClock = new Clock()
const inactiveClock = new Clock()
function activeAnimation() {
animationState.setValue(TRUE)
}
function inactiveAnimation() {
animationState.setValue(FALSE)
}
function handlePressOut(event: GestureResponderEvent) {
inactiveAnimation()
props.onPressOut && props.onPressOut(event)
}
function handlePressIn(event: GestureResponderEvent) {
activeAnimation()
props.onPressIn && props.onPressIn(event)
}
function handlePress(e: GestureResponderEvent) {
props.onPress && props.onPress(e)
}
function _stopClock(clock: Animated.Clock) {
return cond(and(defined(clock), clockRunning(clock)), stopClock(clock))
}
return (
<>
<Pressable
{...rest}
onPress={handlePress}
onPressIn={handlePressIn}
onPressOut={handlePressOut}
>
<Animated.View
style={StyleSheet.flatten([
style as any,
props.getAnimationStyle
? props.getAnimationStyle(animationValue)
: undefined
])}
>
{props.children}
</Animated.View>
</Pressable>
<Animated.Code
exec={block([
cond(
[animationState],
[
_stopClock(inactiveClock),
set(
animationValue,
timing({
clock: activeClock,
duration: animationDuration,
from: animationValue,
to: activeValue
})
) // active
],
[
_stopClock(activeClock),
set(
animationValue,
timing({
clock: inactiveClock,
duration: animationDuration,
from: animationValue,
to: inactiveValue
})
) // inactive
]
)
])}
/>
</>
)
}
使用
import React from 'react'
import { StyleProp, Text, View, ViewStyle } from 'react-native'
import Animated from 'react-native-reanimated'
import { TouchWithFeedback } from '.'
export default class TouchWithFeedbackDemo extends React.Component {
handleGetAnimationStyle = (
animationValue: Animated.Value<number>
): StyleProp<Animated.AnimateStyle<ViewStyle>> => {
return {
transform: [
{
scale: animationValue.interpolate({
inputRange: [0, 1],
outputRange: [1, 0.9],
}), // 收缩效果
},
],
opacity: animationValue.interpolate({
inputRange: [0, 1],
outputRange: [1, 0.8],
}), // 透明度从1 - 0.8
}
}
handlePress = () => {
console.log('yes ~')
}
render() {
return (
<TouchWithFeedback
getAnimationStyle={this.handleGetAnimationStyle}
onPress={this.handlePress}
>
<View
style={{
padding: 10,
backgroundColor: 'red',
}}
>
<Text>点击我</Text>
</View>
</TouchWithFeedback>
)
}
}
效果图
点击之后有收缩效果(GIF 略掉帧)