question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Nested PinchGestureHandler & PanGestureHandler not working in Android

See original GitHub issue

I am completely confused on why this code would work on iOS but not on Android. My react-native-gesture-handler setup is working because I have another PanGestureHandler working as expected.

/*********************** Imports ***********************/
import React, { useRef, useEffect } from 'react';
import {
    View, StyleSheet, Animated,
} from 'react-native';

import ZoomImage from './ZoomableImage';
import { vw, vh, isiPhoneX } from '../Styles/StyleUtils';
/********************* End Imports *********************/

/*********************** ImageView Function ***********************/
const ImageView = ({ dismiss, uri, imageLayout }) => {
    const animation = useRef(new Animated.Value(0));

    useEffect(() => {
        Animated.timing(animation.current, {
            toValue: 1,
            duration: 250,
        }).start();
    }, []);

    function closeImage() {
        Animated.timing(animation.current, {
            toValue: 0,
            duration: 250,
        }).start(() => dismiss());
    }

    return (
        <View style={styles.mainContainer}>
            <Animated.View style={[
                styles.container,
                {
                    backgroundColor: animation.current.interpolate({
                        inputRange: [0, 1],
                        outputRange: ["rgba(0, 0, 0, 0)", "rgba(0, 0, 0, 0.5)"],
                    })
                }
            ]}>
                {/* <View style={styles.header}>
                    <TouchableOpacity style={styles.closeBtn} onPress={closeImage}>
                        <Icon name="close" color={blackColor} size={30} />
                    </TouchableOpacity>
                </View> */}
                <ZoomImage dismiss={closeImage} imageStyle={[
                    {
                        left: animation.current.interpolate({
                            inputRange: [0, 1],
                            outputRange: [imageLayout.pageX, 0]  // 0 : 150, 0.5 : 75, 1 : 0
                        }),

                        top: animation.current.interpolate({
                            inputRange: [0, 1],
                            outputRange: [imageLayout.pageY, vh(24)]  // 0 : 150, 0.5 : 75, 1 : 0
                        }),
                        width: animation.current.interpolate({
                            inputRange: [0, 1],
                            outputRange: [imageLayout.width, vw(100)],
                        }),
                        height: animation.current.interpolate({
                            inputRange: [0, 1],
                            outputRange: [imageLayout.height, vw(100)],
                        })
                    }
                ]}
                source={{ uri: uri }} />
            </Animated.View>
        </View>
    );
};
/********************** End ImageView Function *******************/
export default ImageView;

const styles = StyleSheet.create({
    header: {
        marginTop: isiPhoneX() ? 40 : 25,
        alignItems: "flex-end"
    },
    closeBtn: {
        paddingHorizontal: 20,
    },
    mainContainer: {
        position: "absolute",
        width: vw(100),
        height: vh(100)
    },
    container: {
        position: "absolute",
        top: 0,
        left: 0,
        right: 0, bottom: 0,
    },
    image: {
        position: "absolute",
    }
});
/*********************** Imports ***********************/
import React, { useEffect, useState, useCallback, useRef } from 'react';
import { Animated } from 'react-native';

import {
    State,
    PanGestureHandler,
    PinchGestureHandler
} from 'react-native-gesture-handler';

import { vw, vh } from '../Styles/StyleUtils';
/********************* End Imports *********************/

