在会议桌旁,手机里保存着一张现场照片,电脑上正打开项目汇报文稿。传统分享步骤是先选择目标设备,再找到文件,随后切到文稿,将图片放到指定位置。HarmonyOS 7 的“碰一碰·精准分享”把交互入口推进到屏幕上的具体位置:手机轻触电脑或平板屏幕,目标窗口与触点位置就成为内容定向处理的依据。

HarmonyOS 7 碰一碰精准分享封面

“碰到哪里,就把素材放到哪里”听起来像一句产品宣传语,但对于编辑应用,它对应着一组非常具体的工程问题:触点位于哪个窗口?落在画布的哪一个区域?当前用户是否拥有编辑权限?图片会不会被连续插入两次?如果画布已经缩放或滚动,屏幕坐标还能直接用吗?把这些问题拆开,才能让近场分享从一次性动作变为稳定的内容编辑流程。

从物理触点到编辑器里的逻辑位置

华为 HarmonyOS 7 特性页面介绍,精准分享支持手机触碰电脑或平板屏幕,定位目标窗口和触碰坐标,将图片等素材插入指定位置。实际工程可以把流程分为“系统触发与内容送达”和“接收应用里的业务落点处理”两层。前者要按照对应特性的接入文档与能力条件实现;后者可以先在应用内部沉淀一套和传输方式无关的插入协议。这样以后即使增加拖拽、文件选择、系统分享等入口,也能共用落点校验与编辑器提交逻辑。

以文稿编辑器为例,目标窗口内可能同时存在页面缩略图栏、正文画布和工具栏。如果仅凭触点在窗口中的水平位置判断内容落点,用户碰到工具栏就可能误把图片插进正文。更合理的做法是在坐标映射之前先完成窗口身份校验,再检查落点是否位于允许编辑的区域,最后把窗口坐标转换成画布内部坐标。

阶段接收侧关心的数据处理结果
目标识别目标窗口标识、当前文档标识确定用户正在处理的文档
触点解析窗口坐标、画布可见区域确认触点位于可插入范围
坐标映射平移偏移、缩放比例计算内容坐标,避免错位
内容提交素材标识、事务标识、用户权限完成一次可追踪的插入
结果反馈成功、重复、已过期、权限不足让用户清楚这次操作的结果

坐标属于哪个空间,是首要约定。 如果收到的是窗口坐标,必须扣除画布左上角的可视偏移;如果编辑器画布被缩放,还要除以缩放系数。把窗口坐标当成文档坐标,会在画布滚动或页面放大时出现明显偏移。以下纯业务函数把这一步独立出来,既能接收系统接入层提供的触点,也能处理应用内部的拖拽坐标。

export interface SurfacePoint {
  x: number;
  y: number;
}

export interface CanvasViewport {
  windowId: string;
  left: number;
  top: number;
  width: number;
  height: number;
  scrollX: number;
  scrollY: number;
  scale: number;
}

export interface DropPosition {
  documentX: number;
  documentY: number;
}

export class DropGeometry {
  static resolve(windowId: string, point: SurfacePoint,
    viewport: CanvasViewport): DropPosition | null {
    if (windowId !== viewport.windowId || viewport.scale <= 0) {
      return null;
    }
    const localX = point.x - viewport.left;
    const localY = point.y - viewport.top;
    if (localX < 0 || localY < 0 ||
      localX > viewport.width || localY > viewport.height) {
      return null;
    }
    return {
      documentX: (localX + viewport.scrollX) / viewport.scale,
      documentY: (localY + viewport.scrollY) / viewport.scale
    };
  }
}

viewport 的定义需与接入层保持一致:这里的 scrollX、scrollY 是显示坐标尺度下的画布滚动偏移;如果编辑器返回的已经是文档坐标,转换公式就应该先统一单位,避免重复缩放。在窗口旋转、独立缩放容器或多层变换的界面中,还需要使用完整变换矩阵。不要用屏幕尺寸的固定比例代替真实布局几何关系。

DevEco Studio 风格的接收侧坐标映射与去重逻辑

系统送达的是入口,编辑器需要的是一次事务

假设手机里准备投递的是现场照片 IMG_2048.jpg。接收应用会关心媒体类型、来源标识、素材引用、文档标识和一次业务操作编号。这里将它们封装为 DropEnvelope,但这个对象属于应用自行定义的数据契约,并非系统精准分享 SDK 的接口名称。系统层的实际回调或交付对象应在完成能力接入后转换为这个结构。

为什么要多加一个操作编号?因为跨设备触发、用户重试、界面转后台和网络重连都可能使接收逻辑多次进入。如果每次都直接执行插入,就会在画布同一位置出现两张相同图片。把操作编号作为幂等键,在编辑器提交之前检查,能够让相同操作只生效一次。需要注意的是,本地内存去重只能覆盖当前进程,生产环境还应把去重状态与编辑事务一起持久化。

export interface DropEnvelope {
  operationId: string;
  targetWindowId: string;
  targetDocumentId: string;
  assetUri: string;
  mimeType: string;
  createdAt: number;
}

