Bayram
Bayram

Reputation: 515

React native pinch zoom with redash and reanimated

I'm just trying to scale the image with pinch zoom using reanimated and redash.

I followed this tutorial

But the component that I grab stays on top of the previous but under the next component. I know It's a bit complicated explainatory. For that I tried to draw a schema to explain you better.

enter image description here

So, In the Image above 1,2 and 3 are my Cards which holds the Images. And I pinch Zooming to 2nd Image, but It stays above the first one (which is I wanted) but, also stays under the next card which is 3rd In this case.

How can I prevent This?

const SIZE = shared === true ? width - 20 : width;
const styles = StyleSheet.create({
    image: {
        ...StyleSheet.absoluteFill,
        width: undefined,
        height: undefined,
        resizeMode: "contain",
    },
});
const state = new Value(State.UNDETERMINED);
const pinchRef = useRef(PinchGestureHandler);
const origin = vec.createValue(0, 0);
const pinch = vec.createValue(0, 0);
const focal = vec.createValue(0, 0);
const scale = new Value(1);
const numberOfPointers = new Value(0);
const pinchGestureHandler = onGestureEvent({
    numberOfPointers,
    scale,
    state,
    focalX: focal.x,
    focalY: focal.y,
});
const zIndex = cond(eq(state, State.ACTIVE), 3, 0);
const adjustedFocal = vec.add(
    {
        x: -SIZE / 2,
        y: -SIZE / 2,
    },
    focal
);
useCode(
    () =>
        block([
            cond(pinchBegan(state), vec.set(origin, adjustedFocal)),
            cond(
                pinchActive(state, numberOfPointers),
                vec.set(pinch, vec.minus(vec.sub(origin, adjustedFocal)))
            ),
            cond(eq(state, State.END), [
                set(pinch.x, timing({ from: pinch.x, to: 0 })),
                set(pinch.y, timing({ from: pinch.y, to: 0 })),
                set(scale, timing({ from: scale, to: 1 })),
            ]),
        ]),
    [adjustedFocal, numberOfPointers, origin, pinch, scale, state]
);
return (
    <>
        <Animated.View style={{ width: SIZE, height: SIZE, zIndex }}>
            <PinchGestureHandler ref={pinchRef} {...pinchGestureHandler}>
                <Animated.View style={StyleSheet.absoluteFill}>
                    <Animated.Image
                        style={[
                            styles.image,
                            {
                                transform: [
                                    ...translate(pinch),
                                    ...transformOrigin(origin, { scale }),
                                ],
                            },
                        ]}
                        source={{ uri: app.HOST + photo }}
                    />
                </Animated.View>
            </PinchGestureHandler>
        </Animated.View>
    </>
);

Here is the component

Upvotes: 0

Views: 2096

Answers (1)

samridhgupta
samridhgupta

Reputation: 1725

I have worked with this quite a bit and have the opposite issue, Android failing on 1 finger and not starting the pan when failed. You need to keep track of the focal points and fingers to adjust back and forth to removing and adding fingers. Here is some code to help you out...

