我看到 HarmonyOS 7 里“精准碰一碰”这个能力时,脑子里冒出来的第一个想法不是“又多了一个分享方式”,而是:如果手机里的素材,能碰到大屏演示文稿里的某个区域,再直接插进去,这件事其实很像真实办公场景。
所以我决定做一个小 Demo,名字叫 SlideDrop。这套五连载不追求一上来就把所有边界全讲完,而是像平时做工程一样,从第一版能跑通开始,一步一步把它做成一个看得懂、能演示、还能继续扩展的小项目。第一篇的目标很明确:先把“选素材 → 碰一碰 → 识别目标区域 → 插入到演示页”这条主链路跑通。

一、我为什么会想到做 SlideDrop
很多时候,我们在写技术文章时容易陷入一种习惯:上来先解释概念,再列能力点,再贴几段 API。这样当然没错,但真到工程里,开发者往往不是因为“概念很完整”才开始写代码,而是因为眼前有一个特别具体的问题。
我这次的出发点就很简单:
平时做课件、讲方案、做汇报的时候,手机里经常会存着一些临时素材。可能是一张产品图,也可能是一张现场拍的照片,或者一段整理好的要点截图。传统方式通常是:先把文件传到电脑,再手动打开演示文稿,再拖进去,再调整位置。整个过程并不复杂,但碎。
HarmonyOS 7 的“精准碰一碰”让我觉得,它天然适合把这个动作变短:
- 手机端负责选择素材;
- 大屏端负责准备一个可接收的演示页;
- 系统帮助建立“碰一碰”的投递链路;
- 应用根据触点位置判断用户到底想把素材放到哪里;
- 最后把图片、文本或图表插入对应区域。
你会发现,这不是一个“炫技 Demo”,而是一个有很强场景感的 Demo。它适合办公、适合演示,也适合用来解释“精准”两个字到底精准在哪里。
所以我给这个 Demo 起了一个很直接的名字:SlideDrop。
它不是做“跨设备文件传输”这么宽的命题,而是收得很小:
从手机把素材精准投递到演示文稿的指定位置。
这个范围一收小,第一篇要做的事情就清楚了:
这五件事如果都打通了,第一篇就成立了。
二、第一篇不求复杂,先把主链路跑通
如果一开始就想把标题区、图片区、图表区、备注区、素材类型、冲突处理、失败恢复、撤销逻辑全部做完,这个项目会马上变重。更麻烦的是,文章也会跟着发散:读者看不清主线,作者自己也容易越写越散。
所以我给第一篇定的范围非常克制:
- 素材类型先以 图片 为主;
- 接收端先只做一个简单的“演示页编辑器”;
- 区域先分成四块:标题区、图片区、图表区、备注区;
- 先证明“碰哪放哪”这件事可以成立;
- 其他边界问题留到后面几篇继续拆。
这其实是我很喜欢的一种连载写法:
第一篇只做“能跑起来的第一版”,
第二篇开始补坐标和区域,
第三篇再处理素材规则,
第四篇解决状态和异常,
第五篇最后做工程收口。
这样整个系列会像一个真实项目,而不是五篇互相没关系的技术稿。
SlideDrop 第一版的页面结构也很简单,我把它拆成两端:
1)手机发送端
发送端的职责很清楚:
- 展示待投递素材;
- 让用户选择一项内容;
- 调用封装好的投递控制器;
- 发起“碰一碰”动作;
- 等待系统返回结果。
2)大屏接收端 / 演示页端
接收端也不复杂:
- 提前定义一页演示文稿中的可投放区域;
- 接收系统传回的触点坐标;
- 通过 hitTest 命中目标区域;
- 调用插入逻辑更新页面;
- 把结果回写到日志和界面状态。
第一篇的价值,不在于“功能很多”,而在于这条链路跑通以后,后面四篇就有了真正可以演进的基础。
三、先把演示页上的“可投放区域”定义清楚
做这种 Demo,最怕的一种写法是:一上来就讲跨设备,讲能力封装,讲流程编排,结果最基本的页面接收对象没定义清楚。
我反而觉得,这个项目第一步最应该做的,是先问一句:
演示页到底哪里可以接素材?
如果这个问题没说清楚,后面所谓的“精准”,其实是站不住的。
所以我先在 SlidePage 里定义了四类区域:
- 标题区 title
- 图片区 image
- 图表区 chart
- 备注区 note
每个区域至少包含四类信息:
这一步看着很基础,但它其实把“演示页是一个接收平面”这件事说清楚了。只有先把接收平面描述出来,后面拿到触点坐标时,才有可能做命中判断。
下面这段代码,就是第一版里最关键的一段“区域定义”。
文件位置: entry/src/main/ets/pages/SlidePage.ets
用途: 先把演示页中的四个投放区域描述清楚,后续所有的坐标判断和插入逻辑都围绕它来做。
enum ZoneType {
TITLE = 'title',
IMAGE = 'image',
CHART = 'chart',
NOTE = 'note'
}
interface DropZone {
id: string
type: ZoneType
rect: Rect
label: string
}
private targetZones: DropZone[] = [
{ id: 'zone-title', type: ZoneType.TITLE, rect: { x: 40, y: 120, width: 640, height: 80 }, label: '标题区' },
{ id: 'zone-image', type: ZoneType.IMAGE, rect: { x: 40, y: 220, width: 300, height: 220 }, label: '图片区' },
{ id: 'zone-chart', type: ZoneType.CHART, rect: { x: 380, y: 220, width: 300, height: 220 }, label: '图表区' },
{ id: 'zone-note', type: ZoneType.NOTE, rect: { x: 40, y: 460, width: 640, height: 120 }, label: '备注区' }
]
这个结构的好处是后面很容易扩展。比如第二篇如果我要支持更多布局,只需要继续调整区域坐标;如果第三篇我要做不同素材的插入规则,也只是围绕 type 继续下沉逻辑,并不用推倒重来。