/*********************** ZoomableImage Function ***********************/
const ZoomableImage = (props) => {
    const panRef = useRef();
    const pinchRef = useRef();

    const closeAnimation = useRef(new Animated.ValueXY({ x: 0, y: 0 }));

    const scaleAnimation = useRef(new Animated.Value(1));
    const baseScale = useRef(new Animated.Value(1));
    const scale = useRef(Animated.multiply(baseScale.current, scaleAnimation.current));
    const [lastScale, setLastScale] = useState(1);

    useEffect(() => {
        console.log('Refs', panRef);
    }, [panRef.current]);

    const onPanHandlerGesture = useCallback(({ nativeEvent }) => {
        console.log('Native Event', nativeEvent);
        closeAnimation.current.setValue({
            x: nativeEvent.translationX,
            y: nativeEvent.translationY
        });
    }, []);

    const onPanHandlerStateChange = useCallback(({ nativeEvent }) => {
        console.log('New Pan Event', nativeEvent);
        if (nativeEvent.oldState === State.ACTIVE) {
            if (
                nativeEvent.translationY > 250
                || nativeEvent.velocityY > 1200
            ) {
                Animated.parallel([
                    Animated.timing(scaleAnimation.current, {
                        toValue: 1,
                        duration: 200
                    }),
                    Animated.timing(baseScale.current, {
                        toValue: 1,
                        duration: 200
                    }),
                    Animated.timing(closeAnimation.current, {
                        toValue: { x: 0, y: 0 },
                        duration: 200
                    })
                ]).start(() => props.dismiss());
            }
            else {
                Animated.timing(closeAnimation.current, {
                    toValue: { x: 0, y: 0 },
                    duration: 100
                }).start();
            }
        }
    }, [lastScale]);

    const onPinchGestureEvent = Animated.event([{ nativeEvent: { scale: scaleAnimation.current } }]);

    useCallback(({ nativeEvent }) => {
        scaleAnimation.current.setValue(nativeEvent.scale);
    }, [lastScale]);

    const onPinchHandlerStateChange = ({ nativeEvent }) => {
        console.log('New Pinch Event', nativeEvent);
        if (nativeEvent.oldState === State.ACTIVE) {
            const newLastScale = lastScale * nativeEvent.scale;
            setLastScale(newLastScale);
            baseScale.current.setValue(newLastScale);
            scaleAnimation.current.setValue(1);
        }
    };

    return (
        <PanGestureHandler maxPointers={2} avgTouches onHandlerStateChange={onPanHandlerStateChange}
        minDist={10} onGestureEvent={onPanHandlerGesture} ref={panRef}>
            <PinchGestureHandler ref={pinchRef} simultaneousHandlers={panRef}
            onHandlerStateChange={onPinchHandlerStateChange} onGestureEvent={onPinchGestureEvent}>
                <Animated.Image style={[
                    props.imageStyle,
                    {
                        transform: [
                            { perspective: 1000 },
                            {
                                translateY: closeAnimation.current.y.interpolate({
                                    inputRange: [-vh(25), vh(25)],
                                    outputRange: [-vh(25), vh(25)],
                                    extrapolate: "clamp"
                                })
                            },
                            {
                                translateX: closeAnimation.current.x.interpolate({
                                    inputRange: [-vw(25), vw(25)],
                                    outputRange: [-vw(10), vw(10)],
                                    extrapolate: "clamp"
                                })
                            },
                            {
                                scale: scale.current.interpolate({
                                    inputRange: [1, 2.5],
                                    outputRange: [1, 2.5],
                                    extrapolate: "clamp"
                                })
                            }
                        ]
                    }
                ]} source={props.source} />
            </PinchGestureHandler>
        </PanGestureHandler>
    );
};

ZoomableImage.defaultProps = {
    imageStyle: {},
    source: { uri: "" }
};
/********************** End ZoomableImage Function *******************/
export default ZoomableImage;

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:3
  • Comments:9 (1 by maintainers)

github_iconTop GitHub Comments

15reactions
stuartthompsoncommented, Dec 12, 2019

@thevishnupradeep @orcunorcun @Hirbod I think I found a solution. Try adding minPointers and maxPointers to the pan and pinch gesture handlers like this:

<PanGestureHandler
   minPointers={1}
   maxPointers={1}>
   <PinchGestureHandler
       minPointers={2}
       maxPointers={2}>

I think this is saying that you pan with 1 finger, and pinch with 2.

3reactions
jakub-gonetcommented, Sep 1, 2020

Every handler should have it’s own Animated.View as a child, like @emclab said. This is related to the way how we internally pass callbacks inside handlers, I’ll check out if this is fixable. Closing because it doesn’t have the proper smallest possible repro example.

Read more comments on GitHub >

github_iconTop Results From Across the Web

PinchGestureHandler not working on android - Stack Overflow
i have an android device, and it is not working, dont know why please help!!!!! beside this PanGestureHandler working perfectly.
Read more >
About Gesture Handlers | React Native ... - Software Mansion
When one gesture becomes active, it cancels all the other gestures (read more ... Gesture handler components do not instantiate a native view...
Read more >
Using the Gesture Handler in React Native | by SaidHayani
PanGestureHandler is a container or wrapper we can use to wrap a component (EX: View `) to handle and track the gesture movements...
Read more >
React-Native Pinchgesturehandler Doesn't Work - ADocLib
PanGestureHandler doesn't work on Android 5.11.11; ... In my screen I try to use nested gestures PanRotatePinch but it is not working If...
Read more >
[QUESTION] PinchGestureHandler transformOrigin problem ...
You could also use a PanGestureHandler in order to figure out the position of your gesture inside the image.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found