React Native 实现更丰富的触摸反馈

492 阅读1分钟

背景

React Native 自带的触摸组件TouchableOpacity TouchableHighlight 能够实现的触摸反馈只有高亮和透明度变化

当遇到需要更多丰富的触摸反馈时显得捉襟见肘。

这里来尝试封装一个能支持更丰富的反馈动效的touch 组件

需要准备的

直接上代码

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 点击之后有收缩效果(GIF 略掉帧)