四、主链路其实没有那么神秘:就是“素材、触点、命中、插入”四步
把页面结构摆清楚以后,我会立刻回头梳理主链路。因为一个 Demo 到底清不清楚,往往不在于页面画了多少,而在于你能不能用一句人话把它讲顺。
我把 SlideDrop 第一版主链路收成四个动作:
1)素材准备
用户在手机端先选中一张图片。这个素材可以来自应用内置素材库,也可以来自相册映射后的演示数据。第一篇里为了保证流程稳定,我先用了内置素材列表。
2)触发投递
当用户点“准备投递”或“开始投递”后,控制器负责把当前素材封装成一次 SlideDrop 可识别的任务。系统在这个阶段会介入设备发现、建立连接以及后续的“碰一碰”行为。
3)命中区域
接收端拿到触点位置以后,不是直接插,而是先执行一次 hitTest。也就是说,先判断这个点落在了标题区、图片区、图表区还是备注区。
4)执行插入
只有确认命中区域以后,才进入真正的业务逻辑:
- 命中标题区,就按标题逻辑插入;
- 命中图片区,就按图片逻辑插入;
- 命中图表区,就走图表逻辑;
- 命中备注区,就写到备注区。
第一篇里我先重点验证图片区,因为它最直观,也最适合作为第一次效果演示。
把这条链路讲清楚以后,很多事情都不再抽象。所谓“精准碰一碰”,本质上不是一句宣传词,而是:
手机素材 + 触点坐标 + 区域命中 + 业务插入
这张类型二的图,就是我希望后面文章里一直保留的那种说明方式:既有 DevEco 拟真截图,也有关系流程图、实机感照片和标注,读者一眼就能明白核心链路。

