react-native - Detect a finger move into a view
Asked Answered
T

1

9

I am researching about how to detect a finger move into a view in React Native. For example, I have a single view in screen. My finger press outside the view and slowly move to the view. The view can recognize my finger when the finger cross the border.

I researched about Gesture Responder and PanResponder. But all the examples I found are touching on the view and moving it.

If you have a way or an example code to do what I said above, please let me know.

Do i need to do some mathematical calculations to reach the destination.?

Thank you in advance!

Tingley answered 5/7, 2018 at 3:46 Comment(4)
Did you solve the problem?Krause
@ChenNi: yes, I found a way but it is quite complicated and need to be very careful when implementing. We have a Pan Responder cover the screen and the (x,y) of the rectangle. When finger press on the screen, Pan Responder will catch the (x,y) and compare with the (x,y) of rectangle. Things get extremely hard if the rectangle stay in a Flat List.Tingley
@LuongTruong can u provide more sample code?Proprietary
@famfamfam: Sorry I can't because all of the code is belong to my company. However, I still remember the idea. We have a rectangle cover the screen. When we press, we actually press on that rectangle, not the view. We also have a list of coordinate of the view. When the finger moves and (x,y) are in the view area, change the view color. Hope it can help you ^^Tingley
M
1

The previous comments by @LuongTrong touched on this, but it's not as hard as it sounds, once you get a handle on what the PanResponder does and how it works. The way you need to detect when the user drags their finger over a view is using a PanResponder, view.onLayout and view References. When the view you want to track touches into and out of loads, capture the 4 corners of the view (onLayout gives you x,y and height, width so some addition is required). Using the onPanResponderMove method, check whether or not the touchpoint is in the bounds of the view, then update the ref's state accordingly.

It sounds more complicated than it is, here's an App.js (100% demo code) that demonstrates how this works. I've broken the "button" components out to a separate component to make it more reusable.

import React, {useEffect, useState} from 'react'
import {setStatusBarBackgroundColor, StatusBar} from 'expo-status-bar'
import {PanResponder, Pressable, StyleSheet, Text, TouchableOpacity, View} from 'react-native'
import {GestureHandlerRootView, PanGestureHandler} from 'react-native-gesture-handler'
import {useEvent} from 'react-native-reanimated'

const CustButton = React.forwardRef((props, ref) => {
  const [viewLayout, setViewLayout] = useState({})
  const selColor = 'red'
  const unselectedColor = 'blue'
  const [selectedColor, setSelectedColor] = useState(unselectedColor)

  function getViewLatout() {
    return viewLayout
  }

  const updateSelectionStatus = (isActive) => {
    setSelectedColor(isActive ? selColor : unselectedColor)
  }

  const checkPoint = (x,y) => {
    const {top, left, right, bottom} = viewLayout
    return (x > left && x < right && y > top && y < bottom)
  }

  React.useImperativeHandle(ref, () => ({
    // each key is connected to `ref` as a method name
    // they can execute code directly, or call a local method
    setActive: (isActive) => { updateSelectionStatus(isActive) },
    isPointInView: (x,y) => { return checkPoint(x,y)}
  }))

  return (<View
    ref={ref}
    style={[styles.button, {backgroundColor: selectedColor}]}
    onLayout={(evt) => {

      const {x, y, height, width} = evt.nativeEvent.layout
      const viewLayoutProp = {
        top: y,
        bottom: y + height,
        right: x + width,
        left: x
      }
      setViewLayout(viewLayoutProp)
  }}>
    <Text style={{color: 'white', fontSize: 24, fontWeight: 'bold'}}>move over me</Text>
  </View>)
})

export default function App() {

  const viewRef = React.useRef()
  const viewRef2 = React.useRef()
  const viewRef3 = React.useRef()
  const checkIfAViewIsSelected = (evt) => {
    const {pageX:x, pageY:y} = evt.nativeEvent
    viewRef.current.setActive(viewRef.current.isPointInView(x,y))
    viewRef2.current.setActive(viewRef2.current.isPointInView(x,y))
    viewRef3.current.setActive(viewRef3.current.isPointInView(x,y))
  }

  const panResponder = React.useMemo(() =>
      PanResponder.create({
          onStartShouldSetPanResponder: (evt, gestureState) => true,
          onMoveShouldSetPanResponder: (evt, gestureState) => true,
          onPanResponderTerminationRequest: (evt, gestureState) => true,
          onPanResponderRelease: (evt, gestureState) => {
          },
          onPanResponderStart:(evt) => {
            checkIfAViewIsSelected(evt)
          },
          onPanResponderMove: (evt, gestureState) => {
              checkIfAViewIsSelected(evt)
          }
      }), [viewRef, viewRef2, viewRef3])

  useEffect(() => {
    if (viewRef.current == null) {
      return;
    }
  }, [viewRef.current])

  return (
    <View style={styles.container}>

          <View style={[styles.container, {backgroundColor: '#fff'}]}
                {...panResponder.panHandlers}
          >
            <CustButton ref={viewRef}  />
            <CustButton ref={viewRef2} />
            <CustButton ref={viewRef3} />

            <StatusBar style="auto"/>
          </View>
    </View>
  )
}

const styles = StyleSheet.create(
  {
   container: {
     flex: 0,
     backgroundColor: '#555',
     alignItems: 'center',
     justifyContent: 'center',
     gap: 20,
     height: '100%',
       width:'100%'
   },
   button: {
     flex: 0,
     height: 100,
     width: 250,
     backgroundColor: 'red',
     alignItems: 'center',
     justifyContent: 'center'
   }
 })

The real gotcha's in here are:

  • Getting back to the child view and calling methods on the child. You need a reference to the child, which is accomplished with forwardRef,
  • Exposing methods in the child must be done using useImperativeHandle, otherwise the ref cannot see the methods and will crash if use them.
  • Make sure your parent View includes {...panResponders.panHandlers},
  • I had to make my panHandler using useMemo instead of useRef as the examples on the react native website indicate. When using useRef, I wasn't getting any updated view references.

Hope this helps you, or someone else who happens to stumble into this post.

Mesics answered 28/9, 2023 at 20:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.