1import React from 'react';
2import { Animated, StyleSheet, View } from 'react-native';
3import {
4  PanGestureHandler,
5  PanGestureHandlerStateChangeEvent,
6  PinchGestureHandler,
7  PinchGestureHandlerStateChangeEvent,
8  RotationGestureHandler,
9  RotationGestureHandlerStateChangeEvent,
10  State,
11} from 'react-native-gesture-handler';
12
13const USE_NATIVE_DRIVER = true;
14
15class PinchableBox extends React.Component {
16  panRef = React.createRef<PanGestureHandler>();
17  rotationRef = React.createRef<RotationGestureHandler>();
18  pinchRef = React.createRef<PinchGestureHandler>();
19
20  _baseScale: Animated.Value;
21  _pinchScale: Animated.Value;
22  _scale: Animated.AnimatedMultiplication<number>;
23  _lastScale: number;
24  _onPinchGestureEvent: (...args: any[]) => void;
25
26  _rotate: Animated.Value;
27  _rotateStr: Animated.AnimatedInterpolation<number>;
28  _lastRotate: number;
29  _onRotateGestureEvent?: (...args: any[]) => void;
30
31  _tilt: Animated.Value;
32  _tiltStr: Animated.AnimatedInterpolation<number>;
33  _lastTilt: number;
34  _onTiltGestureEvent: (...args: any[]) => void;
35
36  constructor(props: any) {
37    super(props);
38
39    /* Pinching */
40    this._baseScale = new Animated.Value(1);
41    this._pinchScale = new Animated.Value(1);
42    this._scale = Animated.multiply(this._baseScale, this._pinchScale);
43    this._lastScale = 1;
44    this._onPinchGestureEvent = Animated.event([{ nativeEvent: { scale: this._pinchScale } }], {
45      useNativeDriver: USE_NATIVE_DRIVER,
46    });
47
48    /* Rotation */
49    this._rotate = new Animated.Value(0);
50    this._rotateStr = this._rotate.interpolate({
51      inputRange: [-100, 100],
52      outputRange: ['-100rad', '100rad'],
53    });
54    this._lastRotate = 0;
55    this._onRotateGestureEvent = Animated.event([{ nativeEvent: { rotation: this._rotate } }], {
56      useNativeDriver: USE_NATIVE_DRIVER,
57    });
58
59    /* Tilt */
60    this._tilt = new Animated.Value(0);
61    this._tiltStr = this._tilt.interpolate({
62      inputRange: [-501, -500, 0, 1],
63      outputRange: ['1rad', '1rad', '0rad', '0rad'],
64    });
65    this._lastTilt = 0;
66    this._onTiltGestureEvent = Animated.event([{ nativeEvent: { translationY: this._tilt } }], {
67      useNativeDriver: USE_NATIVE_DRIVER,
68    });
69  }
70
71  _onRotateHandlerStateChange = (event: RotationGestureHandlerStateChangeEvent) => {
72    if (event.nativeEvent.oldState === State.ACTIVE) {
73      this._lastRotate += event.nativeEvent.rotation;
74      this._rotate.setOffset(this._lastRotate);
75      this._rotate.setValue(0);
76    }
77  };
78  _onPinchHandlerStateChange = (event: PinchGestureHandlerStateChangeEvent) => {
79    if (event.nativeEvent.oldState === State.ACTIVE) {
80      this._lastScale *= event.nativeEvent.scale;
81      this._baseScale.setValue(this._lastScale);
82      this._pinchScale.setValue(1);
83    }
84  };
85  _onTiltGestureStateChange = (event: PanGestureHandlerStateChangeEvent) => {
86    if (event.nativeEvent.oldState === State.ACTIVE) {
87      this._lastTilt += event.nativeEvent.translationY;
88      this._tilt.setOffset(this._lastTilt);
89      this._tilt.setValue(0);
90    }
91  };
92  render() {
93    return (
94      <PanGestureHandler
95        ref={this.panRef}
96        onGestureEvent={this._onTiltGestureEvent}
97        onHandlerStateChange={this._onTiltGestureStateChange}
98        minDist={10}
99        minPointers={2}
100        maxPointers={2}
101        avgTouches>
102        <Animated.View style={styles.wrapper}>
103          <RotationGestureHandler
104            ref={this.rotationRef}
105            simultaneousHandlers={this.pinchRef}
106            onGestureEvent={this._onRotateGestureEvent}
107            onHandlerStateChange={this._onRotateHandlerStateChange}>
108            <Animated.View style={styles.wrapper}>
109              <PinchGestureHandler
110                ref={this.pinchRef}
111                simultaneousHandlers={this.rotationRef}
112                onGestureEvent={this._onPinchGestureEvent}
113                onHandlerStateChange={this._onPinchHandlerStateChange}>
114                <Animated.View style={styles.container} collapsable={false}>
115                  <Animated.Image
116                    style={[
117                      styles.pinchableImage,
118                      {
119                        transform: [
120                          { perspective: 200 },
121                          { scale: this._scale },
122                          { rotate: this._rotateStr },
123                          { rotateX: this._tiltStr },
124                        ],
125                      },
126                    ]}
127                    source={require('../../assets/images/swmansion.png')}
128                  />
129                </Animated.View>
130              </PinchGestureHandler>
131            </Animated.View>
132          </RotationGestureHandler>
133        </Animated.View>
134      </PanGestureHandler>
135    );
136  }
137}
138
139const GestureHandlerPinchScreen = () => (
140  <View style={{ flex: 1 }}>
141    <PinchableBox />
142    <PinchableBox />
143  </View>
144);
145
146GestureHandlerPinchScreen.navigationOptions = {
147  title: 'Pinch and Rotate',
148};
149
150export default GestureHandlerPinchScreen;
151
152const styles = StyleSheet.create({
153  container: {
154    ...StyleSheet.absoluteFillObject,
155    backgroundColor: 'black',
156    overflow: 'hidden',
157    alignItems: 'center',
158    flex: 1,
159    justifyContent: 'center',
160  },
161  pinchableImage: {
162    width: 250,
163    height: 250,
164  },
165  wrapper: {
166    flex: 1,
167  },
168});
169