Hey thanks for this nice library!
Im trying to make a instagram pinch and zoom image, I have a working zoomable and movable image, but i struggle with the release phase, when i just pinch on release the image is way of the position so it "jumps" at start.
onGestureMove works it updates the position correct, but then when i do onGestureRelease the animated value of gesturePosition.x and gesturePosition.y is wrong.
Would love some help on this 馃挴
Element.js
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { Animated, Easing } from 'react-native'
import { PanGestureHandler, PinchGestureHandler, State } from 'react-native-gesture-handler'
const ANIMATION_DURATION = 200
export default class Element extends PureComponent {
opacity = new Animated.Value(1)
static propTypes = {
children: PropTypes.element.isRequired,
}
static contextTypes = {
scaleValue: PropTypes.object,
onGestureStart: PropTypes.func,
onGestureRelease: PropTypes.func,
gesturePosition: PropTypes.object,
}
onPanStateChange = ({ nativeEvent }) => {
switch (nativeEvent.state) {
case State.BEGAN:
return this.onGestureStart()
case State.END:
case State.FAILED:
case State.UNDETERMINED:
case State.CANCELLED:
return this.onGestureRelease()
default:
return null
}
}
onGestureStart = async () => {
const { onGestureStart, gesturePosition } = this.context
const measurement = await this.measureSelected()
this.measurement = measurement
onGestureStart({ element: this, measurement })
gesturePosition.setValue({ x: 0, y: 0 })
gesturePosition.setOffset({
x: measurement.x,
y: measurement.y,
})
Animated.timing(this.opacity, {
toValue: 0,
duration: ANIMATION_DURATION,
}).start()
}
onGestureRelease() {
const { gesturePosition, scaleValue, onGestureRelease } = this.context
Animated.parallel([
Animated.timing(gesturePosition.x, {
toValue: 0,
duration: ANIMATION_DURATION,
easing: Easing.ease,
useNativeDriver: true,
}),
Animated.timing(gesturePosition.y, {
toValue: 0,
duration: ANIMATION_DURATION,
easing: Easing.ease,
useNativeDriver: true,
}),
Animated.timing(scaleValue, {
toValue: 1,
duration: ANIMATION_DURATION,
easing: Easing.ease,
useNativeDriver: true,
}),
]).start(() => {
gesturePosition.setOffset({
x: this.measurement.x,
y: this.measurement.y,
})
// Reset original component opacity
this.opacity.setValue(1)
// Reset scale value
scaleValue.setValue(1)
requestAnimationFrame(() => {
onGestureRelease()
})
})
}
onGestureMove = ({ nativeEvent }) => {
const { gesturePosition } = this.context
const { translationX, translationY } = nativeEvent
gesturePosition.setValue({
x: translationX,
y: translationY,
})
}
onGesturePinch = ({ nativeEvent }) => {
const { scaleValue } = this.context
scaleValue.setValue(nativeEvent.scale)
}
setRef = el => {
this.parent = el
}
/* eslint-disable no-underscore-dangle */
measureSelected = async () => {
const parentMeasurement = await new Promise((resolve, reject) => {
try {
this.parent._component.measureInWindow((x, y) => {
resolve({ x, y })
})
} catch (err) {
reject(err)
}
})
return {
x: parentMeasurement.x,
y: parentMeasurement.y,
}
}
render() {
const imagePan = React.createRef()
return (
<PanGestureHandler
onGestureEvent={this.onGestureMove}
onHandlerStateChange={this.onPanStateChange}
ref={imagePan}
minPointers={2}
maxPointers={2}
minDist={0}
minDeltaX={0}
avgTouches
>
<PinchGestureHandler simultaneousHandlers={imagePan} onGestureEvent={this.onGesturePinch}>
<Animated.View ref={this.setRef} style={{ opacity: this.opacity }}>
{this.props.children}
</Animated.View>
</PinchGestureHandler>
</PanGestureHandler>
)
}
}
Selected.js
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { Animated, View } from 'react-native'
const styles = {
container: {
flex: 1,
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
},
background: {
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
backgroundColor: 'black',
},
}
const MINIMUM_SCALE = 1
const MAXIMUM_SCALE = 5
const SCALE_MULTIPLIER = 1.2
export default class Selected extends PureComponent {
static propTypes = {
selected: PropTypes.object,
}
static contextTypes = {
gesturePosition: PropTypes.object,
scaleValue: PropTypes.object,
}
render() {
const { selected } = this.props
const { gesturePosition, scaleValue } = this.context
const scale = scaleValue.interpolate({
inputRange: [MINIMUM_SCALE, MAXIMUM_SCALE],
outputRange: [MINIMUM_SCALE, MAXIMUM_SCALE * SCALE_MULTIPLIER],
extrapolate: 'clamp',
})
const backgroundOpacityValue = scaleValue.interpolate({
inputRange: [1, 1.2, 3],
outputRange: [0, 0.5, 0.8],
})
const transform = [...gesturePosition.getTranslateTransform(), { scale }]
// TODO: See if cloneElement glitches
return (
<View style={styles.container}>
<Animated.View style={[styles.background, { opacity: backgroundOpacityValue }]} />
<Animated.View
style={{
position: 'absolute',
zIndex: 10,
transform,
}}
>
{React.cloneElement(selected.element.props.children, { disableAnimation: true })}
</Animated.View>
</View>
)
}
}
As far as I am concerned this https://github.com/kmagiera/react-native-gesture-handler/issues/229 issue is related
Hi, @pontusab
Do you observe it on both platform?
@osdnk Yes, both iOS and Android.
I believe I am encountering the same issue on Android, I'm using ClojureScript and Expo. I could attach the source if it'd help. I imagine the generated JS wouldn't be very useful but if is I can attach it as well.
Never mind. Turns out I wasn't accessing the component state properly. :roll_eyes:
Hello this happened to me also so what I did is I don't use setOffset Offset somehow doesn't work right when used togehter with Animated.timing so what I did I defined a separate Animated.Value
to be used as gestureOffset which is then added to the gesturePosition using Animated.add
For example:
const transform = [{
translateX: Animated.add(gesturePosition.x, gestureOffset.x),
},
{
translateY: Animated.add(gesturePosition.y, gestureOffset.y),
}, { scale }]
This workaround works, but I think it is worth looking at Native code to see where is the issue.
Hope this will help.
@tomasgcs Do you have an example you can share?
Yep, @tomasgcs idea worked! Thanks