react-native使用PanResponder实现pinch手势

本文介绍如何在React Native中使用PanResponder实现pinch缩放手势。通过检测触摸点数量来识别双指操作,并计算两个触摸点间的距离变化来实现放大和缩小效果。

react-native使用PanResponder实现pinch手势

一、RN中的高级手势功能PanResponder

PanResponder类可以将多点触摸操作协调成一个手势。它使得一个单点触摸可以接受更多的触摸操作,也可以用于识别简单的多点触摸手势。

它提供了一个对触摸响应系统响应器的可预测的包装。对于每一个处理函数,它在原生事件之外提供了一个新的gestureState对象。

onPanResponderMove: (event, gestureState) => {}

其中event对象有:

  • nativeEvent
    • changedTouches - 在上一次事件之后,所有发生变化的触摸事件的数组集合(即上一次事件后,所有移动过的触摸点)
    • identifier - 触摸点的ID
    • locationX - 触摸点相对于父元素的横坐标
    • locationY - 触摸点相对于父元素的纵坐标
    • pageX - 触摸点相对于根元素的横坐标
    • pageY - 触摸点相对于根元素的纵坐标
    • target - 触摸点所在的元素ID
    • timestamp - 触摸事件的时间戳,可用于移动速度的计算
    • touches - 当前屏幕上的所有触摸点的集合

一个gestureState对象有如下的字段:

  • stateID - 触摸状态的ID。在屏幕上有至少一个触摸点的情况下,这个ID会一直有效。
  • moveX - 最近一次移动时的屏幕横坐标
  • moveY - 最近一次移动时的屏幕纵坐标
  • x0 - 当响应器产生时的屏幕坐标
  • y0 - 当响应器产生时的屏幕坐标
  • dx - 从触摸操作开始时的累计横向路程
  • dy - 从触摸操作开始时的累计纵向路程
  • vx - 当前的横向移动速度
  • vy - 当前的纵向移动速度
  • numberActiveTouches - 当前在屏幕上的有效触摸点的数量

基本用法:

componentWillMount: function() {
    this._panResponder = PanResponder.create({
      // 要求成为响应者:
      onStartShouldSetPanResponder: (evt, gestureState) => true,
      onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
      onMoveShouldSetPanResponder: (evt, gestureState) => true,
      onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,

      onPanResponderGrant: (evt, gestureState) => {
        // 开始手势操作。给用户一些视觉反馈,让他们知道发生了什么事情!

        // gestureState.{x,y}0 现在会被设置为0
      },
      onPanResponderMove: (evt, gestureState) => {
        // 最近一次的移动距离为gestureState.move{X,Y}

        // 从成为响应者开始时的累计手势移动距离为gestureState.d{x,y}
      },
      onPanResponderTerminationRequest: (evt, gestureState) => true,
      onPanResponderRelease: (evt, gestureState) => {
        // 用户放开了所有的触摸点,且此时视图已经成为了响应者。
        // 一般来说这意味着一个手势操作已经成功完成。
      },
      onPanResponderTerminate: (evt, gestureState) => {
        // 另一个组件已经成为了新的响应者,所以当前手势将被取消。
      },
      onShouldBlockNativeResponder: (evt, gestureState) => {
        // 返回一个布尔值,决定当前组件是否应该阻止原生组件成为JS响应者
        // 默认返回true。目前暂时只支持android。
        return true;
      },
    });
  },

  render: function() {
    return (
      <View {...this._panResponder.panHandlers} />
    );
  },

二、编写pinch处理

思路:通过numberActiveTouches获取手势的有效点,只处理2个手指的动作,通过touches点的集合,计算初始位置的直线距离和实时移动的直线距离,计算是放大还是缩放,使用距离作比例尺。

'use strict'

import React, { Component } from 'react'
import { View, StyleSheet, Text, PanResponder } from 'react-native'
import { Style, Const } from 'common'



export default class KLineView extends Component {
    constructor(props) {
        super(props);

        this.initDistend,

            this.state = {
                point1x: 0,
                point1y: 0,
                point2x: 0,
                point2y: 0,
                scaleNumber: 0,
            }
    }

