Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: movable-view 支持 tap、longpress 事件 #1770

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
*/
import { useEffect, forwardRef, ReactNode, useContext, useCallback, useRef, useMemo, createElement } from 'react'
import { StyleSheet, NativeSyntheticEvent, View, LayoutChangeEvent } from 'react-native'
import { getCustomEvent } from './getInnerListeners'
import useInnerProps, { getCustomEvent } from './getInnerListeners'
import useNodesRef, { HandlerRef } from './useNodesRef'
import { MovableAreaContext } from './context'
import { useTransformStyle, splitProps, splitStyle, HIDDEN_STYLE, wrapChildren, GestureHandler, flatGesture, extendObject } from './utils'
import { useTransformStyle, splitProps, splitStyle, HIDDEN_STYLE, wrapChildren, GestureHandler, flatGesture, extendObject, omit } from './utils'
import { GestureDetector, Gesture, GestureTouchEvent, GestureStateChangeEvent, PanGestureHandlerEventPayload, PanGesture } from 'react-native-gesture-handler'
import Animated, {
useSharedValue,
Expand All @@ -33,6 +33,7 @@ import Animated, {
useAnimatedReaction,
withSpring
} from 'react-native-reanimated'
import { collectDataset, noop } from '@mpxjs/utils'

interface MovableViewProps {
children: ReactNode;
Expand All @@ -42,17 +43,22 @@ interface MovableViewProps {
y?: number;
disabled?: boolean;
animation?: boolean;
id?: string;
bindchange?: (event: unknown) => void;
bindtouchstart?: (event: NativeSyntheticEvent<TouchEvent>) => void;
catchtouchstart?: (event: NativeSyntheticEvent<TouchEvent>) => void;
bindtouchmove?: (event: NativeSyntheticEvent<TouchEvent>) => void;
catchtouchmove?: (event: NativeSyntheticEvent<TouchEvent>) => void;
catchtouchend?: (event: NativeSyntheticEvent<TouchEvent>) => void;
bindtouchend?: (event: NativeSyntheticEvent<TouchEvent>) => void;
bindhtouchmove?: (event: NativeSyntheticEvent<TouchEvent>) => void;
bindvtouchmove?: (event: NativeSyntheticEvent<TouchEvent>) => void;
catchhtouchmove?: (event: NativeSyntheticEvent<TouchEvent>) => void;
catchvtouchmove?: (event: NativeSyntheticEvent<TouchEvent>) => void;
bindtouchstart?: (event: GestureTouchEvent) => void;
catchtouchstart?: (event: GestureTouchEvent) => void;
bindtouchmove?: (event: GestureTouchEvent) => void;
catchtouchmove?: (event: GestureTouchEvent) => void;
catchtouchend?: (event: GestureTouchEvent) => void;
bindtouchend?: (event: GestureTouchEvent) => void;
bindhtouchmove?: (event: GestureTouchEvent) => void;
bindvtouchmove?: (event: GestureTouchEvent) => void;
catchhtouchmove?: (event: GestureTouchEvent) => void;
catchvtouchmove?: (event: GestureTouchEvent) => void;
bindlongpress?: (event: GestureTouchEvent) => void;
catchlongpress?: (event: GestureTouchEvent) => void;
bindtap?: (event: GestureTouchEvent) => void;
catchtap?: (event: GestureTouchEvent) => void;
onLayout?: (event: LayoutChangeEvent) => void;
'out-of-bounds'?: boolean;
'wait-for'?: Array<GestureHandler>;
Expand Down Expand Up @@ -153,10 +159,10 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
})

const hasSimultaneousHandlersChanged = prevSimultaneousHandlersRef.current.length !== (originSimultaneousHandlers?.length || 0) ||
(originSimultaneousHandlers || []).some((handler, index) => handler !== prevSimultaneousHandlersRef.current[index])
(originSimultaneousHandlers || []).some((handler, index) => handler !== prevSimultaneousHandlersRef.current[index])

const hasWaitForHandlersChanged = prevWaitForHandlersRef.current.length !== (waitFor?.length || 0) ||
(waitFor || []).some((handler, index) => handler !== prevWaitForHandlersRef.current[index])
(waitFor || []).some((handler, index) => handler !== prevWaitForHandlersRef.current[index])

if (hasSimultaneousHandlersChanged || hasWaitForHandlersChanged) {
gestureSwitch.current = !gestureSwitch.current
Expand Down Expand Up @@ -333,57 +339,77 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
props.onLayout && props.onLayout(e)
}

const extendEvent = useCallback((e: any) => {
'worklet'
const extendEvent = useCallback((e: any, obj?: Record<string, any>) => {
const touchArr = [e.changedTouches, e.allTouches]
touchArr.forEach(touches => {
touches && touches.forEach((item: { absoluteX: number; absoluteY: number; pageX: number; pageY: number }) => {
item.pageX = item.absoluteX
item.pageY = item.absoluteY
})
})
e.touches = e.allTouches
Object.assign(e, {
touches: e.allTouches,
detail: {
x: e.changedTouches[0].absoluteX,
y: e.changedTouches[0].absoluteY
},
currentTarget: {
id: props.id || '',
dataset: collectDataset(props),
offsetLeft: 0,
offsetTop: 0
}
}, obj)
}, [])

const gesture = useMemo(() => {
const handleTriggerStart = (e: any) => {
'worklet'
extendEvent(e)
bindtouchstart && runOnJS(bindtouchstart)(e)
catchtouchstart && runOnJS(catchtouchstart)(e)
}

const handleTriggerMove = (e: any) => {
'worklet'
extendEvent(e)
const hasTouchmove = !!bindhtouchmove || !!bindvtouchmove || !!bindtouchmove
const hasCatchTouchmove = !!catchhtouchmove || !!catchvtouchmove || !!catchtouchmove
const triggerStartOnJS = ({ e }: { e: GestureTouchEvent }) => {
extendEvent(e)
bindtouchstart && bindtouchstart(e)
catchtouchstart && catchtouchstart(e)
}

if (hasTouchmove) {
if (touchEvent.value === 'htouchmove') {
bindhtouchmove && runOnJS(bindhtouchmove)(e)
} else if (touchEvent.value === 'vtouchmove') {
bindvtouchmove && runOnJS(bindvtouchmove)(e)
}
bindtouchmove && runOnJS(bindtouchmove)(e)
const triggerMoveOnJS = ({ e, hasTouchmove, hasCatchTouchmove, touchEvent }: { e: GestureTouchEvent; hasTouchmove: boolean; hasCatchTouchmove: boolean; touchEvent: string }) => {
extendEvent(e)
if (hasTouchmove) {
if (touchEvent === 'htouchmove') {
bindhtouchmove && bindhtouchmove(e)
} else if (touchEvent === 'vtouchmove') {
bindvtouchmove && bindvtouchmove(e)
}
bindtouchmove && bindtouchmove(e)
}

if (hasCatchTouchmove) {
if (touchEvent.value === 'htouchmove') {
catchhtouchmove && runOnJS(catchhtouchmove)(e)
} else if (touchEvent.value === 'vtouchmove') {
catchvtouchmove && runOnJS(catchvtouchmove)(e)
}
catchtouchmove && runOnJS(catchtouchmove)(e)
if (hasCatchTouchmove) {
if (touchEvent === 'htouchmove') {
catchhtouchmove && catchhtouchmove(e)
} else if (touchEvent === 'vtouchmove') {
catchvtouchmove && catchvtouchmove(e)
}
catchtouchmove && catchtouchmove(e)
}
}

const triggerEndOnJS = ({ e }: { e: GestureTouchEvent }) => {
extendEvent(e)
bindtouchend && bindtouchend(e)
catchtouchend && catchtouchend(e)
}

const handleTriggerEnd = (e: any) => {
const gesture = useMemo(() => {
const handleTriggerMove = (e: GestureTouchEvent) => {
'worklet'
extendEvent(e)
bindtouchend && runOnJS(bindtouchend)(e)
catchtouchend && runOnJS(catchtouchend)(e)
const hasTouchmove = !!bindhtouchmove || !!bindvtouchmove || !!bindtouchmove
const hasCatchTouchmove = !!catchhtouchmove || !!catchvtouchmove || !!catchtouchmove
if (hasTouchmove || hasCatchTouchmove) {
runOnJS(triggerMoveOnJS)({
e,
touchEvent: touchEvent.value,
hasTouchmove,
hasCatchTouchmove
})
}
}

const gesturePan = Gesture.Pan()
.onTouchesDown((e: GestureTouchEvent) => {
'worklet'
Expand All @@ -393,12 +419,14 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
x: changedTouches.x,
y: changedTouches.y
}
handleTriggerStart(e)
if (bindtouchstart || catchtouchstart) {
runOnJS(triggerStartOnJS)({ e })
}
})
.onTouchesMove((e: GestureTouchEvent) => {
'worklet'
isMoving.value = true
const changedTouches = e.changedTouches[0] || { x: 0, y: 0 }
isMoving.value = true
if (isFirstTouch.value) {
touchEvent.value = Math.abs(changedTouches.x - startPosition.value.x) > Math.abs(changedTouches.y - startPosition.value.y) ? 'htouchmove' : 'vtouchmove'
isFirstTouch.value = false
Expand Down Expand Up @@ -430,7 +458,9 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
'worklet'
isFirstTouch.value = true
isMoving.value = false
handleTriggerEnd(e)
if (bindtouchend || catchtouchend) {
runOnJS(triggerEndOnJS)({ e })
}
if (disabled) return
if (!inertia) {
const { x, y } = checkBoundaryPosition({ positionX: offsetX.value, positionY: offsetY.value })
Expand All @@ -454,8 +484,8 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
})
.onFinalize((e: GestureStateChangeEvent<PanGestureHandlerEventPayload>) => {
'worklet'
if (!inertia || disabled || !animation) return
isMoving.value = false
if (!inertia || disabled || !animation) return
if (direction === 'horizontal' || direction === 'all') {
xInertialMotion.value = true
offsetX.value = withDecay({
Expand Down Expand Up @@ -497,35 +527,45 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
}
})

const injectCatchEvent = (props: Record<string, any>) => {
const eventHandlers: Record<string, any> = {}
const catchEventList = [
{ name: 'onTouchStart', value: ['catchtouchstart'] },
{ name: 'onTouchMove', value: ['catchtouchmove', 'catchvtouchmove', 'catchhtouchmove'] },
{ name: 'onTouchEnd', value: ['catchtouchend'] }
const rewriteCatchEvent = () => {
const handlers: Record<string, typeof noop> = {}

const events = [
{ type: 'touchstart' },
{ type: 'touchmove', alias: ['vtouchmove', 'htouchmove'] },
{ type: 'touchend' }
]
catchEventList.forEach(event => {
event.value.forEach(name => {
if (props[name] && !eventHandlers[event.name]) {
eventHandlers[event.name] = (e: NativeSyntheticEvent<TouchEvent>) => {
e.stopPropagation()
}
}
})
events.forEach(({ type, alias = [] }) => {
const hasCatchEvent =
props[`catch${type}` as keyof typeof props] ||
alias.some(name => props[`catch${name}` as keyof typeof props])
if (hasCatchEvent) handlers[`catch${type}`] = noop
})
return eventHandlers

return handlers
}

const catchEventHandlers = injectCatchEvent(props)
const layoutStyle = !hasLayoutRef.current && hasSelfPercent ? HIDDEN_STYLE : {}

// bind 相关 touch 事件直接由 gesture 触发,无须重复挂载
// catch 相关 touch 事件需要重写并通过 useInnerProps 注入阻止冒泡逻辑
const filterProps = omit(props, [
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

catch的也需要过滤掉吧

'bindtouchstart',
'bindtouchmove',
'bindvtouchmove',
'bindhtouchmove',
'bindtouchend'
])

const innerProps = useInnerProps(filterProps, extendObject({
ref: nodeRef,
onLayout: onLayout,
style: [innerStyle, animatedStyles, layoutStyle]
}, rewriteCatchEvent()))

return createElement(GestureDetector, { gesture: gesture }, createElement(
Animated.View,
extendObject({
ref: nodeRef,
onLayout: onLayout,
style: [innerStyle, animatedStyles, layoutStyle]
}, catchEventHandlers),
innerProps,
wrapChildren(
props,
{
Expand Down
Loading