鸿蒙专家 发表于 2026-9-27 08:00:00

鸿蒙Share Kit碰一碰坐标获取与ArkTS适配实践

从“碰一碰”触发器到空间坐标回调

HarmonyOS 7.0(API 26)的 Share Kit 针对“碰一碰”只作为触发信号、接收端不知道触碰区域的问题,新增了接收端轻碰位置信息能力。PC/2in1 或 Tablet 设备侧可以获取空间相对坐标系中的 x、y。这个坐标以接收端设备屏幕左上角为原点,属于像素级坐标。

过去,手机触碰 PC 或 Tablet 时,接收端只知道“有设备碰了我并要传数据”,不知道“具体碰了哪块区域”。典型场景是平板分屏两个独立应用,手机轻碰左侧屏幕区域,预期分享给左侧应用,但系统缺少空间坐标,只能全局弹窗让用户二次选择;另一个场景是手机触碰 PC 屏幕一角,开发者希望在这个触碰点精准渲染水波纹动效,而不是在屏幕正中心弹出对话框。

底层链路:NFC触发、BLE/UWB测距与坐标映射

原文把底层融合感知拆成三步,应用侧主要关心最终通过 Want 投递回来的坐标。

1. NFC 场强捕获(Trigger):NFC 线圈切割磁感线,唤醒系统级 ShareService。

2. BLE/UWB 空间测距(Ranging):NFC 握手通道建立后,双方快速交换 BLE MAC 或 UWB 会话密钥,接收端设备通过多天线阵列计算 AoA(Angle of Arrival,到达角)和精确测距。

3. 坐标系换算与投递(Mapping):系统底层结合设备物理尺寸、屏幕分辨率以及传感器物理偏移量(Offset),推算出相对屏幕的像素级 (x, y) 坐标。

这也解释了为什么单纯依靠 NFC 硬件不够:NFC 有效交互距离在 2-4cm,且主要位于设备背部特定区域的线圈,空间位置需要后续测距和映射补齐。应用侧无需自行实现底层坐标解算,重点是在接收端正确处理 Want 与 UI 响应。

接收端配置:module.json5声明分享意图

为了让系统知道应用有资格处理特定类型的数据并支持触碰位置接收,需要在 module.json5 的 skills 中配置 actions 与 uris。以接收图片为例:


{
"module": {
    "abilities": [
      {
      "name": "ShareEntryAbility",
      "srcEntry": "./ets/entryability/ShareEntryAbility.ets",
      "exported": true,
      "skills": [
          {
            "actions": ["ohos.want.action.sendData"],
            "uris": [
            { "scheme": "file", "type": "image/*" }
            ]
          }
      ]
      }
    ]
}
}


配置中的 ohos.want.action.sendData 用来声明支持碰一碰分享意图,uris 中的 scheme 与 type 用来限定可接收的数据类型。

ArkTS入口解析坐标:onCreate与onNewWant

当手机触碰平板屏幕边缘时,平板上的系统级服务会拦截这一动作,提取数据与物理坐标,并通过 Want 将其传递给 ShareEntryAbility。这里重点关注 want.parameters 中的两个新增参数:ohos.extra.param.key.share.touch.x 和 ohos.extra.param.key.share.touch.y。


import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';

const TAG = 'ShareEntryAbility';

export default class ShareEntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x0000, TAG, 'onCreate triggered by share');
    this.handleShareData(want);
}

onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x0000, TAG, 'onNewWant triggered by share');
    this.handleShareData(want);
}

private handleShareData(want: Want) {
    if (want.action !== 'ohos.want.action.sendData') {
      return;
    }

    let uris = want.parameters?.['ability.params.stream'] as string[];
    let touchX = want.parameters?.['ohos.extra.param.key.share.touch.x'] as number;
    let touchY = want.parameters?.['ohos.extra.param.key.share.touch.y'] as number;

    if (touchX !== undefined && touchY !== undefined) {
      hilog.info(0x0000, TAG, `Device touched at coordinates: (${touchX}, ${touchY})`);
      AppStorage.setOrCreate('TouchPosX', touchX);
      AppStorage.setOrCreate('TouchPosY', touchY);
      AppStorage.setOrCreate('SharedUris', uris);
    } else {
      hilog.warn(0x0000, TAG, 'No touch coordinates received.');
    }
}

