云计算百科
云计算领域专业知识百科平台

HarmonyOS 7 精准碰一碰 SlideDrop 实战 01:ArkUI + 精准投递跑通演示素材插入链路【鸿蒙心迹】

我看到 HarmonyOS 7 里“精准碰一碰”这个能力时,脑子里冒出来的第一个想法不是“又多了一个分享方式”,而是:如果手机里的素材,能碰到大屏演示文稿里的某个区域,再直接插进去,这件事其实很像真实办公场景。

所以我决定做一个小 Demo,名字叫 SlideDrop。这套五连载不追求一上来就把所有边界全讲完,而是像平时做工程一样,从第一版能跑通开始,一步一步把它做成一个看得懂、能演示、还能继续扩展的小项目。第一篇的目标很明确:先把“选素材 → 碰一碰 → 识别目标区域 → 插入到演示页”这条主链路跑通。

一、我为什么会想到做 SlideDrop

很多时候,我们在写技术文章时容易陷入一种习惯:上来先解释概念,再列能力点,再贴几段 API。这样当然没错,但真到工程里,开发者往往不是因为“概念很完整”才开始写代码,而是因为眼前有一个特别具体的问题。

我这次的出发点就很简单:

平时做课件、讲方案、做汇报的时候,手机里经常会存着一些临时素材。可能是一张产品图,也可能是一张现场拍的照片,或者一段整理好的要点截图。传统方式通常是:先把文件传到电脑,再手动打开演示文稿,再拖进去,再调整位置。整个过程并不复杂,但碎。

HarmonyOS 7 的“精准碰一碰”让我觉得,它天然适合把这个动作变短:

  • 手机端负责选择素材;
  • 大屏端负责准备一个可接收的演示页;
  • 系统帮助建立“碰一碰”的投递链路;
  • 应用根据触点位置判断用户到底想把素材放到哪里;
  • 最后把图片、文本或图表插入对应区域。

你会发现,这不是一个“炫技 Demo”,而是一个有很强场景感的 Demo。它适合办公、适合演示,也适合用来解释“精准”两个字到底精准在哪里。

所以我给这个 Demo 起了一个很直接的名字:SlideDrop。

它不是做“跨设备文件传输”这么宽的命题,而是收得很小:

从手机把素材精准投递到演示文稿的指定位置。

这个范围一收小,第一篇要做的事情就清楚了:

  • 手机端能看到并选择素材;
  • 演示页里先定义几个明确的目标区域;
  • 能拿到一次“碰一碰”动作返回的落点信息;
  • 根据落点判断命中了哪个区域;
  • 把素材插进去,并让页面和日志都能反馈成功。
  • 这五件事如果都打通了,第一篇就成立了。

    二、第一篇不求复杂,先把主链路跑通

    如果一开始就想把标题区、图片区、图表区、备注区、素材类型、冲突处理、失败恢复、撤销逻辑全部做完,这个项目会马上变重。更麻烦的是,文章也会跟着发散:读者看不清主线,作者自己也容易越写越散。

    所以我给第一篇定的范围非常克制:

    • 素材类型先以 图片 为主;
    • 接收端先只做一个简单的“演示页编辑器”;
    • 区域先分成四块:标题区、图片区、图表区、备注区;
    • 先证明“碰哪放哪”这件事可以成立;
    • 其他边界问题留到后面几篇继续拆。

    这其实是我很喜欢的一种连载写法:

    第一篇只做“能跑起来的第一版”,
    第二篇开始补坐标和区域,
    第三篇再处理素材规则,
    第四篇解决状态和异常,
    第五篇最后做工程收口。

    这样整个系列会像一个真实项目,而不是五篇互相没关系的技术稿。

    SlideDrop 第一版的页面结构也很简单,我把它拆成两端:

    1)手机发送端

    发送端的职责很清楚:

    • 展示待投递素材;
    • 让用户选择一项内容;
    • 调用封装好的投递控制器;
    • 发起“碰一碰”动作;
    • 等待系统返回结果。

    2)大屏接收端 / 演示页端

    接收端也不复杂:

    • 提前定义一页演示文稿中的可投放区域;
    • 接收系统传回的触点坐标;
    • 通过 hitTest 命中目标区域;
    • 调用插入逻辑更新页面;
    • 把结果回写到日志和界面状态。

    第一篇的价值,不在于“功能很多”,而在于这条链路跑通以后,后面四篇就有了真正可以演进的基础。

    三、先把演示页上的“可投放区域”定义清楚

    做这种 Demo,最怕的一种写法是:一上来就讲跨设备,讲能力封装,讲流程编排,结果最基本的页面接收对象没定义清楚。

    我反而觉得,这个项目第一步最应该做的,是先问一句:

    演示页到底哪里可以接素材?

    如果这个问题没说清楚,后面所谓的“精准”,其实是站不住的。

    所以我先在 SlidePage 里定义了四类区域:

    • 标题区 title
    • 图片区 image
    • 图表区 chart
    • 备注区 note

    每个区域至少包含四类信息:

  • id:区域唯一标识;
  • type:区域类型;
  • rect:区域在页面中的位置和尺寸;
  • label:界面上给人看的名称。
  • 这一步看着很基础,但它其实把“演示页是一个接收平面”这件事说清楚了。只有先把接收平面描述出来,后面拿到触点坐标时,才有可能做命中判断。

    下面这段代码,就是第一版里最关键的一段“区域定义”。

    文件位置: 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 已经不是“一个静态演示页面”,而是真正出现了后续问题:坐标、区域、素材规则、状态同步、异常恢复、工程收口。这说明它具备继续往下写的空间。

    所以我对第一篇的评价其实很简单:

    它还远远不完整,但它已经足够真实;它还只是第一版,但它已经有了继续演进的方向。对一个五连载项目来说,这就很重要。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » HarmonyOS 7 精准碰一碰 SlideDrop 实战 01:ArkUI + 精准投递跑通演示素材插入链路【鸿蒙心迹】
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!