五、发送端先做轻一点:把“选素材”做好比“堆能力”更重要
很多 Demo 写到这里,容易忍不住给手机端加很多功能,比如筛选、搜索、分组、最近使用、云端同步。但第一篇真的没必要。
因为第一篇发送端的目标就两件事:
所以我把发送端写得很轻:
- 顶部一个简单的标题;
- 中间是素材网格;
- 底部一个主按钮“准备碰一碰投递”;
- 选中态用蓝色勾选高亮。
这类页面最重要的不是华丽,而是可确认。因为跨设备动作一旦开始,如果用户自己都不确定当前选的是哪张素材,体验会非常差。
所以你会发现我在 UI 上故意强化了两点:
- 选中卡片有明显蓝色描边和勾选状态;
- 底部按钮直接把数量写出来,让用户知道已经选择了 1 项素材。
第一篇里,这个页面已经足够用了。它没有太多“产品感”,但工程上非常明确。

接着,我在发送端加了一层很薄的控制器封装。它不负责处理所有业务,只负责把“当前选中的素材”交给后面的投递链路。
这里有个很重要的工程判断:
页面层不要直接到处散落“准备投递”“发起投递”“处理结果”的代码。
第一版项目还小,看不出问题;但如果不提前收住,后面第二篇第三篇一加功能,页面就会开始失控。所以第一篇我就先把这层控制器放进去,即便逻辑还不复杂,也先给项目留出结构空间。
六、接收端的关键不是插入,而是先做 hitTest
如果要说第一篇最有“技术味儿”的点,我觉得不是投递,不是日志,甚至也不是 UI,而是 hitTest。
因为 SlideDrop 这个项目最核心的体验,其实就取决于这一点:
当用户碰到演示页时,系统给回来的坐标,到底怎么转成具体区域?
这一步如果说不清楚,整个项目会沦为“碰一碰传一张图片”的普通演示,而不是“精准投到某个位置”的场景 Demo。
我的处理方式很直白:
- 先把每个区域定义成一个 rect;
- 接到触点 point 后,遍历所有区域;
- 判断 point.x、point.y 是否落在对应 rect 范围内;
- 命中哪个,就返回哪个区域对象。
这个判断本身并不复杂,但它把“触点 → 区域”的映射关系明确落成了代码。
下面这段代码就是第一版接收端的核心。
文件位置: entry/src/main/ets/pages/SlidePage.ets
用途: 接收触点事件,执行命中判断,再把素材插入对应区域。
private hitTest(point: Point): DropZone | undefined {
return this.targetZones.find(zone => {
return point.x >= zone.rect.x &&
point.x <= zone.rect.x + zone.rect.width &&
point.y >= zone.rect.y &&
point.y <= zone.rect.y + zone.rect.height
})
}
private insertMaterial(zone: DropZone, material: MaterialItem) {
switch (zone.type) {
case ZoneType.TITLE:
this.insertTitle(material)
break
case ZoneType.IMAGE:
this.insertImage(material)
break
case ZoneType.CHART:
this.insertChart(material)
break
case ZoneType.NOTE:
this.insertNote(material)
break
}
}
onDrop(event: DragEvent) {
const point = { x: event.x, y: event.y }
const zone = this.hitTest(point)
if (zone && event.data) {
const material = event.data as MaterialItem
this.insertMaterial(zone, material)
console.info(`已在【${zone.label}】插入: ${material.name}`)
}
}
这段代码解决了两个问题:
第一,命中判断。
它回答的是“你碰到了哪里”。
第二,插入分发。
它回答的是“碰到这个位置以后,该按哪类逻辑处理”。
这就是为什么我说第一篇真正的骨架不是“投递”,而是“目标区域 + 命中判断 + 分发执行”。

