All files / app/(tabs)/scanner/components/camera/hooks useZoomGesture.ts

95.65% Statements 44/46
77.77% Branches 14/18
100% Functions 8/8
100% Lines 44/44

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115                                    7x 7x 7x 7x     7x     7x 9x 9x       7x 9x 9x 9x 9x       7x       2x     2x     5x 5x 5x 5x 5x       5x   4x 4x     4x 4x   4x     4x 2x 2x 2x               2x 1x 1x 1x     4x 3x 3x 3x               3x 3x           1x 1x         7x    
/**
 * 相机缩放手势控制 Hook
 */
 
import { useCallback, useRef } from 'react';
import { PanResponder } from 'react-native';
import * as Haptics from 'expo-haptics';
 
interface UseZoomGestureProps {
  zoom: number;
  setZoom: (zoom: number) => void;
}
 
/**
 * 相机缩放手势 Hook
 * 支持双指捏合和双指垂直滑动两种方式
 */
export function useZoomGesture({ zoom, setZoom }: UseZoomGestureProps) {
  const lastDistance = useRef(0);
  const lastY = useRef(0);
  const isZooming = useRef(false);
  const zoomRef = useRef(zoom);
 
  // 保持 zoomRef 与 zoom 同步
  zoomRef.current = zoom;
 
  // 计算两指的平均Y坐标(用于垂直滑动)
  const getAverageY = useCallback((touches: any[]) => {
    Iif (touches.length < 2) return 0;
    return (touches[0].pageY + touches[1].pageY) / 2;
  }, []);
 
  // 计算两点之间的距离(用于捏合手势)
  const getDistance = useCallback((touches: any[]) => {
    Iif (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 panResponder = useRef(
    PanResponder.create({
      onStartShouldSetPanResponder: (evt) => {
        // 只在双指触摸时响应
        return evt.nativeEvent.touches.length === 2;
      },
      onMoveShouldSetPanResponder: (evt) => {
        return evt.nativeEvent.touches.length === 2;
      },
      onPanResponderGrant: (evt) => {
        Eif (evt.nativeEvent.touches.length === 2) {
          isZooming.current = true;
          lastDistance.current = getDistance(evt.nativeEvent.touches);
          lastY.current = getAverageY(evt.nativeEvent.touches);
          console.log('🎯 双指手势开始,初始距离:', lastDistance.current, '初始Y:', lastY.current);
        }
      },
      onPanResponderMove: (evt) => {
        if (evt.nativeEvent.touches.length === 2 && isZooming.current) {
          // 方式1:捏合缩放
          const currentDistance = getDistance(evt.nativeEvent.touches);
          const distanceDiff = currentDistance - lastDistance.current;
 
          // 方式2:双指垂直滑动缩放
          const currentY = getAverageY(evt.nativeEvent.touches);
          const yDiff = lastY.current - currentY;
 
          let zoomChange = 0;
 
          // 捏合手势优先(降低阈值,提高灵敏度)
          if (Math.abs(distanceDiff) > 3) {
            zoomChange = distanceDiff / 400;
            lastDistance.current = currentDistance;
            console.log(
              '📏 捏合缩放:',
              distanceDiff.toFixed(1),
              '=> zoomChange:',
              zoomChange.toFixed(3)
            );
          }
          // 如果没有明显捏合,使用垂直滑动(降低阈值)
          else if (Math.abs(yDiff) > 3) {
            zoomChange = yDiff / 250;
            lastY.current = currentY;
            console.log('↕️ 垂直滑动:', yDiff.toFixed(1), '=> zoomChange:', zoomChange.toFixed(3));
          }
 
          if (Math.abs(zoomChange) > 0.005) {
            const newZoom = Math.max(0, Math.min(1, zoomRef.current + zoomChange));
            setZoom(newZoom);
            console.log(
              '🔍 缩放更新:',
              (zoomRef.current * 100).toFixed(0) + '%',
              '->',
              (newZoom * 100).toFixed(0) + '%'
            );
 
            // 触觉反馈(降低阈值)
            Eif (Math.abs(zoomChange) > 0.03) {
              Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
            }
          }
        }
      },
      onPanResponderRelease: () => {
        console.log('✋ 双指手势结束');
        isZooming.current = false;
      },
    })
  ).current;
 
  return { panResponder };
}