问题
Goal
Cancel custom button's onResponderRelease()
when the user's finger moves off of it. Is there a standard way of doing this that I'm not finding online?
What I've tried
Using onLayout
to grab the component's position and the onResponderMove
's GestureResponderEvent
's pageX/pageY to calculate the click area. However, onLayout only gets the position relative to the parent, so this doesn't work well with modals.
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>
)
Other thoughts
I have also considered using a useRef
and the ref's measure
. This might work. A component from another library that I'm using doesn't seem to have have measure
on its ref, so my concern is that this may not be the most flexible solution.
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>
)
回答1:
Using measure()
in the following way seems to work for me.
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>
)
Update
After refactoring my components a bit, and having never tried it before, I decided to publish a package. Hopefully, it helps someone else: react-native-better-buttons.
I ended up with a simpler hook that just needs to be put on the component's ref
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 }
}
来源:https://stackoverflow.com/questions/63767623/cancel-button-click-when-finger-is-dragged-off