七、日志一定要早加,而且要围绕主链路打
很多人做 Demo 会把日志留到最后。但我这几年越来越觉得,日志应该尽早加,而且要尽早围绕主链路去打。
原因很简单:
第一篇要验证的不是“页面看起来像成功了”,而是整个动作链条真的跑通了。
所以我在第一版里把日志重点放在了下面几个节点:
- 选择素材成功;
- 调用 prepare 成功;
- 发起投递成功;
- 收到触点坐标;
- 命中目标区域;
- 插入成功;
- 当前页与耗时信息。
这样做有两个好处。
1)文章更好写
因为你不是在空口讲流程,而是有证据。哪一步发生了,哪一步成功了,哪一步还没做,都能从日志里读出来。
2)后面几篇更好演进
第二篇如果我要分析坐标偏差,日志就能直接复用;第三篇如果我要分析不同素材策略,日志结构也已经在;第四篇第五篇做异常恢复和工程收口时,这些日志还能继续成为排查入口。
我很不喜欢那种“前面三篇都不打日志,最后一篇突然加一堆输出”的写法。那样看起来像补作业,不像工程。
所以第一篇虽然只是最小 Demo,我也要求它在日志层能讲清楚故事:
[SlideDrop] 选择素材: 产品图.jpg
[SlideDrop] 准备素材成功,mimeType=image/png
[SlideDrop] startDrop 调用成功,等待结果
[SlideDrop] 接收到触点坐标: x=642, y=1188
[SlideDrop] 命中目标区域: IMAGE
[SlideDrop] 插入成功,目标应用: 幻灯片编辑器
这样的日志不是为了炫,而是为了给后面的文章打底。
八、第一次跑通效果时,我最关注的不是“传过去了”,而是“放对了”
第一版跑通以后,最容易高兴的一点当然是:手机上的素材真的进到演示文稿里了。
但如果只停在“传过去了”,我觉得这个项目还谈不上成立。因为普通的传图方式也能做到“传过去了”。
SlideDrop 第一篇真正让我觉得值得继续写下去的地方,是我看到它已经开始具备“放对位置”的雏形。
比如我把图片素材碰到图片区,最后它就真的插到了图片区,而不是整个页面随便哪个地方。这个感受很重要。它说明这个 Demo 不是一个“跨设备展示效果”,而是在朝“跨设备操作能力”走。
当然,第一篇我也很清楚它还不完整:
- 目前更多是图片主链路;
- 还没有处理重复插入和覆盖逻辑;
- 文本、图表、备注虽然区域在,但策略还很轻;
- UI 也还是偏工程演示。
但这都没关系。第一篇不是为了“完整”,而是为了建立第一块真正稳的地基。
下面这张实机感效果图,就是我希望读者在第一篇最后看到的状态:
- 手机端确认素材已发送;
- 大屏端演示文稿当前页已插入图片;
- 整个流程能被肉眼感知,也能被日志印证。

