Skip to content
Open
2 changes: 2 additions & 0 deletions .agents/skills/mpx2rn/references/rn-template-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,7 @@ movable-view 的可移动区域。
| animation | boolean | `true` | 是否使用动画 |
| damping | number | `20` | 阻尼系数,用于控制 x 或 y 改变时的动画和过界回弹的动画,值越大移动越快 |
| friction | number | `2` | 摩擦系数,用于控制惯性滑动的动画,值越大摩擦力越大,滑动越快停止 |
| workletChange | function | | RN 环境特有属性,拖动位置变化时在 UI 线程立即触发,回调参数为 `{x, y, source}`,不受 `changeThrottleTime` 影响 |
| simultaneous-handlers | array\<object> | `[]` | RN 环境特有属性,主要用于组件嵌套场景,允许多个手势同时识别和处理并触发,这个属性可以指定一个或多个手势处理器,处理器支持使用 this.$refs.xxx 获取组件实例来作为数组参数传递给 movable-view 组件 |
| wait-for | array\<object> | `[]` | RN 环境特有属性,主要用于组件嵌套场景,允许延迟激活处理某些手势,这个属性可以指定一个或多个手势处理器,处理器支持使用 this.$refs.xxx 获取组件实例来作为数组参数传递给 movable-view 组件 |
| disable-event-passthrough | boolean | `false` | RN 环境特有属性,有时候我们希望 movable-view 在水平方向滑动,并且竖直方向的手势也希望被 movable-view 组件消费掉,不被其他组件响应,可以将这个属性设置为 true) |
Expand All @@ -769,6 +770,7 @@ movable-view 的可移动区域。

