Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'
}
}
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)
}
}
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
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