如何使用React Reanimated和React Native Gesture Handler更新比例焦点原点以进行迭代式缩放手势?

3

我使用react-native-gesture-handler和react-native-reanimated构建了一个捏合缩放效果。用户可以在图像的任意位置捏合并以手指之间的位置为缩放原点进行缩放。这很棒。但是,我遇到了一个问题,就是允许用户在多个捏合手势中缩放。这需要记住用户先前捏合手势的偏移量和缩放比例。目前我的代码存在一个问题,当用户第二次捏合时,手势处理程序会记住第一次捏合手势的缩放比例值,但无法正确更新缩放原点。如何解决这个问题而不增加变换语句的数量?

  const prevZoomScale = useSharedValue(1)
  const currZoomScale = useSharedValue(1)
  const zoomScale = useDerivedValue(() => { return prevZoomScale.value * currZoomScale.value }, [prevZoomScale.value, currZoomScale.value])
  const tempZoomScale = useSharedValue(1)
  const prevOriginOffset = useSharedValue({x: 0, y: 0})
  const tempOriginOffset = useSharedValue({x: 0, y: 0})
  const currOriginOffset = useSharedValue({x: 0, y: 0})
  const pinchOriginOffset = useDerivedValue(() => 
     { 
        return {
                 x: (prevOriginOffset.value.x + currOriginOffset.value.x), 
                 y: (prevOriginOffset.value.y + currOriginOffset.value.y)
        }
    }, 
     [prevOriginOffset.value.x,  prevOriginOffset.value.y,  currOriginOffset.value.x,  currOriginOffset.value.y]
  )

  const onPinchEvent = useAnimatedGestureHandler<PinchGestureHandlerGestureEvent>({
    onStart: (_) => {
      prevZoomScale.value = tempZoomScale.value
      currZoomScale.value = 1
      prevOriginOffset.value = tempOriginOffset.value
      currOriginOffset.value = {x: _.focalX - SIZE / 2, y: _.focalY - SIZE / 2}
    },
    onActive: (event) => {
      if ((event.scale * prevZoomScale.value) > 1) {
        currZoomScale.value = event.scale
      }
    },
    onEnd: (_) => {
      tempZoomScale.value = zoomScale.value
      tempOriginOffset.value = pinchOriginOffset.value
    },


  const animatedStyle = useAnimatedStyle(
    () => ({
      transform: [
        {
          translateX: (pinchOriginOffset.value.x)
        },
        {
          translateY:  (pinchOriginOffset.value.y)
        },
        {
          scale: zoomScale.value
        },
        {
          translateX: - (pinchOriginOffset.value.x)
        },
        {
          translateY: - ( pinchOriginOffset.value.y)
        }
      ],
    }),
    []
  )

    return (
      <View style={[styles.zoomScrollContainer, { backgroundColor: color.core.black }]}>
        <PinchGestureHandler
          onGestureEvent={onPinchEvent}
        >
              <Animated.View >
                <Animated.Image
                  source={{ uri: zoomedImageUri }}
                  style={[styles.imageStyle, animatedStyle]}
                >
                </Animated.Image>
              </Animated.View>
        </PinchGestureHandler>
      </View>
    )
1个回答

6
我们最近遇到了类似的问题,试图在React Native中创建一个类似于Canvas的元素。经过几天的尝试后,我们解决了这个问题,并在下面概述了我们的思考过程和代码。我们支持平移和缩放,但是由于你不需要它,我已经将任何平移逻辑从下面的代码中删除了。
最初,我们像你一样,试图跟踪用户的每个偏移/缩放,但发现会导致奇怪的结果,并且我们无法想出如何组合变换。当用户释放手指时,对象会跳转到一个新位置,因为净聚焦点不正确。
现在我们跟踪比例和净x、y值,当用户捏合时,很容易将当前变换与先前变换组合起来,当捏合结束时。
我们分别跟踪X和Y分量,但它们可以轻松地合并成{x,y}对象。
我们还确保屏幕上有2个手指,因为在捏合结束时,如果两个手指没有同时从屏幕上移除,焦点可能会跳转到单个手指。
如果以下内容对您有用,请告诉我!
import React from "react";
import { View } from "react-native";
import {
    PinchGestureHandler,
} from "react-native-gesture-handler";
import Animated, {
    useAnimatedStyle,
    useAnimatedGestureHandler,
    useSharedValue,
} from "react-native-reanimated";


export default function Canvas() {
    const WIDTH = 400;
    const HEIGHT = 400;

    const focalX = useSharedValue(0);
    const focalY = useSharedValue(0);
    const xCurrent = useSharedValue(0);
    const yCurrent = useSharedValue(0);
    const xPrevious = useSharedValue(0);
    const yPrevious = useSharedValue(0);
    const scaleCurrent = useSharedValue(1);
    const scalePrevious = useSharedValue(1);

    const pinchHandler = useAnimatedGestureHandler({
        onStart: (event) => {
            if (event.numberOfPointers == 2) {
                focalX.value = event.focalX;
                focalY.value = event.focalY;
            }
        },
        onActive: (event) => {
            if (event.numberOfPointers == 2) {
                // On Android, the onStart event gives 0,0 for the focal
                // values, so we set them here instead too.
                if (event.oldState === 2) {
                    focalX.value = event.focalX;
                    focalY.value = event.focalY;
                }
                scaleCurrent.value = event.scale;

                xCurrent.value = (1 - scaleCurrent.value) * (focalX.value - WIDTH / 2);
                yCurrent.value = (1 - scaleCurrent.value) * (focalY.value - HEIGHT / 2);
            }
        },
        onEnd: () => {
            scalePrevious.value = scalePrevious.value * scaleCurrent.value;

            xPrevious.value = scaleCurrent.value * xPrevious.value + xCurrent.value;
            yPrevious.value = scaleCurrent.value * yPrevious.value + yCurrent.value;

            xCurrent.value = 0;
            yCurrent.value = 0;

            scaleCurrent.value = 1;
        },
    });

    const animatedStyle = useAnimatedStyle(() => {
        return {
            transform: [
                { translateX: xCurrent.value },
                { translateY: yCurrent.value },
                { scale: scaleCurrent.value },
                { translateX: xPrevious.value },
                { translateY: yPrevious.value },
                { scale: scalePrevious.value },
            ],
        };
    });

    return (
        <View>
            <PinchGestureHandler onGestureEvent={pinchHandler}>
                <Animated.View style={{width: 1000, height: 1000}}>
                    <Animated.Image
                        source={{uri:<IMAGE_URI>}}
                        style={[{
                            width: WIDTH,
                            height: HEIGHT,
                        },animatedStyle]}
                    >
                    </Animated.Image>
                </Animated.View>
            </PinchGestureHandler>
        </View>
    );
}

我正在寻找panning :) - Ali Demirci

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接