1

I made a single component based on the react-native-gesture-handler examples and they are working pretty well. I can transform, scale, move and rotate the image. But as soon as I rotate the image for 90° as an example, I receive wrong values for translateX and translateY. Moving down will move it right, swiping up will move it left etc.

How do I take the rotation into consideration based on my component. Please ignore the "tilt" feature, it's not in use for now.

import React from 'react';
import {Animated, StyleSheet} from 'react-native';

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

export class PinchableBox extends React.Component {
    panRef = React.createRef();
    rotationRef = React.createRef();
    pinchRef = React.createRef();
    dragRef = React.createRef();

    constructor(props) {
        super(props);

        this.state = {
            _isMounted: false
        };

        /* Pinching */
        this._baseScale = new Animated.Value(1);
        this._pinchScale = new Animated.Value(1);

        this._scale = Animated.multiply(this._baseScale, this._pinchScale);
        this._lastScale = 1;
        this._onPinchGestureEvent = Animated.event(
            [{nativeEvent: {scale: this._pinchScale}}],
            {useNativeDriver: true}
        );

        /* Rotation */
        this._rotate = new Animated.Value(0);
        this._rotateStr = this._rotate.interpolate({
            inputRange: [-100, 100],
            outputRange: ['-100rad', '100rad'],
        });
        this._lastRotate = 0;
        this._onRotateGestureEvent = Animated.event(
            [{nativeEvent: {rotation: this._rotate}}],
            {useNativeDriver: true}
        );

        /* Tilt */
        this._tilt = new Animated.Value(0);
        this._tiltStr = this._tilt.interpolate({
            inputRange: [-501, -500, 0, 1],
            outputRange: ['1rad', '1rad', '0rad', '0rad'],
        });
        this._lastTilt = 0;
        this._onTiltGestureEvent = Animated.event(
            [{nativeEvent: {translationY: this._tilt}}],
            {useNativeDriver: false}
        );

        this._translateX = new Animated.Value(0);
        this._translateY = new Animated.Value(0);

        this._lastOffset = {x: 0, y: 0};
        this._onGestureEvent = Animated.event(
            [
                {
                    nativeEvent: {
                        translationX: this._translateX,
                        translationY: this._translateY,
                    },
                },
            ],
            {useNativeDriver: true}
        );

    }


    _onRotateHandlerStateChange = event => {
        if (event.nativeEvent.oldState === State.ACTIVE) {
            this._lastRotate += event.nativeEvent.rotation;
            this._rotate.setOffset(this._lastRotate);
            this._rotate.setValue(0);
        }
    };
    _onPinchHandlerStateChange = event => {
        if (event.nativeEvent.oldState === State.ACTIVE) {
            this._lastScale *= event.nativeEvent.scale;
            this._baseScale.setValue(this._lastScale);
            this._pinchScale.setValue(1);
        }
    };
    _onTiltGestureStateChange = event => {
        if (event.nativeEvent.oldState === State.ACTIVE) {
            this._lastTilt += event.nativeEvent.translationY;
            this._tilt.setOffset(this._lastTilt);
            this._tilt.setValue(0);
        }
    };
    _onHandlerStateChange = event => {
        if (event.nativeEvent.oldState === State.ACTIVE) {
            this._lastOffset.x += event.nativeEvent.translationX;
            this._lastOffset.y += event.nativeEvent.translationY;
            this._translateX.setOffset(this._lastOffset.x);
            this._translateX.setValue(0);
            this._translateY.setOffset(this._lastOffset.y);
            this._translateY.setValue(0);
        }
    };

    render() {
        const {image} = this.props;

        return (
            <PanGestureHandler
                ref={this.dragRef}
                simultaneousHandlers={[this.rotationRef, this.pinchRef]}
                onGestureEvent={this._onGestureEvent}
                minPointers={1}
                maxPointers={2}
                avgTouches
                onHandlerStateChange={this._onHandlerStateChange}>
                <Animated.View style={styles.wrapper}>
                    <RotationGestureHandler
                        ref={this.rotationRef}
                        simultaneousHandlers={this.pinchRef}
                        onGestureEvent={this._onRotateGestureEvent}
                        onHandlerStateChange={this._onRotateHandlerStateChange}>
                        <Animated.View style={styles.wrapper}>
                            <PinchGestureHandler
                                ref={this.pinchRef}
                                simultaneousHandlers={this.rotationRef}
                                onGestureEvent={this._onPinchGestureEvent}
                                onHandlerStateChange={this._onPinchHandlerStateChange}>
                                <Animated.View style={styles.container} collapsable={false}>
                                    <Animated.Image
                                        resizeMode={"contain"}
                                        style={[
                                            styles.pinchableImage,
                                            {
                                                transform: [
                                                    {scale: this._scale},
                                                    {rotate: this._rotateStr},
                                                    {rotateX: this._tiltStr},
                                                    {translateX: this._translateX},
                                                    {translateY: this._translateY},
                                                ],
                                            },
                                        ]}
                                        source={{uri: image}}
                                    />
                                </Animated.View>
                            </PinchGestureHandler>
                        </Animated.View>
                    </RotationGestureHandler>
                </Animated.View>
            </PanGestureHandler>
        );
    }
}

export default PinchableBox;


const styles = StyleSheet.create({
    container: {
        ...StyleSheet.absoluteFillObject,
        backgroundColor: 'transparent',
        overflow: 'hidden',
        alignItems: 'center',
        flex: 1,
        justifyContent: 'center'
    },
    pinchableImage: {
        ...StyleSheet.absoluteFillObject,
    },
    wrapper: {
        flex: 1,
    },
});
4

1 回答 1

4

