Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PanResponder snaps Animated.View back to original position on second drag

I have created a PanResponder to move a Animated.View vertically. When I move it from it's original position it works fine, but once I go to move it for a second time it snaps back to its original position and then moves relative to the touch.

I am unpacking the responder straight into the Animated.View, could this be causing this behaviour somehow?

Here is how I define my PanResponder:

this.state = {                            
  drag: new Animated.ValueXY()            
}                                         

this._responder = PanResponder.create({                                              
    onStartShouldSetPanResponder: () => true,                             
    onPanResponderMove: Animated.event([null, {                           
      dy: this.state.drag.y                                             
    }]),                                                                  
    onPanResponderRelease: (e, coords) => {
      ...
    }     
})

And applying the responder to my Animated.View:

<Animated.View {...this._responder.panHandlers} style={this.state.drag.getLayout()}>
  // children go here
</Animated.View>                 

Thanks

like image 680
JmJ Avatar asked Feb 02 '17 23:02

JmJ


1 Answers

First, let's look at why this is happening:

  • Your onPanResponderMove callback reads the gesture's dy (delta Y), which gives you the amount of pixels moved vertically since the beginning of the gesture. This means that every time you start a new gesture, the delta starts from 0.

  • AnimatedXY#getLayout() on the other hand simply maps the y value to style property top. This means that when y is set to 0 in the beginning of the touch, the element will bounce back to its initial, non-offset position.

In order to preserve the offset from the previous drag, you can use setOffset to preserve the previous offset position, and then setValue to reset the initial delta to 0. This can be done when the gesture starts, e.g. on onPanResponderGrant:

this._responder = PanResponder.create({
  onStartShouldSetPanResponder: () => true,
  onPanResponderGrant: (evt, gestureState) => {
    this.state.drag.setOffset(this.state.drag.__getValue());
    this.state.drag.setValue({ x: 0, y: 0 });
  },
  onPanResponderMove: Animated.event([
    null,
    { dy: this.state. drag.y }
  ])
});

As you can see, we are using a "private" method __getValue() here. Generally, syncronously reading the value of an Animated.value is not recommended, because the animation may be offloaded onto the native thread and the value may not be up to date. Here, at the beginning of the gesture, it should be safe though.

As a side note, since you are only moving the element on the Y axis, you don't necessarily need to use the two-dimensional Animated.ValueXY, since a basic Animated.Value will suffice. If you refactor your code to use Value, you can just call drag.extractOffset() to reset the offset. It does the same thing, but does not appear to be available on AnimatedXY.

like image 194
jevakallio Avatar answered Oct 23 '22 21:10

jevakallio