资讯详情

资讯详情

HarmonyOS 7 SlideDrop:坐标变换与触点重映射

前两篇已经把 SlideDrop 的两条基础链路做稳了。01 解决“碰到哪个窗口、窗口里的哪个位置”最终把tap_20261002_01锁定到slot_0302 则把素材封装、UDMF 多记录、幂等提交和重复触碰去重串起来让seq31只提交一次seq32在 800ms 窗口里被明确拒绝。第三篇继续同一个slidedrop_board_01但我故意把接收端变复杂。这次 Board 不再以 100% 比例静止在窗口里而是缩放到 125% 横向滚动 96vp 纵向滚动 60vp BoardRect 重新布局系统侧仍然给到一个窗口级触点812 / 260如果继续用第一篇的touch - boardRect直接命中 slot结果一定会偏。所以 03 真正要解决的是精准分享提供的是窗口坐标SlideDrop 需要的是当前业务画布坐标。两者中间必须有一条可解释、可回归的坐标变换链。本轮固定数据taskId: tap_20261002_03 targetWindowId: slidedrop_board_01 viewport: 1200 × 750vp boardRect: x180 y84 w900 h590 scale: 1.25 scrollOffset: x96 y60 touchPoint: x812 y260 localPoint: x632 y176 scrolledPoint: x728 y236 logicalPoint: x582.4 y188.8 normalized: x0.809 y0.400 geometryVersion: 17 targetSlot: slot_02 payload: image_20261002_19.webp payloadSize: 3.2MB transformCost: 8ms status: REMAPPED_LOCKED一、第一篇的坐标算法在缩放以后为什么会错01 的 Board 没有缩放也没有滚动。那时window coordinate → 减去 BoardRect → local coordinate → DropZone足够。现在 Board 被放大到1.25x并且 Scroll 容器已经滚动96 / 60如果仍然只做localX 812 - 180 localY 260 - 84得到632 / 176这个值只是“触点在当前可视 Board 外框里的位置”不是逻辑画布坐标。真正要落槽还要把滚动偏移加回去再除以缩放比例。二、坐标变换必须固定顺序当前 SlideDrop 的顺序是Window Coordinate → Board Local → Scroll Offset → ÷ Scale → Logical Board Coordinate → DropZoneResolver代码exportinterfaceTransformInput{touchX:numbertouchY:numberboardX:numberboardY:numberscrollX:numberscrollY:numberscale:number}exportfunctiontoLogicalPoint(input:TransformInput):{localX:numberlocalY:numberlogicalX:numberlogicalY:number}{constlocalXinput.touchX-input.boardXconstlocalYinput.touchY-input.boardYconstscrolledXlocalXinput.scrollXconstscrolledYlocalYinput.scrollYreturn{localX,localY,logicalX:scrolledX/input.scale,logicalY:scrolledY/input.scale}}本轮local: 632 / 176 scroll: 728 / 236 ÷ 1.25: 582.4 / 188.8最终 logical point 落在slot_02。三、为什么滚动偏移是“加回去”不是减掉这个地方最容易写反。页面已经向右滚动 96vp相当于用户当前看到的是逻辑画布更靠右的一段。窗口触点632表示当前可视区域里的位置。要还原到完整逻辑画布632 96 728然后再除以 1.25。如果写成632 - 96命中结果会往左漂。所以我没有把这段换算散在页面里而是集中放进CoordinateTransformCoordinator。坐标方向一旦固定后面所有投递入口都复用。四、Geometry Version 解决“布局刚变化事件就到了”的竞争真正跑起来以后还有一个比数学更麻烦的问题BoardRect 更新了 DropZone 列表还没更新 触碰事件到了如果 Board 用 version 17Zones 还是 version 16计算本身没错几何关系却已经不一致。所以BoardGeometryStore改成带版本的完整快照exportinterfaceBoardGeometrySnapshot{version:numberboardRect:{x:numbery:numberwidth:numberheight:number}scale:numberscrollOffset:{x:numbery:number}zones:DropZone[]}只要 Board、Scale、Scroll、Zones 任意一项变化就生成新的 version。本轮geometryVersion17解析过程中禁止混用两个版本。五、事件锁定目标时要把 geometryVersion 一起保存TARGET_LOCKED不应该只保存slot_02而应该保存slot_02 geometryVersion17 logicalPoint582.4/188.8原因是后续传输可能花 100ms 以上。这期间用户如果又缩放一次 Board17 → 18Commit 前就知道当前目标上下文已经变化。这时不能直接沿用旧结果。可以重新解析也可以拒绝旧投递具体产品策略后面再定但至少上下文变化是可检测的。六、normalized 这次换成逻辑画布比例第一篇 normalized 用的是整个窗口touchX / viewportWidth touchY / viewportHeight03 开始我更关心业务画布里的比例位置。当前逻辑 Board 约为720 × 472所以582.4 / 720 ≈ 0.809 188.8 / 472 ≈ 0.400得到0.809 / 0.400这个值用于日志 回归 跨尺寸比较但仍然不会替代真实 DropZone 几何。七、DropZone 本身也要定义在逻辑坐标系里如果 slot 的 Rect 直接使用屏幕像素那么缩放一次就要重写所有 slot。所以从第三篇开始DropZone Rect统一定义在 Logical Board Coordinate。例如constzones:DropZone[][{id:slot_01,rect:{x:0,y:0,width:360,height:236}},{id:slot_02,rect:{x:360,y:0,width:360,height:236}},{id:slot_03,rect:{x:0,y:236,width:360,height:236}},{id:slot_04,rect:{x:360,y:236,width:360,height:236}}]当前 logical point582.4 / 188.8命中slot_02八、缩放手势和触碰事件不能同时改 Geometry还有一种竞争用户正在 pinch zoom 精准碰一碰事件同时到达如果 Scale 正在 1.23、1.24、1.25 连续变化哪个才是投递时真正的画布QuickDrop 当前规则缩放进行中 → Geometry 状态 TRANSFORMING 缩放稳定 → Geometry version 1 → READYPrecise Share 事件只有在READY时才做最终锁定。否则返回GEOMETRY_TRANSFORMING让用户稍后重新碰。比在动画中间猜一个比例更可靠。九、滚动同样需要稳定快照Scroll 高频回调很多。如果每一帧都生成一个 geometryVersion日志会被刷爆。所以 Scroll 只更新内存中的 transient offset在scroll stop或者短时间稳定后再提交新的 GeometrySnapshot。本轮最终scroll96/60 version17是稳定态。这条思路和 QuickDock 里“DragMove 不持久化DragEnd 才写入”很像。十、DevEco 图必须把变换链完整打印出来开发图HiLogtaskIdtap_20261002_03 windowTouch 812 / 260 boardRect 180 / 84 / 900 / 590 local 632 / 176 scroll 96 / 60 scrolled 728 / 236 scale 1.25 logical 582.4 / 188.8 normalized 0.809 / 0.400 geometryVersion 17 targetSlot slot_02 transformCost 8ms status REMAPPED_LOCKED只打印最终slot_02不够。坐标链一旦出错需要知道到底是BoardRect 错 Scroll 错 Scale 错 Zone 错十一、运行图重点不是“slot_02 发亮”而是所有中间值一致最终运行图同一套数据window: 812/260 local: 632/176 scrolled: 728/236 logical: 582.4/188.8 slot: slot_02底部流程把每一步都展示出来。最终状态REMAPPED_LOCKED它表示的不只是“命中一个槽位”而是在 125% 缩放和 96/60 滚动之后系统窗口触点仍然能准确映射到业务画布里的同一个语义位置。十二、布局尺寸改变后不复用旧 BoardRect这一篇还模拟了一次窗口变窄。只要onAreaChange或对应布局测量结果变化BoardGeometryStore 就会提交新快照。旧 version 不再用于新事件。当前工程原则一次解析 只读取一次 snapshot 解析中途 不再次读 Store这样不会出现上半段用 version17 下半段用 version18这种最难排查的混合状态。十三、边界触点继续采用唯一归属规则缩放以后边界问题更明显。逻辑坐标如果刚好是x360会落在左右两个 slot 分界。SlideDrop 仍采用左闭右开 上闭下开Board 最右、最下边界例外。这条规则统一放进 Resolver不让每个 DropZone 自己写。十四、03 的 8ms 只代表坐标变换成本transformCost8ms包含读取 GeometrySnapshot 窗口到局部坐标 滚动补偿 缩放逆变换 DropZone 命中 锁定状态不包含文件读取 跨设备传输 Board Commit性能口径继续和 01、02 保持分段。后面 06 才会把target resolve payload pack transfer commit分别统计成完整链路。十五、坐标算法必须允许单元测试第三篇的 Transform 函数刻意写成纯函数。输入touch board scroll scale输出logical不直接依赖 UI 组件。这样可以固定测试scale1 scroll0 scale1.25 scroll96/60 scale0.8 scroll0/200精准投递的核心数学如果只能靠实机手工碰维护成本会很高。十六、缩放为 0 或异常值要直接拒绝虽然正常 UI 不会出现scale0但 Coordinator 仍然做防御if(snapshot.scale0){return{ok:false,reason:INVALID_SCALE}}不能让坐标变换产生Infinity NaN再进入 DropZoneResolver。业务边界越早拦后面状态越干净。十七、为什么 03 没有直接把图片提交进去因为这一篇的主问题是坐标是否还准确image_20261002_19.webp / 3.2MB只是统一测试素材。只要REMAPPED_LOCKED成立02 的 UDMF Commit 链可以继续复用。这就是连载连续性的意义01 坐标基础 02 提交基础 03 升级坐标变换而不是每篇重新写一套 Demo。十八、第三篇最后固定八组回归第一组scale1 / scroll0结果和 01 一致。第二组scale1.25 / scroll96/60命中 slot_02。第三组scale0.8坐标逆变换正确。第四组Geometry version 不一致拒绝解析。第五组缩放进行中返回GEOMETRY_TRANSFORMING。第六组触点在 Board 外返回OUTSIDE_BOARD。第七组边界点按唯一归属规则只命中一个 slot。第八组窗口尺寸变化后旧 snapshot 不再被新事件复用。全部通过后REMAPPED_LOCKED才算成立。十九、下一篇坐标已经准确真正难题变成“大文件别一次全读”04 会把素材从3.2MB WebP换成84.6MB 视频目标仍然是 SlideDrop。但接收端不会先把完整视频全部读进内存再开始传。我会把UDMF 元数据 预览图 文本 真实大文件拆开让 Board 先拿到可展示信息再按需读取真实文件。同时故意在第 9 个分片制造一次失败从 32MB checkpoint 继续恢复。二十、坐标链里还要处理“页面缩放”和“内容缩放”不是一回事实际页面里有两种看起来很像的缩放。一种是整个页面因为窗口尺寸变化而重新布局另一种是 Board 自己的内容缩放例如用户双指把素材板放到 125%。前者改变的是boardRect viewport后者改变的是scale logical board如果把两个值混在一起就会出现一个很隐蔽的问题页面变窄以后开发者为了“看起来缩小了”直接把boardRect.width当成业务 scale。这样触点换算虽然能得出数字真正逻辑画布的几何却已经被破坏。所以 GeometrySnapshot 里明确保留viewport geometry board display rect business scale scroll offset四个层级。窗口缩放只更新显示矩形用户缩放才更新业务 scale。二十一、嵌套滚动容器不能只记一层 scrollOffset第三篇当前 Board 只在一层 Scroll 里因此96 / 60就够用。真正产品里常见的结构是页面整体纵向 Scroll → Board 横向 Scroll → Board 内部画布 Pan如果未来 SlideDrop 加工具栏和侧边资源区只保存最内层 offset 一定会出错。所以 Coordinator 没有把 scroll 定义成两个裸数字而是准备成 TransformChainexportinterfaceScrollTransform{x:numbery:numbersource:string}exportinterfaceTransformChain{scrolls:ScrollTransform[]scale:number}计算时按容器层级顺序累加。当前只有BoardScroll 96 / 60以后增加页面 Scroll不需要推倒整个接口。二十二、GeometrySnapshot 应该是不可变对象精准投递事件到达以后我不会让 Resolver 继续引用一个会被 UI 实时修改的对象。而是const snapshot geometryStore.snapshot()拿到一份不可变拷贝。整个 8ms 解析过程都使用它。这样即使用户正在继续滚动画布新事件会得到新 version旧事件不会在中途被新数据污染。这是并发和 UI 高频变化场景里很重要的工程习惯一次业务判断只消费一次稳定快照。二十三、坐标数值还要统一 vp / px 口径跨设备开发里最容易出现的错误之一是某一层返回 px另一层以为是 vp。第三篇当前所有业务几何明确统一成vp系统事件如果不是同一口径Adapter 必须在进入PreciseShareEvent前完成转换。业务层不再判断这个值到底是 px 还是 vpHiLog 也会在字段名或文档里明确口径。坐标算法本身不难最难的是单位混用时产生的“看起来只偏几十像素”的问题。二十四、投递命中以后还要保留视觉反馈延迟预算用户碰到 slot_02 后真正跨设备文件传输可能还没开始。但目标高亮必须尽快出现。所以 03 的 8ms 解析完成以后先更新slot highlight TARGET_LOCKED再进入后续 UDMF / Transfer。这条顺序让“精准”先被用户感知到。如果等文件传完才高亮目标用户会觉得我刚才到底碰中了没有坐标准确是一部分反馈时机同样是交互质量的一部分。二十五、Geometry 错误要保留原始输入方便复盘解析失败时SlideDrop 不只记录NO_DROP_ZONE还会保留windowTouch boardRect scale scroll version因为坐标类问题非常依赖现场。如果日志只剩一个错误码下一次页面布局已经变化几乎无法复现。最终诊断记录会做脱敏和大小控制但工程调试阶段必须能还原当时的 TransformChain。二十六、触点在动画过程中到达时我更愿意“拒绝一次”而不是“猜一次”这是这一篇比较明确的产品取舍。页面正在执行缩放动画或布局过渡时GeometryStateTRANSFORMING精准分享事件如果到达SlideDrop 返回可重试提示。不会取动画中间某一帧的 geometry 去猜。原因是“精准投递”最重要的是结果可预测。一次让用户重新碰比把素材错误插入另一个槽位再让用户手动撤销更可接受。二十七、第三篇完成后坐标层已经可以独立复用现在 SlideDrop 的位置处理已经不依赖“碰一碰”本身。只要输入window coordinate geometry snapshot同一套 Transform DropZoneResolver 也可以服务鼠标拖拽 手写笔投递 跨设备拖拽 普通本地拖拽这就是为什么我没有把代码命名成TapToShareSlotResolver而使用更通用的坐标变换语义。精准碰一碰只是目前第一个数据入口。二十八、03 最终验收里我还会看 100 次随机点命中率除了固定八组测试我还会在逻辑 Board 内生成 100 个可预测测试点。每个点预先知道属于哪个 zone。用相同的scale scroll boardRect经过正向变换生成窗口触点再走逆向解析。最终要求100 / 100回到原 zone。这种测试比手工点四个格子更容易发现边界 浮点误差 舍入 单位问题。第六篇最终回归会把这类坐标准确率正式纳入指标。二十九、为什么这一篇值得保留所有中间数值很多技术文章喜欢最后只留slot_02但工程排查最有价值的恰恰是632 / 176 728 / 236 582.4 / 188.8 0.809 / 0.400这些数值让我们知道每一步是否符合预期。坐标问题最忌讳“黑盒正确”。只要中间链路可见哪怕以后加旋转矩阵、嵌套 Scroll也仍然可以逐层验证。这也是 SlideDrop 后续能继续扩到复杂画布的基础。参考资料HarmonyOS 7 全场景能力碰一碰·精准分享https://developer.huawei.com/consumer/cn/features/all-scenarioArkUI / Input Kit 坐标体系术语https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/input-kit-glossaryArkUI 帧率与状态更新优化建议https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-zhenlv
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →