【问题标题】:How to update scale focal origin with react reanimated and react native gesture handler for iterative pinch gestures?如何使用重新激活的反应更新缩放焦点原点并为迭代捏合手势反应本机手势处理程序?
【发布时间】:2022-08-18 04:21:15
【问题描述】:

我使用 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>
    )

    标签: react-native animation gesture react-native-reanimated react-native-gesture-handler


    【解决方案1】:

    我们最近遇到了一个与此非常相似的事情,试图在 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>
        );
    }
    

    【讨论】:

      【解决方案2】:

      用打字稿要求上面的这个例子会不会太过分了?哈哈

      【讨论】:

        猜你喜欢
        • 2021-11-08
        • 1970-01-01
        • 2020-03-05
        • 2020-11-24
        • 2018-12-02
        • 2019-11-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多