    componentWillMount() {
        this._pinchResponder = PanResponder.create({
            onStartShouldSetPanResponder: this._handleStartShouldSetPanResponder,
            onStartShouldSetPanResponderCapture: this._handleStartShouldSetPanResponderCapture,
            onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder,
            onMoveShouldSetPanResponderCapture: this._handleMoveShouldSetPanResponderCapture,


            onPanResponderMove: this._handlePanResponderMove,
            onPanResponderGrant: this._handlePanResponderGrant,
            onPanResponderRelease: this._handlePanResponderEnd,
            onPanResponderTerminate: this._handlePanResponderEnd,
            onPanResponderStart: this._handlePanResponderStart,
        })
    }
    componentDidMount() {


    }

    _handleStartShouldSetPanResponder = (event, gestureState) => {
        if (gestureState.numberActiveTouches == 2)   //两点手势则处理
        {
            return true;
        }
        return false;
    }
    _handleStartShouldSetPanResponderCapture = (event, gestureState) => {
        if (gestureState.numberActiveTouches == 2)   //两点手势则处理
        {
            return true;
        }
        return false;
    }
    _handleMoveShouldSetPanResponder = (event, gestureState) => {
        if (gestureState.numberActiveTouches == 2)   //两点手势则处理
        {
            return true;
        }
        return false;
    }
    _handleMoveShouldSetPanResponderCapture = (event, gestureState) => {
        if (gestureState.numberActiveTouches == 2)   //两点手势则处理
        {
            return true;
        }
        return false;
    }



    _handlePanResponderGrant = () => {

    }
    _handlePanResponderEnd = () => {

    }
    _handlePanResponderStart = (event, gestureState) => {
        if (gestureState.numberActiveTouches == 2)   //两点手势
        {
            //这里计算初始的距离 √(x1-x2)^2+(y1-y2)^2
            let distend = Math.sqrt(
                Math.pow(event.nativeEvent.touches[0].pageX - event.nativeEvent.touches[1].pageX, 2) +
                Math.pow(event.nativeEvent.touches[0].pageY - event.nativeEvent.touches[1].pageY, 2)
            )
            this.initDistend = distend;

            this.setState({
                point1x: event.nativeEvent.touches[0].pageX,
                point1y: event.nativeEvent.touches[0].pageY,
                point2x: event.nativeEvent.touches[1].pageX,
                point2y: event.nativeEvent.touches[1].pageY,
            })
        }

    }
    _handlePanResponderMove = (event, gestureState) => {
        if (gestureState.numberActiveTouches == 2)   //两点手势
        {
            let distend = Math.sqrt(
                Math.pow(event.nativeEvent.touches[0].pageX - event.nativeEvent.touches[1].pageX, 2) +
                Math.pow(event.nativeEvent.touches[0].pageY - event.nativeEvent.touches[1].pageY, 2)
            )
            let scaleNumber = distend - this.initDistend
            this.setState({
                point1x: event.nativeEvent.touches[0].pageX,
                point1y: event.nativeEvent.touches[0].pageY,
                point2x: event.nativeEvent.touches[1].pageX,
                point2y: event.nativeEvent.touches[1].pageY,
                scaleNumber: scaleNumber,

            })
        }
    }