export interface InsertResult {
  ok: boolean;
  message: string;
}

export interface EditorPort {
  insertImage(documentId: string, uri: string,
    x: number, y: number): Promise<void>;
}

export class DropCoordinator {
  private completed: Set<string> = new Set<string>();
  private pending: Set<string> = new Set<string>();

  async accept(data: DropEnvelope, point: DropPosition,
    editor: EditorPort, editable: boolean,
    now: number): Promise<InsertResult> {
    if (!editable) {
      return { ok: false, message: '当前文档不可编辑' };
    }
    if (data.mimeType !== 'image/jpeg' &&
      data.mimeType !== 'image/png') {
      return { ok: false, message: '素材格式不受支持' };
    }
    if (now - data.createdAt > 120000 ||
      data.createdAt > now + 10000) {
      return { ok: false, message: '投递请求已过期' };
    }
    if (this.completed.has(data.operationId) ||
      this.pending.has(data.operationId)) {
      return { ok: false, message: '该素材正在处理或已插入' };
    }
    this.pending.add(data.operationId);
    try {
      await editor.insertImage(data.targetDocumentId,
        data.assetUri, point.documentX, point.documentY);
      this.completed.add(data.operationId);
      return { ok: true, message: '图片已插入文档' };
    } catch (error) {
      return { ok: false, message: '插入失败,可重新发起' };
    } finally {
      this.pending.delete(data.operationId);
    }
  }
}

这份协调器重点表达校验顺序和幂等思路。真实文档应用还要检查 targetDocumentId 是否与当前编辑器一致,确认素材 URI 的访问权限以及大小限制,并且在提交事务时复核一次编辑权限。对长时间的图片解码与落盘操作,应让 UI 展示清楚的进度,不宜让用户在没有反馈时反复碰触。

精准落点之外,还需要防误触和可撤销

办公场景里最容易忽略的情况是:手机碰到了错误的应用窗口。目标窗口识别固然重要,接收应用仍要有自己的规则。例如用户当前查看的是只读 PDF,接收侧应该提供“保存到附件”或“选择可编辑文稿”的路径;当触点正落在文本输入框上,则可以把投递理解为插入到光标位置,而非粗暴覆盖整个画布。

建议把投递结果分成三种交互强度。明确可编辑且素材较小、落点清楚时可直接提交;落点位于空白区域或文件体积较大时先显示预览;权限不足、内容类型不支持或目标窗口不匹配时给出可理解的失败反馈。预览中要展示文件名、目标文档、预计插入位置与“取消”入口。确认按钮的文字应是“插入图片”,而不是笼统的“继续”。

另外,图片插入应该与编辑器撤销栈统一。无论入口是精准分享、鼠标拖拽还是菜单插入,最终都应写成一条文档编辑命令。用户按撤销后,文档状态、图片资源引用和选区状态需要一起回退。若编辑器采用协同编辑模式,服务端还要核对版本向量或文档修订号,避免一张图片覆盖其他人的实时更新。

手机端素材选择与定向投递状态界面

故障路径要按“能否继续编辑”来组织

与其只记录“投递失败”,不如按根因拆分。触点越界属于位置问题,用户可以重新选择落点;目标窗口切换属于上下文变化,应该重新确认文档;素材引用失效则需要重新获取内容;权限不足应先解释当前文档的编辑状态。把错误消息直接写成底层错误码,对用户完成任务没有帮助。

情况接收侧动作用户可见反馈
触点落在编辑区之外拒绝坐标映射提示选择文档内容区域
窗口标识不一致停止处理该次请求显示当前目标窗口名称
相同操作重复送达通过幂等键合并提示已经插入或正在处理
文档权限发生变化在提交阶段重新校验说明当前只读状态
素材暂不可访问保留可重试的业务上下文给出重新选择素材入口

排查时可按 operationId 关联接收事件、几何映射、格式校验、权限判断和编辑事务日志,但不要在业务日志里输出完整图片、私密文档正文或访问令牌。对触点坐标可以记录经过脱敏或分桶后的诊断信息,避免把精确的用户交互位置当成长期的行为画像。

开发者还需要辨别两个不同的问题:系统是否把内容送达目标窗口,以及目标应用是否正确把内容放入编辑器。前者由具体的分享能力和接入流程决定;后者可以通过注入固定的窗口标识、触点坐标与素材元数据进行单元测试。将它们分层处理,定位问题时就不会反复调整与故障无关的代码。

相关资料:HarmonyOS 7 新系统能力、HarmonyOS 全场景能力、HarmonyOS 开发者文档中心。具体的能力申请、目标窗口交付及数据对象适配应依据开发者平台对应版本的接入文档完成。

精准分享真正改变的不是文件被传输得更快,而是“目标位置”进入了内容流转的上下文。只要把窗口、坐标、权限、素材和编辑事务组织成明确的业务契约,这种触点交互便能融入已有的编辑体系,而不必让每一种分享方式再造一套插入逻辑。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