九、第一篇里我刻意没做的几件事
我觉得写连载有一个很重要的习惯,就是不只告诉读者“这篇做了什么”,还要告诉读者“这篇故意没做什么”。
因为这能帮助整套系列保持节奏。
第一篇里我刻意没做下面这些事情:
1)没做复杂素材库
没有做搜索、分组、最近使用、云素材同步。因为第一篇的目标不是做素材管理,而是跑通精准投递。
2)没做区域冲突处理
如果标题区已经有内容、图片区已经有图怎么办?这类冲突很真实,但不适合第一篇上来就塞进来。
3)没做结果回撤和撤销
这会直接带来状态同步和 UI 反馈问题,我准备放到后面的篇章里再讲。
4)没做更完整的设备状态判断
比如设备未发现、连接超时、用户取消、落点无效等。这些都是真问题,但在第一篇里我只保留了最小路径。
其实这就是连载式写法的好处。你不用每一篇都追求“大而全”,而是把真实问题一层一层展开。第一篇负责建立信心,第二篇第三篇再慢慢加厚。
十、我会怎么验收 SlideDrop 第一篇
技术文章写到最后,如果没有验收标准,很多内容会显得很虚。
所以我给第一篇定了一套很朴素但很有用的验收标准:
1)页面结构清楚
- 发送端能选素材;
- 接收端能看到四个目标区域;
- 交互入口明显,不会让人不知道下一步干什么。
2)主链路跑通
- 选择图片;
- 准备投递;
- 发起碰一碰;
- 收到触点;
- 命中图片区;
- 演示页显示插入结果。
3)日志能解释过程
不是只看到最后一个成功提示,而是整条链路的关键节点都能在日志中看到。
4)页面结果和日志一致
如果界面显示插入成功,日志也应该能看到命中区域和插入结果;如果日志显示命中图片区,页面最终也应该真的落在图片区。
5)项目结构还能继续扩展
第一篇不求完美,但至少要看得出来:
- 发送端和接收端没有完全搅在一起;
- 区域定义是独立的;
- hitTest 是独立的;
- 插入分发逻辑后面还能往下拆。
这五条如果都满足,我就认为第一篇是成立的。
十一、本文小记
如果让我给第一篇下一个最直接的总结,我会说:
SlideDrop 第一篇最重要的不是“碰一碰成功了”,而是“精准插入这件事第一次具备了工程雏形”。
这篇文章做的事其实并不复杂:
- 定义演示页的目标区域;
- 建一个轻量发送端;
- 打通投递控制器;
- 根据触点做命中判断;
- 把图片插入到对应位置;
- 用日志把整个过程串起来。
但我很喜欢这种节奏。因为它不是一口气追求一个看起来很大的成品,而是像真实开发一样,先把第一块骨架搭起来。
到这里,SlideDrop 五连载的第一步就算走稳了。
后面第二篇,我会继续往下走,重点不再是“能不能传过去”,而是:
触碰坐标 + 区域映射,怎么把‘碰哪放哪’这件事讲得更细、更准、更像一个真正可用的演示协作工具。
十二、第一篇做完以后,我已经看到的几个真实问题
第一篇虽然已经把主链路跑通了,但我写到这里时,脑子里其实已经冒出好几个后续必须面对的问题。把这些问题提前写出来,我觉得比假装“第一篇已经很完整”更有意义。
1)命中区域目前还是偏静态
现在的区域坐标来自我手工定义的 rect。这对第一篇完全够用,因为它能帮助我们把“精准落位”这件事先讲清楚。但如果后面演示页支持不同模板、不同布局,甚至支持缩放和滚动,单纯静态坐标就不够了。也就是说,第二篇我肯定要继续处理“坐标系统”和“区域映射”这两个问题。
2)素材类型虽然有四类,插入策略还不对等
第一篇里最成熟的是图片。标题、图表、备注虽然已经留好了区域,也在分发逻辑里占了位置,但它们目前更像是“占坑”而不是“完整能力”。这没有问题,因为工程本来就应该先打通一条最清晰的主链路。只是我心里会一直记着:后面文章不能停留在“图片区演示成功”,而要真正让四类区域都逐步具备业务意义。
3)发送端和接收端的状态还没有做得很细
现在用户能看到选中态、按钮态和插入结果,但整个过程里的“准备中、等待碰一碰、投递中、成功、失败、取消”等状态还没有完全展开。对 Demo 来说,这不是致命问题;可一旦要把体验往“可用”推进,这部分就会变得很关键。
4)日志已经有了,但诊断层次还不够
我在第一篇里已经开始打主链路日志,这是一个很好的开端。但如果后面真遇到坐标不准、区域识别错误、素材插入失败,仅靠现在这几条日志还是不够。后面我会继续把日志拆成“发送端日志、接收端日志、区域判断日志、插入结果日志”,这样文章也会更像真实项目复盘。
5)最重要的一点:第一篇已经值得继续写下去
我做连载时很看重一个信号:做完第一篇以后,你是不是还能自然地知道第二篇要写什么。如果第一篇做完以后脑子里一片空白,说明这个选题大概率不适合连载。
但 SlideDrop 恰恰相反。第一篇写完以后,第二篇、第三篇、第四篇要做什么,我反而更清楚了。因为这个 Demo 已经不是“一个静态演示页面”,而是真正出现了后续问题:坐标、区域、素材规则、状态同步、异常恢复、工程收口。这说明它具备继续往下写的空间。
所以我对第一篇的评价其实很简单:
它还远远不完整,但它已经足够真实;它还只是第一版,但它已经有了继续演进的方向。对一个五连载项目来说,这就很重要。
网硕互联帮助中心




评论前必须登录!
注册