    render() {
        return (<View style={[Style.container, { backgroundColor: 'green', }]} {...this._pinchResponder.panHandlers}>
            <Text>两点手势坐标</Text>
            <View style={{flexDirection: 'row'}}>
                <Text>缩放数值</Text>
                <Text>{this.state.scaleNumber}</Text>
            </View>
            <View style={{ flex: 1, flexDirection: 'row' }}>
                <View style={{ flex: 1 }}>
                    <Text>point1</Text>
                    <Text>x</Text>
                    <Text>{this.state.point1x}</Text>
                    <Text>y</Text>
                    <Text>{this.state.point1y}</Text>
                </View>

                <View style={{ flex: 1 }}>
                    <Text>point2</Text>
                    <Text>x</Text>
                    <Text>{this.state.point2x}</Text>
                    <Text>y</Text>
                    <Text>{this.state.point2y}</Text>
                </View>

            </View>
        </View>)
    }
}
// 默认基础样式 
var baseStyle = StyleSheet.create({
    container: {
        flex: 1,
        borderTopWidth: 1,
        borderBottomWidth: 1,
        borderColor: '#cbd2d9',
        backgroundColor: '#FFF',
    },

})
import React, { useState, useRef, useEffect } from 'react'; import { View, Text, StyleSheet, PanResponder, TouchableOpacity, Animated, StatusBar, Dimensions, NativeSyntheticEvent, GestureResponderEvent } from 'react-native'; // 手势类型定义 type Gesture = 'tap' | 'doubleTap' | 'longPress' | 'swipe' | 'pinch'; // 滑动方向 type SwipeDir = '' | 'Up' | 'Down' | 'Left' | 'Right'; // 手势状态接口 interface GestureState { active: boolean; value?: any; } // 验证状态 type ValidationStatus = 'pending' | 'success' | 'failure'; // 手势组合顺序 const REQUIRED_GESTURES: Gesture[] = ['longPress', 'doubleTap', 'swipe', 'pinch']; // 长按优化配置(降低识别门槛) const GESTURE_CONFIG = { longPressThreshold: 900, // 长按时间延长至800ms(更容易触发) longPressMoveTolerance: 10, // 长按允许的最大移动距离(10px内视为静止) doubleTapTimeWindow: 600, // 双击时间窗口 doubleTapDistanceThreshold: 70, tapDelay: 400, // 单击延迟(降低灵敏度) swipeThreshold: 15, // 滑动触发阈值 swipeDirThreshold: 25, }; const CompleteGestureValidator: React.FC = () => { // 基础配置 const { width } = Dimensions.get('window'); const statusBarHeight = StatusBar.currentHeight || 0; // 手势状态管理 const [gestures, setGestures] = useState<Record<Gesture, GestureState>>({ tap: { active: false }, doubleTap: { active: false }, longPress: { active: false }, swipe: { active: false, value: { direction: '', distance: 0 } }, pinch: { active: false, value: { scale: 1 } } }); // 验证状态管理 const [currentStep, setCurrentStep] = useState(0); const [validationStatus, setValidationStatus] = useState<ValidationStatus>('pending'); const [message, setMessage] = useState("请按顺序完成手势: 长按 → 双击 → 滑动 → 捏合"); // 动画效果 const bgAnim = useRef(new Animated.Value(0)).current; const scaleAnim = useRef(new Animated.Value(1)).current; // 手势识别引用(新增长按相关状态) const panResponder = useRef<PanResponder.PanResponderInstance>(PanResponder.create({})); const pressTimer = useRef<NodeJS.Timeout | null>(null); const tapTimer = useRef<NodeJS.Timeout | null>(null); const lastTap = useRef(0); const tapCount = useRef(0); const lastTapPosition = useRef<{x: number, y: number} | null>(null); const pressStartPosition = useRef<{x: number, y: number} | null>(null); // 记录长按开始位置 const isSwiping = useRef(false); const isPinching = useRef(false); const isLongPressing = useRef(false); // 标记长按状态 const hasSwiped = useRef(false); const initialDist = useRef(0); const initialScale = useRef(1); // 安全获取触摸点 const getTouches = (evt: NativeSyntheticEvent<GestureResponderEvent>) => { const touches = evt.nativeEvent.touches; const result: Array<{pageX: number, pageY: number}> = []; if (!touches) return result; try { if (Array.isArray(touches)) { touches.forEach(t => result.push({pageX: t.pageX, pageY: t.pageY})); } else if (touches.length !== undefined) { for (let i = 0; i < touches.length; i++) { if (typeof touches.item === 'function') { const t = touches.item(i); result.push({pageX: t.pageX, pageY: t.pageY}); } else { // @ts-ignore const t = touches[i]; if (t && t.pageX !== undefined) { result.push({pageX: t.pageX, pageY: t.pageY}); } } } } } catch (e) { console.log('触摸点获取失败:', e); } return result; }; // 计算两点距离 const getDistance = (touches: Array<{pageX: number, pageY: number}>) => { if (touches.length < 2) return 0; const dx = touches[0].pageX - touches[1].pageX; const dy = touches[0].pageY - touches[1].pageY; return Math.sqrt(dx * dx + dy * dy); }; // 计算两点间直线距离 const getPointDistance = (p1: {x: number, y: number}, p2: {x: number, y: number}) => { const dx = p1.x - p2.x; const dy = p1.y - p2.y; return Math.sqrt(dx * dx + dy * dy); }; // 确定滑动方向 const getSwipeDir = (dx: number, dy: number): SwipeDir => { const threshold = GESTURE_CONFIG.swipeDirThreshold; const absDx = Math.abs(dx); const absDy = Math.abs(dy); if (Math.max(absDx, absDy) < threshold) return ''; return absDx > absDy ? dx > 0 ? 'Right' : 'Left' : dy > 0 ? 'Down' : 'Up'; }; // 获取手势名称 const getGestureName = (gesture: Gesture): string => { const names: Record<Gesture, string> = { tap: "单击", doubleTap: "双击", longPress: "长按", swipe: "滑动", pinch: "捏合" }; return names[gesture]; }; // 激活手势 const activateGesture = (type: Gesture, value?: any) => { if (type === 'longPress') { isLongPressing.current = true; // 标记长按激活 } // 初始化所有手势为未激活状态 const newGestures: Record<Gesture, GestureState> = { tap: { active: false }, doubleTap: { active: false }, longPress: { active: false }, swipe: { active: false, value: { direction: '', distance: 0 } }, pinch: { active: false, value: { scale: 1 } } }; newGestures[type] = { active: true, value }; setGestures(newGestures); // 视觉反馈 Animated.sequence([ Animated.timing(bgAnim, { toValue: 1, duration: 150, useNativeDriver: false }), Animated.timing(bgAnim, { toValue: 0, duration: 300, useNativeDriver: false }) ]).start(); Animated.sequence([ Animated.timing(scaleAnim, { toValue: 1.05, duration: 150, useNativeDriver: true }), Animated.timing(scaleAnim, { toValue: 1, duration: 150, useNativeDriver: true }) ]).start(); // 检查手势是否符合当前步骤要求 const expectedGesture = REQUIRED_GESTURES[currentStep]; if (type === expectedGesture) { const nextStep = currentStep + 1; setCurrentStep(nextStep); if (nextStep < REQUIRED_GESTURES.length) { setMessage(`成功!下一步: ${getGestureName(REQUIRED_GESTURES[nextStep])}`); } else { setValidationStatus('success'); setMessage("恭喜!所有手势验证通过!"); } } else { // 识别失败:自动重置 setValidationStatus('failure'); setMessage("手势识别失败"); setTimeout(resetAll, 1500); } // 自动重置当前手势状态 setTimeout(() => { setGestures(prev => ({ ...prev, [type]: type === 'swipe' ? { active: false, value: { direction: '', distance: 0 } } : type === 'pinch' ? { active: false, value: { scale: 1 } } : { active: false } })); if (type === 'longPress') { isLongPressing.current = false; } }, 1500); }; // 处理点击 const handleTap = (evt: GestureResponderEvent) => { // 长按状态下不处理单击/双击 if (isLongPressing.current || isSwiping.current || isPinching.current || hasSwiped.current || validationStatus !== 'pending') { resetTapState(); return; } const currentPosition = { x: evt.nativeEvent.pageX, y: evt.nativeEvent.pageY }; const now = Date.now(); const timeSinceLast = now - lastTap.current; if (tapTimer.current) clearTimeout(tapTimer.current); // 双击检测 if ( timeSinceLast < GESTURE_CONFIG.doubleTapTimeWindow && tapCount.current === 1 && lastTapPosition.current && getPointDistance(lastTapPosition.current, currentPosition) < GESTURE_CONFIG.doubleTapDistanceThreshold ) { activateGesture('doubleTap'); resetTapState(); return; } // 单击检测 tapCount.current = 1; lastTap.current = now; lastTapPosition.current = currentPosition; tapTimer.current = setTimeout(() => { activateGesture('tap'); resetTapState(); }, GESTURE_CONFIG.tapDelay); }; // 重置点击相关状态 const resetTapState = () => { tapCount.current = 0; lastTap.current = 0; lastTapPosition.current = null; if (tapTimer.current) { clearTimeout(tapTimer.current); tapTimer.current = null; } }; // 重置所有状态 const resetAll = () => { setGestures({ tap: { active: false }, doubleTap: { active: false }, longPress: { active: false }, swipe: { active: false, value: { direction: '', distance: 0 } }, pinch: { active: false, value: { scale: 1 } } }); setCurrentStep(0); setValidationStatus('pending'); setMessage("请按顺序完成手势: 长按 → 双击 → 滑动 → 捏合"); // 清除所有定时器 if (pressTimer.current) clearTimeout(pressTimer.current); resetTapState(); // 重置所有状态标记 hasSwiped.current = false; isSwiping.current = false; isPinching.current = false; isLongPressing.current = false; pressStartPosition.current = null; }; // 初始化手势识别(重点优化长按逻辑) useEffect(() => { panResponder.current = PanResponder.create({ onStartShouldSetPanResponder: () => validationStatus === 'pending', // 提高滑动触发门槛,轻微移动不视为滑动 onMoveShouldSetPanResponder: (_, gs) => { return validationStatus === 'pending' && (Math.abs(gs.dx) > GESTURE_CONFIG.longPressMoveTolerance || Math.abs(gs.dy) > GESTURE_CONFIG.longPressMoveTolerance); }, // 触摸开始:记录长按起始位置 onPanResponderStart: (evt) => { if (validationStatus !== 'pending') return; const touches = getTouches(evt); hasSwiped.current = false; isLongPressing.current = false; resetTapState(); // 记录长按起始位置(单指操作时) if (touches.length === 1) { pressStartPosition.current = { x: touches[0].pageX, y: touches[0].pageY }; } // 长按检测(延长至800ms) pressTimer.current = setTimeout(() => { // 长按触发条件:单指、未滑动、未捏合、移动在容忍范围内 if (!isSwiping.current && !isPinching.current && touches.length === 1 && pressStartPosition.current) { activateGesture('longPress'); } }, GESTURE_CONFIG.longPressThreshold); // 捏合初始化 if (touches.length === 2) { isPinching.current = true; initialDist.current = getDistance(touches); initialScale.current = gestures.pinch.value?.scale || 1; if (pressTimer.current) { clearTimeout(pressTimer.current); pressTimer.current = null; } } }, // 触摸移动:优化长按取消逻辑 onPanResponderMove: (evt, gs) => { if (validationStatus !== 'pending') return; const touches = getTouches(evt); // 处理捏合 if (touches.length === 2) { isPinching.current = true; isSwiping.current = false; isLongPressing.current = false; if (pressTimer.current) { clearTimeout(pressTimer.current); pressTimer.current = null; } const currentDist = getDistance(touches); if (initialDist.current > 0 && currentDist > 0) { const scale = (currentDist / initialDist.current) * initialScale.current; const clamped = Math.max(0.5, Math.min(2, scale)); activateGesture('pinch', { scale: clamped }); } } // 处理滑动(只有明显滑动才取消长按) else if (touches.length === 1) { const { dx, dy } = gs; const dist = Math.sqrt(dx * dx + dy * dy); // 检查是否超过长按允许的移动距离 if (pressStartPosition.current && dist > GESTURE_CONFIG.longPressMoveTolerance) { isSwiping.current = true; } // 只有超过滑动阈值才触发滑动并取消长按 if (dist > GESTURE_CONFIG.swipeThreshold) { hasSwiped.current = true; isLongPressing.current = false; if (pressTimer.current) { clearTimeout(pressTimer.current); pressTimer.current = null; } const dir = getSwipeDir(dx, dy); if (dir) { activateGesture('swipe', { direction: dir, distance: Math.round(dist) }); } } } }, // 触摸结束:清理状态 onPanResponderRelease: () => { if (pressTimer.current) { clearTimeout(pressTimer.current); pressTimer.current = null; } // 延迟重置状态,避免快速操作冲突 setTimeout(() => { isSwiping.current = false; isPinching.current = false; isLongPressing.current = false; }, 200); }, // 触摸中断:强制重置 onPanResponderTerminate: () => { if (validationStatus === 'pending') { resetAll(); } } }); }, [gestures.pinch.value?.scale, validationStatus]); // 动画颜色 const bgColor = bgAnim.interpolate({ inputRange: [0, 1], outputRange: ['#fff', validationStatus === 'failure' ? '#fff0f0' : '#e6f7ff'] }); const borderColor = bgAnim.interpolate({ inputRange: [0, 1], outputRange: [ validationStatus === 'failure' ? '#ff4d4f' : validationStatus === 'success' ? '#52c41a' : '#ccc', validationStatus === 'failure' ? '#ff4d4f' : validationStatus === 'success' ? '#52c41a' : '#1890ff' ] }); // 进度条颜色 const progressColor = validationStatus === 'failure' ? '#ff4d4f' : validationStatus === 'success' ? '#52c41a' : '#1890ff'; return ( <View style={[styles.container, { paddingTop: statusBarHeight }]}> <View style={styles.header}> <Text style={styles.title}>手势组合验证</Text> <Text style={styles.subtitle}>按指定顺序完成所有手势以通过验证</Text> </View> {/* 进度指示器 */} <View style={styles.progressContainer}> <View style={styles.progressBarBackground}> <View style={[ styles.progressBarFill, { width: `${(currentStep / REQUIRED_GESTURES.length) * 100}%`, backgroundColor: progressColor } ]} /> </View> <View style={styles.progressSteps}> {REQUIRED_GESTURES.map((gesture, index) => ( <View key={gesture} style={[ styles.progressStep, { backgroundColor: index < currentStep ? progressColor : index === currentStep && validationStatus === 'failure' ? '#ff4d4f' : '#ccc', borderColor: index < currentStep ? progressColor : '#ccc' } ]} > <Text style={styles.stepNumber}>{index + 1}</Text> </View> ))} </View> </View> {/* 提示信息 */} <View style={[ styles.messageBox, validationStatus === 'failure' ? styles.failureMessageBox : validationStatus === 'success' ? styles.successMessageBox : styles.defaultMessageBox ]}> <Text style={styles.messageText}>{message}</Text> </View> {/* 手势操作区 */} <Animated.View style={[ styles.area, { backgroundColor: bgColor, borderColor, transform: [{ scale: scaleAnim }], opacity: validationStatus !== 'pending' ? 0.7 : 1 } ]} onTouchEnd={handleTap} {...panResponder.current.panHandlers} > <Text style={styles.areaText}>操作区域</Text> <Text style={styles.areaHint}> {validationStatus === 'pending' ? `当前需要: ${getGestureName(REQUIRED_GESTURES[currentStep])}` : validationStatus === 'success' ? '验证成功!' : '验证失败!' } </Text> {/* 缩放演示元素 */} <Animated.View style={[ styles.scaleBox, { transform: [{ scale: gestures.pinch.active ? gestures.pinch.value?.scale : 1 }] } ]} > <Text style={styles.scaleText}>缩放我</Text> </Animated.View> </Animated.View> {/* 结果展示 */} <View style={styles.resultBox}> <Text style={styles.resultTitle}>当前识别: { Object.entries(gestures).find(([_, state]) => state.active) ? getGestureName((Object.entries(gestures).find(([_, state]) => state.active) as [Gesture, GestureState])[0]) : '无' }</Text> {validationStatus === 'success' && ( <TouchableOpacity style={styles.resetBtn} onPress={resetAll}> <Text style={styles.resetText}>重新开始</Text> </TouchableOpacity> )} </View> </View> ); }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f5f5f5', }, header: { padding: 16, backgroundColor: '#1890ff', alignItems: 'center', }, title: { color: '#fff', fontSize: 20, fontWeight: 'bold', marginBottom: 4, }, subtitle: { color: 'rgba(255,255,255,0.8)', fontSize: 14, }, progressContainer: { paddingHorizontal: 20, marginBottom: 10, }, progressBarBackground: { height: 6, backgroundColor: '#e8e8e8', borderRadius: 3, overflow: 'hidden', }, progressBarFill: { height: '100%', borderRadius: 3, transitionProperty: 'width', transitionDuration: '300ms', }, progressSteps: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 8, paddingHorizontal: 10, }, progressStep: { width: 24, height: 24, borderRadius: 12, borderWidth: 2, justifyContent: 'center', alignItems: 'center', }, stepNumber: { color: '#fff', fontSize: 12, fontWeight: 'bold', }, messageBox: { marginHorizontal: 20, padding: 12, borderRadius: 6, marginBottom: 15, }, defaultMessageBox: { backgroundColor: '#e6f7ff', }, successMessageBox: { backgroundColor: '#f6ffed', }, failureMessageBox: { backgroundColor: '#fff2f0', }, messageText: { fontSize: 15, textAlign: 'center', }, area: { flex: 1, margin: 20, borderRadius: 12, borderWidth: 2, justifyContent: 'center', alignItems: 'center', padding: 20, backgroundColor: '#fff', }, areaText: { fontSize: 20, color: '#333', fontWeight: '600', marginBottom: 10, }, areaHint: { fontSize: 15, color: '#666', marginBottom: 30, }, scaleBox: { width: 120, height: 120, backgroundColor: '#722ed1', borderRadius: 8, justifyContent: 'center', alignItems: 'center', }, scaleText: { color: '#fff', fontSize: 16, fontWeight: 'bold', }, resultBox: { margin: 20, padding: 16, backgroundColor: '#fff', borderRadius: 8, borderWidth: 1, borderColor: '#eee', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, resultTitle: { fontSize: 16, fontWeight: 'bold', color: '#333', }, resetBtn: { paddingVertical: 8, paddingHorizontal: 16, backgroundColor: '#1890ff', borderRadius: 6, alignItems: 'center', }, resetText: { color: '#fff', fontSize: 14, }, }); export default CompleteGestureValidator; 为什么识别不到长按,什么原因
最新发布
08-17
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值