The solution was actually pretty easy. Instead of adding all computed values to one View, I had to chunk them into a single view per gesture.

import React from 'react';
import {Animated, StyleSheet} from 'react-native';

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



export class PinchableBox extends React.Component {
    panRef = React.createRef();
    rotationRef = React.createRef();
    pinchRef = React.createRef();
    dragRef = React.createRef();


    constructor(props) {
        super(props);

        this.state = {
            _isMounted: false
        };

        /* Pinching */
        this._baseScale = new Animated.Value(1);
        this._pinchScale = new Animated.Value(1);

        this._scale = Animated.multiply(this._baseScale, this._pinchScale);
        this._lastScale = 1;
        this._onPinchGestureEvent = Animated.event(
            [{nativeEvent: {scale: this._pinchScale}}],
            {useNativeDriver: true}
        );

        /* Rotation */
        this._rotate = new Animated.Value(0);
        this._rotateStr = this._rotate.interpolate({
            inputRange: [-100, 100],
            outputRange: ['-100rad', '100rad'],
        });
        this._lastRotate = 0;
        this._onRotateGestureEvent = Animated.event(
            [{nativeEvent: {rotation: this._rotate}}],
            {useNativeDriver: true}
        );

        /* Tilt */
        this._tilt = new Animated.Value(0);
        this._tiltStr = this._tilt.interpolate({
            inputRange: [-501, -500, 0, 1],
            outputRange: ['1rad', '1rad', '0rad', '0rad'],
        });
        this._lastTilt = 0;
        this._onTiltGestureEvent = Animated.event(
            [{nativeEvent: {translationY: this._tilt}}],
            {useNativeDriver: true}
        );

        this._translateX = new Animated.Value(0);
        this._translateY = new Animated.Value(0);

        this._lastOffset = {x: 0, y: 0};
        this._onGestureEvent = Animated.event(
            [
                {
                    nativeEvent: {
                        translationX: this._translateX,
                        translationY: this._translateY,
                    },
                },
            ],
            {useNativeDriver: true}
        );

    }


    _onRotateHandlerStateChange = event => {
        if (event.nativeEvent.oldState === State.ACTIVE) {
            this._lastRotate += event.nativeEvent.rotation;
            this._rotate.setOffset(this._lastRotate);
            this._rotate.setValue(0);
        }
    };
    _onPinchHandlerStateChange = event => {
        if (event.nativeEvent.oldState === State.ACTIVE) {
            this._lastScale *= event.nativeEvent.scale;
            this._baseScale.setValue(this._lastScale);
            this._pinchScale.setValue(1);
        }
    };
    _onTiltGestureStateChange = event => {
        if (event.nativeEvent.oldState === State.ACTIVE) {
            this._lastTilt += event.nativeEvent.translationY;
            this._tilt.setOffset(this._lastTilt);
            this._tilt.setValue(0);
        }
    };
    _onHandlerStateChange = event => {
        if (event.nativeEvent.oldState === State.ACTIVE) {
            this._lastOffset.x += event.nativeEvent.translationX;
            this._lastOffset.y += event.nativeEvent.translationY;
            this._translateX.setOffset(this._lastOffset.x);
            this._translateX.setValue(0);
            this._translateY.setOffset(this._lastOffset.y);
            this._translateY.setValue(0);
        }
    };

    render() {
        const {image, children} = this.props;

        return (
            <React.Fragment>
                <PanGestureHandler
                ref={this.dragRef}
                simultaneousHandlers={[this.rotationRef, this.pinchRef]}
                onGestureEvent={this._onGestureEvent}
                minPointers={2}
                maxPointers={2}
                avgTouches
                onHandlerStateChange={this._onHandlerStateChange}>
                <Animated.View  style={[
                    styles.wrapper,
                    {
                        transform: [
                            {translateX: this._translateX},
                            {translateY: this._translateY},
                        ],
                    },
                ]}>
                    <RotationGestureHandler
                        ref={this.rotationRef}
                        simultaneousHandlers={this.pinchRef}
                        onGestureEvent={this._onRotateGestureEvent}
                        onHandlerStateChange={this._onRotateHandlerStateChange}>
                        <Animated.View style={[
                                styles.wrapper,
                                {
                                    transform: [
                                        {rotate: this._rotateStr},
                                    ],
                                },
                            ]}
                        >
                            <PinchGestureHandler
                                ref={this.pinchRef}
                                simultaneousHandlers={this.rotationRef}
                                onGestureEvent={this._onPinchGestureEvent}
                                onHandlerStateChange={this._onPinchHandlerStateChange}>
                                <Animated.View style={[
                                    styles.container,
                                    {
                                        transform: [
                                            {scale: this._scale},
                                        ],
                                    },
                                ]} collapsable={false}>
                                    <Animated.Image
                                        resizeMode={"contain"}
                                        style={[
                                            styles.pinchableImage,
                                        ]}
                                        source={{uri: image}}
                                    />
                                </Animated.View>
                            </PinchGestureHandler>
                        </Animated.View>
                    </RotationGestureHandler>
                </Animated.View>
            </PanGestureHandler>
                { children }
            </React.Fragment>
        );
    }
}

export default PinchableBox;


const styles = StyleSheet.create({
    container: {
        ...StyleSheet.absoluteFillObject,
        backgroundColor: 'transparent',
        overflow: 'hidden',
        alignItems: 'center',
        flex: 1,
        justifyContent: 'center'
    },
    pinchableImage: {
        backgroundColor: "transparent",
        ...StyleSheet.absoluteFillObject,
    },
    wrapper: {
        flex: 1,
    },
});
于 2019-12-03T04:08:21.067 回答