当手指被拖离时取消按钮点击

Cancel button click when finger is dragged off

目标

当用户的手指离开自定义按钮时取消 onResponderRelease()。有没有我在网上找不到的标准方法?

我试过的

利用onLayout抓取组件的位置,利用onResponderMoveGestureResponderEvent的pageX/pageY计算点击面积。但是,onLayout 只获取相对于父级的位置,因此这不适用于模态。

  const handleMove = (e: GestureResponderEvent) => {
    const { pageX, pageY } = e.nativeEvent
    if (pageX < minX || pageX > maxX || pageY < minY || pageY > maxY) {
      setCancelClick(true)
      setLongPressed(false)
      setPressed(false)
    }
  }

  const setComponentDimensions = ({ nativeEvent }: LayoutChangeEvent) => {
    const { x, y, width, height } = nativeEvent.layout
    setMinX(x - BUFFER)
    setMaxX(x + width + BUFFER)
    setMinY(y - BUFFER)
    setMaxY(y + height + BUFFER)
  }

  return (
    <View
      style={[style, pressed && pressedStyle]}
      onStartShouldSetResponder={() => true}
      onResponderGrant={() => setPressed(true)}
      onResponderRelease={handleRelease}
      onResponderMove={handleMove}
      onLayout={setComponentDimensions}
      onResponderTerminationRequest={() => false}
      {...ViewProps}>
      {children}
    </View>
  )

其他想法

我也考虑过使用 useRef 和裁判的 measure。这可能有效。我正在使用的另一个库中的组件在其引用中似乎没有 measure,所以我担心这可能不是最灵活的解决方案。

  const viewRef = useRef(null as View | null)

  useEffect(() => {
    if (viewRef.current) {
      // TODO: base calculations off of measure
      console.log(viewRef.current.measure)
    }
  }, [viewRef.current])

  ...

  return (
    <View
      ref={(view) => (viewRef.current = view)}
      ...
      {children}
    </View>
  )

按以下方式使用 measure() 似乎对我有用。

  const viewRef = useRef(null as View | null)
  const [dimensions, setDimensions] = useState({
    minX: 0,
    maxX: 0,
    minY: 0,
    maxY: 0,
  })

  const handleMove = (e: GestureResponderEvent) => {
    const { pageX, pageY } = e.nativeEvent
    const { minX, maxX, minY, maxY } = dimensions

    if (pageX < minX || pageX > maxX || pageY < minY || pageY > maxY) {
      setLongPressed(false)
      setPressed(false)
    }
  }

  const setComponentDimensions = ({ nativeEvent }: LayoutChangeEvent) => {
    const { width, height } = nativeEvent.layout
    if (viewRef.current) {
      viewRef.current.measure((_a, _b, _width, _height, px, py) => {
        setDimensions({
          minX: px - BUFFER,
          maxX: px + width + BUFFER,
          minY: py - BUFFER,
          maxY: py + height + BUFFER,
        })
      })
    }
  }

  return (
    <View
      ref={(view) => (viewRef.current = view)}
      onStartShouldSetResponder={() => true}
      onResponderMove={handleMove}
      onLayout={setComponentDimensions}
      onResponderTerminationRequest={() => false}
      {children}
    </View>
  )

更新

在对我的组件进行了一些重构之后,之前从未尝试过,我决定发布一个包。希望它能帮助其他人:react-native-better-buttons.

我最终得到了一个更简单的钩子,它只需要放在组件的引用上

const useMeasurement = (buffer = 0) => {
  const ref = useRef<View | null>(null)
  const [dimensions, setDimensions] = useState({
    minX: 0,
    maxX: 0,
    minY: 0,
    maxY: 0,
  })

  /* This goes on the view's ref property */
  const setRef = (view: View) => (ref.current = view)

  /* 
     Updating ref.current does not cause rerenders, which is fine for us.
     We just want to check for ref.current updates when component that uses
     us rerenders.
  */
  useEffect(() => {
    if (ref.current && ref.current.measure) {
      ref.current.measure((_a, _b, width, height, px, py) => {
        setDimensions({
          minX: px - buffer,
          maxX: px + width + buffer,
          minY: py - buffer,
          maxY: py + height + buffer,
        })
      })
    }
  }, [ref.current])

  return { dimensions, setRef }
}