The typical use of decay
is to take something that may be moving at a particular velocity and apply deceleration to it. One such use case is throwing a card. The finger will cause the card to move at a sepcific velocity. We can use the velocity, apply it to our decay
and bring the card to it's final position and make it look like a real flick happened.
Without decay
, when someone flicks to release a card, it would just stop wherever they last touched with their fingers. Most commonly, this will be used with PanResponder
and Animated.ValueXY
.
this._animatedValue = new Animated.ValueXY(); Animated.decay(this._animatedValue, { // coast to a stop velocity: { x: gestureState.vx, y: gestureState.vy }, // velocity from gesture release deceleration: 0.997, });
What does it look like in a real life example? Well thanks to Brent Vatne you can check out some real life code here https://github.com/brentvatne/react-native-animated-demo-tinder/blob/master/index.ios.js#L47.
this._panResponder = PanResponder.create({ onMoveShouldSetResponderCapture: () => true, onMoveShouldSetPanResponderCapture: () => true, onPanResponderGrant: (e, gestureState) => { this.state.pan.setOffset({ x: this.state.pan.x._value, y: this.state.pan.y._value, }); this.state.pan.setValue({ x: 0, y: 0 }); }, onPanResponderMove: Animated.event([ null, { dx: this.state.pan.x, dy: this.state.pan.y }, ]), onPanResponderRelease: (e, { vx, vy }) => { this.state.pan.flattenOffset(); let velocity; if (vx >= 0) { velocity = clamp(vx, 3, 5); } else if (vx < 0) { velocity = clamp(vx * -1, 3, 5) * -1; } if (Math.abs(this.state.pan.x._value) > SWIPE_THRESHOLD) { Animated.decay(this.state.pan, { velocity: { x: velocity, y: vy }, deceleration: 0.98, }).start(this._resetState); } else { Animated.spring(this.state.pan, { toValue: { x: 0, y: 0 }, friction: 4, }).start(); } }, });
Note: It would be much better to NOT access the Animated.Value's _value
directly as in the example. This is synchronous access and can lead to performance issues. A much better approach would be to attach an event listener. However, for the sake of this example, this will work.
The main part to look at is this function:
onPanResponderRelease: (e, {vx, vy}) => {
On the release we the second argument is the ending gestureState
, we use destructing to pop off the velocities.
if (Math.abs(this.state.pan.x._value) > SWIPE_THRESHOLD) { Animated.decay(this.state.pan, { velocity: { x: velocity, y: vy }, deceleration: 0.98, }).start(this._resetState); }
If the user move the card passed a certain threshold (left or right) then we'll continue to animate the card until it stops. This allows you to have the card movement continue as if the user threw the card.