- simultaneous-handlers 为 RN 环境特有属性,具体含义可参考[react-native-gesture-handler](https://docs.swmansion.com/react-native-gesture-handler/docs/fundamentals/gesture-composition/#simultaneouswithexternalgesture)
- wait-for 为 RN 环境特有属性,具体含义可参考[react-native-gesture-handler](https://docs.swmansion.com/react-native-gesture-handler/docs/fundamentals/gesture-composition/#requireexternalgesturetofail)
- workletChange 为 RN 环境特有属性,回调会在 UI 线程执行,函数体需包含 `'worklet'` 指令,且只接收 `{x, y, source}` 参数,不是完整 Mpx 事件对象
- RN 环境 movable 相关组件暂不支持缩放能力

### image
Expand Down
2 changes: 2 additions & 0 deletions docs-vitepress/guide/rn/component.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ movable-view的可移动区域。
| animation | boolean | `true` | 是否使用动画 |
| damping | number | `20` | 阻尼系数,用于控制x或y改变时的动画和过界回弹的动画,值越大移动越快 |
| friction | number | `2` | 摩擦系数,用于控制惯性滑动的动画,值越大摩擦力越大,滑动越快停止 |
| workletChange | function | | RN 环境特有属性,拖动位置变化时在 UI 线程立即触发,回调参数为 `{x, y, source}`,不受 `changeThrottleTime` 影响 |
| simultaneous-handlers | array\<object> | `[]` | RN 环境特有属性,主要用于组件嵌套场景,允许多个手势同时识别和处理并触发,这个属性可以指定一个或多个手势处理器,处理器支持使用 this.$refs.xxx 获取组件实例来作为数组参数传递给 movable-view 组件 |
| wait-for | array\<object> | `[]` | RN 环境特有属性,主要用于组件嵌套场景,允许延迟激活处理某些手势,这个属性可以指定一个或多个手势处理器,处理器支持使用 this.$refs.xxx 获取组件实例来作为数组参数传递给 movable-view 组件 |
| disable-event-passthrough | boolean | `false` | RN 环境特有属性,有时候我们希望movable-view 在水平方向滑动,并且竖直方向的手势也希望被 movable-view 组件消费掉,不被其他组件响应,可以将这个属性设置为true) |
Expand All @@ -227,6 +228,7 @@ movable-view的可移动区域。
>
> - simultaneous-handlers 为 RN 环境特有属性,具体含义可参考[react-native-gesture-handler](https://docs.swmansion.com/react-native-gesture-handler/docs/fundamentals/gesture-composition/#simultaneouswithexternalgesture)
> - wait-for 为 RN 环境特有属性,具体含义可参考[react-native-gesture-handler](https://docs.swmansion.com/react-native-gesture-handler/docs/fundamentals/gesture-composition/#requireexternalgesturetofail)
> - workletChange 为 RN 环境特有属性,回调会在 UI 线程执行,函数体需包含 `'worklet'` 指令,且只接收 `{x, y, source}` 参数,不是完整 Mpx 事件对象
> - RN 环境 movable 相关组件暂不支持缩放能力

### image
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* ✘ scale-value
* ✔ animation
* ✔ bindchange
* ✔ workletChange
* ✘ bindscale
* ✔ htouchmove
* ✔ vtouchmove
Expand Down Expand Up @@ -146,6 +147,9 @@ const withWechatDecay = (
}, callback)
}

type ChangePayload = { x: number; y: number; type?: string }
type ChangeDetail = { x: number; y: number; source: string }

interface MovableViewProps {
children: ReactNode
style?: Record<string, any>
Expand All @@ -159,6 +163,7 @@ interface MovableViewProps {
id?: string
changeThrottleTime?:number
bindchange?: (event: unknown) => void
workletChange?: (detail: ChangeDetail) => void
bindtouchstart?: (event: GestureTouchEvent) => void
catchtouchstart?: (event: GestureTouchEvent) => void
bindtouchmove?: (event: GestureTouchEvent) => void
Expand Down Expand Up @@ -193,12 +198,40 @@ const styles = StyleSheet.create({
top: 0
}
})
const getChangeSource = (
offsetX: number,
offsetY: number,
xRange: [min: number, max: number],
yRange: [min: number, max: number],
moving: boolean,
inertialMotion: boolean,
currentSource: string
) => {
'worklet'
const hasOverBoundary = offsetX < xRange[0] || offsetX > xRange[1] ||
offsetY < yRange[0] || offsetY > yRange[1]
let source = currentSource
if (hasOverBoundary) {
if (moving) {
source = 'touch-out-of-bounds'
} else {
source = 'out-of-bounds'
}
} else {
if (moving) {
source = 'touch'
} else if (inertialMotion && (currentSource === 'touch' || currentSource === 'friction')) {
source = 'friction'
Comment thread
yandadaFreedom marked this conversation as resolved.
Outdated
}
}
return source
}

const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewProps>((movableViewProps: MovableViewProps, ref): JSX.Element => {
const { textProps, innerProps: props = {} } = splitProps(movableViewProps)
const movableGestureRef = useRef<PanGesture>()
const layoutRef = useRef<any>({})
const changeSource = useRef<any>('')
const bindChangeSource = useRef<any>('')
const hasLayoutRef = useRef(false)
const propsRef = useRef<any>({})
propsRef.current = (props || {}) as MovableViewProps
Expand Down Expand Up @@ -233,7 +266,8 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
catchtouchmove,
bindtouchend,
catchtouchend,
bindchange
bindchange,
workletChange
} = props

const {
Expand Down Expand Up @@ -270,6 +304,7 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
const touchEvent = useSharedValue<string>('')
const initialViewPosition = useSharedValue({ x: x || 0, y: y || 0 })
const lastChangeTime = useSharedValue(0)
const workletChangeSource = useSharedValue('')

const MovableAreaLayout = useContext(MovableAreaContext)

Expand All @@ -296,15 +331,21 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
prevSimultaneousHandlersRef.current = originSimultaneousHandlers || []
prevWaitForHandlersRef.current = waitFor || []

const handleTriggerChange = useCallback(({ x, y, type }: { x: number; y: number; type?: string }) => {
const handleTriggerChange = useCallback(({ x, y, type }: ChangePayload) => {
const { bindchange } = propsRef.current
if (!bindchange) return
let source = ''
if (type !== 'setData') {
source = getTouchSource(x, y)
} else {
changeSource.current = ''
}
const source = type !== 'setData'
? getChangeSource(
x,
y,
draggableXRange.value,
draggableYRange.value,
isMoving.value,
xInertialMotion.value || yInertialMotion.value,
bindChangeSource.current
)
: ''
bindChangeSource.current = source
bindchange(
getCustomEvent('change', {}, {
detail: {
Expand All @@ -317,6 +358,23 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
)
}, [])

const handleTriggerWorkletChange = useCallback(({ x, y, type }: ChangePayload) => {
'worklet'
const source = type !== 'setData'
? getChangeSource(
x,
y,
draggableXRange.value,
draggableYRange.value,
isMoving.value,
xInertialMotion.value || yInertialMotion.value,
workletChangeSource.value
)
: ''
workletChangeSource.value = source
workletChange && workletChange({ x, y, source })
}, [workletChange])

useEffect(() => {
runOnUI(() => {
if (offsetX.value !== x || offsetY.value !== y) {
Expand All @@ -331,6 +389,13 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
? withWechatSpring(newY, damping)
: newY
}
if (workletChange) {
handleTriggerWorkletChange({
x: newX,
y: newY,
type: 'setData'
})
}
if (bindchange) {
runOnJS(runOnJSCallback)('handleTriggerChange', {
x: newX,
Expand All @@ -349,27 +414,6 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
}
}, [MovableAreaLayout.height, MovableAreaLayout.width])

const getTouchSource = useCallback((offsetX: number, offsetY: number) => {
const hasOverBoundary = offsetX < draggableXRange.value[0] || offsetX > draggableXRange.value[1] ||
offsetY < draggableYRange.value[0] || offsetY > draggableYRange.value[1]
let source = changeSource.current
if (hasOverBoundary) {
if (isMoving.value) {
source = 'touch-out-of-bounds'
} else {
source = 'out-of-bounds'
}
} else {
if (isMoving.value) {
source = 'touch'
} else if ((xInertialMotion.value || yInertialMotion.value) && (changeSource.current === 'touch' || changeSource.current === 'friction')) {
source = 'friction'
}
}
changeSource.current = source
return source
}, [])

const setBoundary = useCallback(({ width, height }: { width: number; height: number }) => {
const top = (style.position === 'absolute' && style.top) || 0
const left = (style.position === 'absolute' && style.left) || 0
Expand Down Expand Up @@ -591,8 +635,13 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
offsetY.value = applyBoundaryDecline(newY, draggableYRange.value)
}
Comment thread
yandadaFreedom marked this conversation as resolved.
}
if (workletChange) {
handleTriggerWorkletChange({
x: offsetX.value,
y: offsetY.value
})
}
if (bindchange) {
// 使用节流版本减少 runOnJS 调用
handleTriggerChangeThrottled({
x: offsetX.value,
y: offsetY.value
Expand Down Expand Up @@ -625,6 +674,12 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
? withWechatSpring(y, damping)
: y
}
if (workletChange) {
handleTriggerWorkletChange({
x,
y
})
}
if (bindchange) {
runOnJS(runOnJSCallback)('handleTriggerChange', {
x,
Expand All @@ -643,6 +698,12 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
friction,
() => {
xInertialMotion.value = false
Comment thread
yandadaFreedom marked this conversation as resolved.
Outdated
if (workletChange) {
handleTriggerWorkletChange({
x: offsetX.value,
y: offsetY.value
})
}
if (bindchange) {
runOnJS(runOnJSCallback)('handleTriggerChange', {
x: offsetX.value,
Expand All @@ -661,6 +722,12 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
friction,
() => {
yInertialMotion.value = false
if (workletChange) {
handleTriggerWorkletChange({
x: offsetX.value,
y: offsetY.value
})
}
if (bindchange) {
runOnJS(runOnJSCallback)('handleTriggerChange', {
x: offsetX.value,
Expand Down Expand Up @@ -733,7 +800,8 @@ const _MovableView = forwardRef<HandlerRef<View, MovableViewProps>, MovableViewP
'catchtouchmove',
'catchvtouchmove',
'catchhtouchmove',
'catchtouchend'
'catchtouchend',
'workletChange'
])

const innerProps = useInnerProps(
Expand Down
Loading