const onPinch = useAnimatedGestureHandler<PinchGestureHandlerGestureEvent>(
    {
      onActive: (evt) => {
        /**
         * The scale is clamped to a minimum of 1 and maximum of 8 for aesthetics.
         * We use the clamped value to determine a local event scale so the focal
         * point does not become out of sync with the actual photo scaling, e.g.
         * evt.scale is 20 but scale is 8, using evt.scale for offset will put the
         * photo and calculations out of sync
         */
        scale.value = clamp(offsetScale.value * evt.scale, 1, 8);
        const localEvtScale = scale.value / offsetScale.value;

        /**
         * We calculate the adjusted focal point on the photo using the events
         * focal position on the screen, screen size, and current photo offset
         */
        adjustedFocalX.value = evt.focalX - (halfScreenWidth - offsetX.value);
        adjustedFocalY.value = evt.focalY - (halfScreenHeight + offsetY.value);

        /**
         * If the number of fingers on the screen changes, the position of the
         * focal point will change and this needs to be accounted for, e.g. if
         * two fingers are on the screen the focal is between them, but if one is
         * then removed the focal is now at the remaining fingers touch position.
         * If this happens without accounting for the change the image will jump
         * around, we keep track of the previous two finger focal to adjust for this
         * change in a reduction from two fingers to one, then if another finger
         * is added again we adjust the origin to account for the difference between
         * the original two finger touch and the new two finger touch position.
         */
        if (numberOfPinchFingers.value !== evt.numberOfPointers) {
          numberOfPinchFingers.value = evt.numberOfPointers;
          if (evt.numberOfPointers === 1) {
            focalOffsetX.value = oldFocalX.value - adjustedFocalX.value;
            focalOffsetY.value = oldFocalY.value - adjustedFocalY.value;
          } else if (numberOfPinchFingers.value > 1) {
            originX.value =
              originX.value -
              (oldFocalX.value / localEvtScale -
                adjustedFocalX.value / localEvtScale);
            originY.value =
              originY.value -
              (oldFocalY.value / localEvtScale -
                adjustedFocalY.value / localEvtScale);
          }
        }

        /**
         * If pinch handler has been activated via two fingers then the fingers
         * reduced to one we keep track of the old focal using the focal offset
         * from when the number of fingers was two. We then translate the photo
         * taking into account the offset, focal, focal offset, origin, and scale.
         */
        if (numberOfPinchFingers.value === 1) {
          oldFocalX.value = adjustedFocalX.value + focalOffsetX.value;
          oldFocalY.value = adjustedFocalY.value + focalOffsetY.value;
          translateX.value =
            offsetX.value - oldFocalX.value + localEvtScale * originX.value;
          translateY.value =
            offsetY.value + oldFocalY.value - localEvtScale * originY.value;

          /**
           * If the number of fingers in the gesture is greater than one the
           * adjusted focal point is saved as the old focal and the photo is
           * translated taking into account the offset, focal, origin, and scale.
           */
        } else if (numberOfPinchFingers.value > 1) {
          oldFocalX.value = adjustedFocalX.value;
          oldFocalY.value = adjustedFocalY.value;
          translateX.value =
            offsetX.value -
            adjustedFocalX.value +
            localEvtScale * originX.value;
          translateY.value =
            offsetY.value +
            adjustedFocalY.value -
            localEvtScale * originY.value;
        }
      },
      onFinish: () => {
        /**
         * When the pinch is finished if the scale is less than 1 return the
         * photo to center, if the photo is inside the edges of the screen
         * return the photo to line up with the edges, otherwise leave the
         * photo in its current position
         */
        translateX.value =
          scale.value < 1
            ? withTiming(0)
            : translateX.value > halfScreenWidth * (scale.value - 1)
            ? withTiming(halfScreenWidth * (scale.value - 1))
            : translateX.value < -halfScreenWidth * (scale.value - 1)
            ? withTiming(-halfScreenWidth * (scale.value - 1))
            : translateX.value;

        /**
         * When the pinch is finished if the height is less than the screen
         * height return the photo to center, if the photo is inside the
         * edges of the screen return the photo to line up with the edges,
         * otherwise leave the photo in its current position
         */
        translateY.value =
          currentImageHeight * scale.value < screenHeight
            ? withTiming(0)
            : translateY.value >
              (currentImageHeight / 2) * scale.value - screenHeight / 2
            ? withTiming(
                (currentImageHeight / 2) * scale.value - screenHeight / 2,
              )
            : translateY.value <
              (-currentImageHeight / 2) * scale.value + screenHeight / 2
            ? withTiming(
                (-currentImageHeight / 2) * scale.value + screenHeight / 2,
              )
            : translateY.value;

        /**
         * If the scale has been reduced below one, i.e. zoomed out, translate
         * the zoom back to one
         */
        offsetScale.value = scale.value < 1 ? 1 : scale.value;
        scale.value = scale.value < 1 ? withTiming(1) : scale.value;

        resetTouchValues();
      },
      onStart: (evt) => {
        if (evt.numberOfPointers > 1) { // This # pointers check is WIP for android support, not needed in current iOS version
          /**
           * Cancel any previous motion animation on translations when a touch
           * begins to interrupt the animation and take over the position handling
           */
          cancelAnimation(translateX);
          cancelAnimation(translateY);
          cancelAnimation(scale);

          /**
           * Set pinch to true to stop all pan gesture interactions
           */
          isPinch.value = true;

          /**
           * Reset isSwiping as now the pan gesture handler is no longer running
           */
          isSwiping.value = IsSwiping.UNDETERMINED;

          /**
           * Set initial values for pinch gesture interaction handler
           */
          numberOfPinchFingers.value = evt.numberOfPointers;
          offsetX.value = translateX.value;
          offsetY.value = translateY.value;
          adjustedFocalX.value = evt.focalX - (halfScreenWidth - offsetX.value);
          adjustedFocalY.value =
            evt.focalY - (halfScreenHeight + offsetY.value);
          originX.value = adjustedFocalX.value;
          originY.value = adjustedFocalY.value;
          offsetScale.value = scale.value;
        }
      },
    },
    [currentImageHeight],
  );

Upvotes: 2

Related Questions