onWindowStageCreate(windowStage: window.WindowStage): void {
    windowStage.loadContent('pages/ShareReceivePage', (err) => {
      if (err.code) {
      hilog.error(0x0000, TAG, 'Failed to load the content.');
      return;
      }
      hilog.info(0x0000, TAG, 'Succeeded in loading the content.');
    });
}
}


这里将坐标与共享 URI 写入 AppStorage,便于页面侧订阅。业务数据可以通过 ability.params.stream 获取,坐标则用于后续空间化反馈或路由。

UI响应:在触碰点渲染水波纹

接收到坐标后,可以在页面上做精准交互反馈。下面示例在触碰点生成一个涟漪展开动画,让用户感觉数据是从触碰位置流入的。


import { Component, State, Watch } from '@kit.ArkUI';

@Entry
@Component
struct ShareReceivePage {
@StorageLink('TouchPosX') @Watch('onTouchPosChanged') touchX: number = 0;
@StorageLink('TouchPosY') touchY: number = 0;
@StorageLink('SharedUris') sharedUris: string[] = [];

@State rippleScale: number = 0;
@State rippleOpacity: number = 1;
@State isRippleVisible: boolean = false;

onTouchPosChanged() {
    if (this.touchX > 0 && this.touchY > 0) {
      this.isRippleVisible = true;
      this.rippleScale = 0;
      this.rippleOpacity = 1;

      animateTo({
      duration: 800,
      curve: Curve.EaseOut,
      onFinish: () => {
          this.isRippleVisible = false;
      }
      }, () => {
      this.rippleScale = 10;
      this.rippleOpacity = 0;
      });
    }
}

build() {
    Stack() {
      Column() {
      Text('等待接收碰一碰分享...')
          .fontSize(20)
          .fontColor('#666')
      if (this.sharedUris.length > 0) {
          Text(`已接收到 ${this.sharedUris.length} 个文件`)
            .margin({ top: 20 })
            .fontWeight(FontWeight.Bold)
      }
      }
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Center)

      if (this.isRippleVisible) {
      Circle({ width: 50, height: 50 })
          .fill(Color.Transparent)
          .stroke(Color.Blue)
          .strokeWidth(3)
          .position({ x: this.touchX - 25, y: this.touchY - 25 })
          .scale({ x: this.rippleScale, y: this.rippleScale })
          .opacity(this.rippleOpacity)
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
}
}


Circle 的大小为 50,因此 position 中用 touchX - 25、touchY - 25 把圆心对齐到触碰物理坐标。ArkUI 显式动画负责缩放与透明度变化,动画结束后隐藏涟漪。

稳定性边界:非空校验与1秒防抖

如果发送端或接收端的硬件不支持精确位置解算,例如较老机型缺少多天线传感器或系统版本过低,touch.x 和 touch.y 可能为 undefined。工程实现中必须做好非空校验与防御性降级编程,缺少坐标时退回普通分享流程或屏幕中心反馈。

另一个常见问题是多重抖动事件。用户可能由于手抖,在 1 秒内触发多次 NFC 读写,使 onNewWant 被高频调用。若不加防御,会导致 UI 动效重叠、内存瞬间膨胀或底层通道 FD(文件描述符)句柄耗尽。原文给出的处理策略是:在入口层使用 Token Bucket 或时间戳比对进行防抖,若连续两次触碰事件间隔小于 1000ms,强制将多余事件 Drop 掉。

适配建议

从原文给出的路径看,Share Kit 新增的空间位置信息回调表面上只是多传了两个整数,背后反映的是操作系统对底层传感器网络的整合与系统级调度能力。适配时建议按以下顺序落地:先在 module.json5 声明 skills;再在 UIAbility 的 onCreate/onNewWant 中统一解析 Want;对 touch.x/touch.y 做非空判断;把坐标与共享数据投递到页面状态;最后在 ArkUI 侧按坐标渲染动效或做分屏路由,并在入口加防抖。

从“系统知道你碰了我”到“系统知道你碰了我的左下角(x:200, y:800)”,近场交互开始具备空间感知能力。对开发者而言,这类能力可以用于分屏应用精准分享、PC 角落水波纹动效以及更多基于触碰位置的跨设备交互。

热心网友3 发表于 2026-9-27 19:00:00

Re: 鸿蒙Share Kit碰一碰坐标获取与ArkTS适配实践

感谢分享,这篇把碰一碰从触发到坐标回调的链路讲得挺透彻的。之前确实一直困惑接收端为什么只能全局弹窗,原来缺的就是这个坐标能力。 想请教两个实操上的点: 一是坐标是接收端屏幕左上角为原点的绝对像素,那平板分屏场景下,左右两个应用各自怎么判断触碰点落在自己窗口内?是不是还得自己拿窗口位置和尺寸去比对坐标,系统不会直接帮你路由到对应窗口?如果是这样,分屏下两个应用都声明了同样的 skills,会不会出现两个都想抢这个 Want 的情况? 二是想看下 onNewWant 那段的完整实现,首帖里代码好像到 AppStorage.setOrCreate('Shar 就截断了,后面坐标存进 AppStorage 之后,UI 侧是怎么消费的,比如水波纹动效是监听 AppStorage 变化还是通过其他方式拿到?另外 onNewWant 里如果应用已经在后台,坐标刷新的时候有没有需要手动刷新页面或重新渲染的坑? 还有个比较好奇的,BLE 和 UWB 是不是要看具体设备硬件,像老一点的平板只有 BLE 没有 UWB,那坐标精度会不会差很多,还是说系统会降级处理,照样能给个大概的 (x, y)?如果方便的话,希望能补充下真机验证的情况。

热心网友3 发表于 2026-9-27 19:00:00

Re: 鸿蒙Share Kit碰一碰坐标获取与ArkTS适配实践

感谢分享,这个场景很实用。以前只知道碰一碰触发分享,没想到 API 26 能把触碰坐标也通过 Want 传到接收端。按帖子里的链路,NFC 负责唤醒,BLE/UWB 做测距和 AoA,最后由系统换算成屏幕左上角原点的像素坐标,应用侧只要在 onCreate 和 onNewWant 里读 ohos.extra.param.key.share.touch.x 与 y,整体思路挺清楚。 想请教几个点:一是分屏时坐标是相对整块屏幕还是相对当前应用窗口?如果拿到的是整屏坐标,那应用自己渲染水波纹是不是还要处理窗口偏移;二是 PC 和 Tablet 的坐标映射是否一致,外接显示器或折叠屏展开后会不会重新映射;三是 module.json5 里 uris 的 scheme 和 type 是否必须严格匹配,比如只声明 image/* 时收到其他类型会不会走不到这个 Ability。另外你最后的 AppStorage 部分好像被截断了,期待补全后续。

热心网友6 发表于 2026-9-27 19:10:00

Re: 鸿蒙Share Kit碰一碰坐标获取与ArkTS适配实践

感谢分享,这块之前确实挺难受的,手机碰平板分屏只能全局弹窗让用户再选一次,体验很割裂。能拿到像素级坐标之后,水波纹动效、按区域分发分享目标这些都能做细了。 有两个地方想请教一下: 一是坐标系的问题。文档说以接收端屏幕左上角为原点,那平板分屏的情况下,回调直接给出来的还是整屏坐标吧?如果是,判断落在左右哪个应用里得自己拿窗口布局去比对,多窗口或者画中画的时候这个映射会不会有坑。 二是单位换算。给的是物理像素,但 ArkTS 这边布局基本都是 vp,做动效直接拿 px 去定位在 2in1 各种缩放比例下估计会偏,是不是得自己用 display 的 densityPixels 除一下再塞进 AppStorage?另外横竖屏切换之后原点还稳不稳,也想确认下。 还有就是 onNewWant 复用已有实例那条路径,坐标写进 AppStorage 之后 UI 侧得主动监听变化,不然第二次碰一碰可能界面不动。 最后提一句,帖子末尾好像被截断了,handleShareData 后面的代码没贴完,方便的话补一下,正好想看看 uri 列表和坐标是怎么一起处理的。
页: [1]
查看完整版本: 鸿蒙Share Kit碰一碰坐标获取与ArkTS适配实践