Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Declarative future of gestures and animations in React Native

Declarative future of gestures and animations in React Native

Introducing react-native-reanimated.

Animations and touch related interactions are the secret ingredients that can make a good app an awesome app. Apparently, they are also the elements most sensitive to frame drops, which degrades the great experience they ought to provide. We will learn what the challenges are when building performant and responsive UI on mobile, and get to know how declarative APIs let us deal with these problems.

Krzysztof Magiera

May 17, 2018
Tweet

More Decks by Krzysztof Magiera

Other Decks in Technology

Transcript

  1. Animated state = { fade: new Animated.Value(0) }; 1. Create

    value <Animated.View style={{ opacity: this.state.fade }} /> 2. Hook it up as a view attribute Animated.timing( this.state.fade, { toValue: 1, } ).start(); 3. Animate value 4. Or attach it to an event 5. Combine/process values const fade = Animated.multiply( this.state.factor, this.state.scrollY.interpolate({ inputRange: [0, 300], outputRange: [1, 0] }) ); <Animated.ScrollView onScroll={Animated.event([ { // event.nativeEvent.contentOffset.y nativeEvent: { contentOffset: { y: this.state.scrollY } } } ])} >
  2. JS responder system ! <TouchableHighlight onPress={this.rowPressed}> <View style={styles.row}> <Text>Show this

    slide</Text> <TouchableOpacity onPress={this.checkboxPressed} disabled={false} style={styles.checkbox} > <Checkbox checked={this.state.checked} /> </TouchableOpacity> </View> </TouchableHighlight> Show this slide
  3. JS responder system ! <TouchableHighlight onPress={this.rowPressed}> <View style={styles.row}> <Text>Show this

    slide</Text> <TouchableOpacity onPress={this.checkboxPressed} disabled={true} style={styles.checkbox} > <Checkbox checked={this.state.checked} /> </TouchableOpacity> </View> </TouchableHighlight> Show this slide
  4. <View/> <Touchable/> <Text/> <Touchable/> <Image/> <Checkbox/> JS responder system !

    TOUCH DOWN TOUCH MOVE TOUCH MOVE TOUCH MOVE TOUCH UP JS Thread UI Thread <Image/>
  5. <View/> <Touchable/> <Text/> <Touchable/> <Image/> <Checkbox/> JS responder system !

    TOUCH DOWN TOUCH MOVE TOUCH MOVE TOUCH MOVE TOUCH UP JS Thread UI Thread <Touchable/> <Image/> <Touchable/> BUBBLING <View/> <Checkbox/>
  6. <Animated.ScrollView onScroll={Animated.event( [ { // nativeEvent.contentOffset.y nativeEvent: { contentOffset: {

    y: this.state.scrollY } } } ], { useNativeDriver: true } )} > What about gestures? this._pan = PanResponder.create({ onMoveShouldSetPanResponder: YES, onMoveShouldSetPanResponderCapture: YES, onPanResponderMove: Animated.event( [ null, { dx: this._translateValue } ], { useNativeDriver: false } ) }); DIRECT BUBBLING
  7. react-native-gesture-handler Handler Parameters Event attributes Pan minDist, minPointers translationXY, absoluteXY

    Pinch scale, focalXY, velocity Tap numberOfTaps, maxDuration numberOfPointers Rotation angle, anchorXY, velocity
  8. ‣Component for each gesture type (pan, pinch, etc) ‣Natively backed

    “touchables” ‣Declarative API for defining cross handler interactions ‣Extras: drawer, swipeable row, more to come… react-native-gesture-handler
  9. react-native-gesture-handler <RotationGestureHandler onGestureEvent={Animated.event( [{ nativeEvent: { rotation: this.rotationValue } }],

    { useNativeDriver: true } )} > <Animated.View style={animatedTransforms} /> </RotationGestureHandler>; DIRECT
  10. class Snappable extends Component { translateX = new Animated.Value(0); render()

    { return ( <PanGestureHandler> <Animated.View style={[ styles.box, { transform: [{ translateX: this.translateX }] } ]} /> </PanGestureHandler> ); } }
  11. class Snappable extends Component { translateX = new Animated.Value(0); render()

    { return ( <PanGestureHandler onGestureEvent={Animated.event( [{ nativeEvent: { translationX: this.translateX } }], { useNativeDriver: true } )}> <Animated.View style={[ styles.box, { transform: [{ translateX: this.translateX }] } ]} /> </PanGestureHandler> ); } }
  12. class Snappable extends Component { translateX = new Animated.Value(0); render()

    { return ( <PanGestureHandler onGestureEvent={Animated.event( [{ nativeEvent: { translationX: this.translateX } }], { useNativeDriver: true } )} onHandlerStateChange={event N> { if (event.nativeEvent.oldState === State.ACTIVE) { // Check if the last state was "ACTIVE" Animated.spring(this.translateX, { toValue: 0, velocity: event.nativeEvent.velocityX, useNativeDriver: true, }).start(); } }}> <Animated.View style={[ styles.box, { transform: [{ translateX: this.translateX }] } ]} /> </PanGestureHandler> ); } }
  13. ImagePreview component challenge ‣ Correctly centred pinch ‣ Pan with

    inertia ‣ Pan friction on edges ‣ Bouncing from edges ‣ Pinch friction ‣ Bouncing zoom ‣ Pinch&Pan simultaneously
  14. { // Animated this.transX = cond( eq(state, State.ACTIVE), [ stopClock(clock),

    set(transX, add(transX, sub(dragX, prevDragX))), set(prevDragX, dragX), transX, ], [ set(prevDragX, 0), set( transX, cond(defined(transX), runSpring(clock, transX, dragVX), 0) ), ] ); }
  15. Clocks export default function decay(clock, state, config) { const lastTime

    = cond(state.time, state.time, clock); const deltaTime = sub(clock, lastTime); // v0 = v / 1000 // v = v0 * powf(deceleration, dt); // v = v * 1000; // x0 = x; // x = x0 + v0 * deceleration * (1 - powf(deceleration, dt)) / (1 - deceleration) const kv = pow(config.deceleration, deltaTime); const kx = divide( multiply(config.deceleration, sub(1, kv)), sub(1, config.deceleration) ); const v0 = divide(state.velocity, 1000); const v = multiply(v0, kv, 1000); const x = add(state.position, multiply(v0, kx)); return [ set(state.position, x), set(state.velocity, v), set(state.time, clock), cond(lessThan(abs(v), VELOCITY_EPS), set(state.finished, 1)), ]; }
  16. Lower level abstraction export const diffClamp = function(a, minVal, maxVal)

    { const value = new AnimatedValue(); return set( value, min( max( add(cond(defined(value), value, a), diff(a)), minVal ), maxVal ) ); };
  17. ‣ More generic primitive node types ‣ Can be used

    to implement Animated compatible API w/o writing specific native code for things like: ‣ Complex nodes such as “diffClamp” ‣ Interactions such as animated value tracking or animation staggering ‣ Conditional evaluation & nodes with side effects ‣ Less native code & more cross platform JS code ‣ No more “useNativeDriver” – all animations runs on UI thread by default Reanimated highlights