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

React Native Touch & Gesture

React Native Touch & Gesture

React Native allows for building great native apps by utilising native mobile components. Apparently handling touch and gestures is quite tricky as it often needs to interact with the application code which runs asynchronously in the javascript VM. This talk is going to be about how the gesture system has been designed and implemented in React Native and how to use it to stay out of trouble.

Krzysztof Magiera

April 21, 2017
Tweet

More Decks by Krzysztof Magiera

Other Decks in Programming

Transcript

  1. React Native Touch & Gesture Krzysztof Magiera (@kzzzf) krzys@swmansion.com

  2. • Handling touch in JS • Native gesture recognizers •

    Problems • Solutions
  3. TOUCHABLE <TouchableHighlight /> <TouchableOpacity /> <TouchableWithoutFeedback /> <TouchableNativeFeedback />

  4. TOUCHABLE <TouchableHighlight onPress={() => console.log('!!')}/> <TouchableOpacity onPress={() => console.log('!!')}/> <TouchableWithoutFeedback

    onPress={() => console.log('!!')}/> <TouchableNativeFeedback onPress={() => console.log('!!')}/>
  5. TOUCHABLE <TouchableHighlight /> <TouchableOpacity /> <TouchableWithoutFeedback /> <TouchableNativeFeedback />

  6. JS RESPONDER SYSTEM TOUCH DOWN TOUCH MOVE TOUCH MOVE TOUCH

    MOVE TOUCH UP JS Thread UI Thread <View/> <Touchable/> <View/> <View/> <Touchable/> <Touchable/> <Touchable/>
  7. JS RESPONDER SYSTEM TOUCH DOWN TOUCH MOVE TOUCH MOVE TOUCH

    MOVE TOUCH UP JS Thread UI Thread <View/> <Touchable/> <View/> <View/> <Touchable/> <Touchable/> <Touchable/>
  8. JS RESPONDER SYSTEM onStartShouldSetResponder onMoveShouldSetResponder Become a responder onResponderGrant onResponderReject

    onResponderMove onResponderRelease https://facebook.github.io/react-native/docs/gesture-responder-system.html
  9. PAN RESPONDER gestureState = { dx, dy, vx, vy, ...

    }
  10. SWITCH

  11. ScrollView is your friend

  12. COLLAPSABLE HEADERS

  13. COLLAPSABLE HEADERS const yOffset = new Animated.Value(0) const translateY =

    yOffset.interpolate({ inputRange: [0, IMG_HEIGHT - NAVBAR_HEIGHT, IMG_HEIGHT], outputRange: [0, 0, NAVBAR_HEIGHT], })
  14. COLLAPSABLE HEADERS const yOffset = new Animated.Value(0) const scale =

    yOffset.interpolate({ inputRange: [-IMG_HEIGHT, 0, 1], outputRange: [2, 1, 1], }) https://gist.github.com/kmagiera/883c991e613de681e137dd01a25ae58f
  15. COLLAPSABLE HEADERS const yOffset = new Animated.Value(0) const scale =

    yOffset.interpolate({ inputRange: [-IMG_HEIGHT, 0, 1], outputRange: [2, 1, 1], }) const translateY = yOffset.interpolate({ inputRange: [-2, 0, IMG_HEIGHT - NAVBAR_HEIGHT, IMG_HEIGHT], outputRange: [-1, 0, 0, NAVBAR_HEIGHT], }) https://gist.github.com/kmagiera/883c991e613de681e137dd01a25ae58f
  16. COLLAPSABLE HEADERS const yOffset = new Animated.Value(0) const scale =

    yOffset.interpolate({ inputRange: [-IMG_HEIGHT, 0, 1], outputRange: [2, 1, 1], }) const translateY = yOffset.interpolate({ inputRange: [-2, 0, IMG_HEIGHT - NAVBAR_HEIGHT, IMG_HEIGHT], outputRange: [-1, 0, 0, NAVBAR_HEIGHT], }) animatedHeaderStyles = { transform: [{ translateY }, { scale }] } onScrollHandler = Animated.event( [{ nativeEvent: { contentOffset: { y: yOffset } } }], { useNativeDriver: true } ) <Animated.ScrollView onScroll={onScrollHandler}> ... <Animated.Image style={[styles.header, animatedHeaderStyles]} source={IMG_SRC} /> </Animated.ScrollView>
  17. INTERACTIVE DRAWER

  18. INTERACTIVE DRAWER

  19. INTERACTIVE DRAWER

  20. INTERACTIVE DRAWER

  21. INTERACTIVE DRAWER

  22. INTERACTIVE DRAWER https://gist.github.com/kmagiera/710460dac3ba722bb6a8d552c343b44a

  23. Problems

  24. SCROLL & BUTTON

  25. GESTURE RECOGNITION TOUCH DOWN TOUCH MOVE TOUCH MOVE TOUCH MOVE

    TOUCH UP RECOGNIZED !
  26. GESTURE RECOGNITION TOUCH DOWN TOUCH MOVE TOUCH MOVE TOUCH MOVE

    TOUCH UP UI Thread JS Thread
  27. GESTURE RECOGNITION TOUCH DOWN TOUCH MOVE TOUCH MOVE TOUCH MOVE

    TOUCH UP UI Thread JS Thread ! CANCEL
  28. GESTURE RECOGNITION TOUCH DOWN TOUCH MOVE TOUCH MOVE TOUCH MOVE

    TOUCH UP UI Thread JS Thread ! !
  29. Problem #1 Gesture recognition logic is distributed between threads that

    run in parallel
  30. VIEWS WITH NATIVE GESTURE RECOGNIZERS https://gist.github.com/kmagiera/b888b5c71dccbba4e9a97cc12e7c0013

  31. VIEWS WITH NATIVE GESTURE RECOGNIZERS https://gist.github.com/kmagiera/ab1ca0831bb42aa6927e68a4e5ede7d0

  32. Problem #2 Lack of an API that would allow for

    defining interactions between native gesture recognizers
  33. NATIVE DRIVER onScrollHandler = Animated.event( [{ nativeEvent: { contentOffset: {

    y: yOffset } } }], { useNativeDriver: true } )
  34. NATIVE DRIVER onScrollHandler = Animated.event( [{ nativeEvent: { contentOffset: {

    y: yOffset } } }], // { useNativeDriver: true } )
  35. NATIVE DRIVER UI Thread JS Thread SCROLL updateTranslation updateScale

  36. NATIVE DRIVER const translateX = new Animated.Value(0) const translateY =

    new Animated.Value(0) const moveEvent = Animated.event( [{}, { dx: translateX, dy: translateY }], ) const animatedStyles = { transform: [ { translateX }, { translateY }, ] } const panResponder = PanResponder.create({ ... onPanResponderMove: moveEvent, onPanResponderRelease: handlePanResponderEnd, })
  37. NATIVE DRIVER const handlePanResponderEnd = (e, gestureState) => { //

    Merge value and offset for each animated node and // then store it under offset keeping value set // to 0 and ready for next pan move events translateX.flattenOffset() translateX.extractOffset() translateY.flattenOffset() translateY.extractOffset() } <Animated.View style={[styles.circle, animatedStyles]} {...panResponder.panHandlers} > <Image source={IMG_SRC} style={styles.image} /> </Animated.View>
  38. NATIVE DRIVER https://gist.github.com/kmagiera/a147d437236094c4b3818df6b8824fcb

  39. Problem #3 Touch events recognized by JS responder system cannot

    be connected with native animated nodes
  40. INTERACTABLE https://github.com/wix/react-native-interactable • Supports pan gesture recognition in native •

    Works with Animated Native Driver • Provides very easy to use API for building spring-based interactions
  41. GESTURE HANDLER https://github.com/kmagiera/react-native-gesture-handler <TapGestureHandler /> <PanGestureHandler /> <LongPressGestureHandler /> <NativeViewGestureHandler

    /> Components States UNDETERMINED FAILED BEGAN CANCELLED ACTIVE END Handlers onHandlerStateChange onGestureEvent Animated Native Driver Approved
  42. GESTURE HANDLERS INTERACTION DOWN, MOVE, MOVE, MOVE, UP DOWN, MOVE,

    MOVE, MOVE, UP Single Tap Double Tap !
  43. GESTURE HANDLERS INTERACTION DOWN, MOVE, MOVE, MOVE, UP DOWN, MOVE,

    MOVE, MOVE, UP Single Tap Double Tap ! DOWN, MOVE, MOVE, MOVE, MOVE, UP !
  44. GESTURE HANDLERS INTERACTION DOWN, MOVE, MOVE, MOVE, UP DOWN, MOVE,

    MOVE, MOVE, UP Single Tap Double Tap ! DOWN, MOVE, MOVE, MOVE, MOVE, UP !
  45. GESTURE HANDLERS INTERACTION shouldCancelWhenOutside shouldCancelOthersWhenActivated shouldBeRequiredByOthersToFail hitSlop (works for native

    views such as ScrollView or Slider as well)
  46. render() { return ( <LongPressGestureHandler minDurationMs={2000}> <TapGestureHandler shouldCancelWhenOutside={true}> <PanGestureHandler minDeltaX={50}>

    <Animated.View style={...}/> </PanGestureHandler> </TapGestureHandler> </LongPressGestureHandler> ) } GESTURE HANDLER
  47. ENJOY

  48. Thank you! Krzysztof Magiera krzys@swmansion.com @kzzzf