在这里插入图片描述

HarmonyKit | 鸿蒙新特性:pasteboard 剪贴板 API 集成与 promptAction 迁移指南

引言

剪贴板是移动应用中最重要的系统服务之一。无论是复制 Base64 编码结果、分享生成的 UUID,还是将时间戳转换结果粘贴到 IM 工具中,开发者工具集的几乎每一个功能都离不开对系统剪贴板的读写操作。HarmonyKit 的 CopyButton 组件封装了鸿蒙的 pasteboard 剪贴板 API 和 promptAction 提示 API,为所有 10 个工具提供了统一的"一键复制 + 操作反馈"体验。本文将深入解析 pasteboard API 的使用方式、promptAction 的迁移路径以及 CopyButton 组件的设计思路。

CopyButton 组件的完整实现

CopyButton 是 HarmonyKit 中复用率最高的组件——每个工具页面至少使用 2-3 次,整个项目中有超过 30 处调用。它封装了剪贴板写入和用户提示的完整流程:
在这里插入图片描述

import { pasteboard } from '@kit.BasicServicesKit';

@Component
export struct CopyButton {
  @Prop text: string;
  @Prop btnText: string = '复制';

  build() {
    Button(this.btnText)
      .fontSize(12)
      .height(32)
      .padding({ left: 12, right: 12 })
      .backgroundColor('#f0f0f0')
      .fontColor('#333333')
      .borderRadius(6)
      .onClick(() => {
        if (this.text.length > 0) {
          let data = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, this.text);
          let sysPasteboard = pasteboard.getSystemPasteboard();
          sysPasteboard.setData(data, (err) => {
            if (err) {
              this.getUIContext().getPromptAction().showToast({ message: '复制失败', duration: 1500 });
            } else {
              this.getUIContext().getPromptAction().showToast({ message: '已复制到剪贴板', duration: 1500 });
            }
          });
        }
      })
  }
}

CopyButton 使用 @Component 而非 @Entry 装饰——它永远作为其他页面的子组件存在,不会独立作为页面入口。两个 @Prop 属性提供了灵活性:text 是被复制的文本内容(必传),btnText 是按钮显示文本(默认为"复制")。

按钮使用低饱和度的灰色背景 #f0f0f0,与工具页面主色调 #f5f5f5 的浅灰背景形成微妙的层次感。12px 的字号在提供足够信息的同时不抢占视觉注意力,32px 的高度便于手指点击(符合 iOS 和 HarmonyOS 的 44px/40vp 最小触摸区域建议的接近值)。

@kit.BasicServicesKit:pasteboard 的 Kit 归属

剪贴板 API 属于 @kit.BasicServicesKit(基础服务 Kit),而非 @kit.ArkTS@kit.ArkUI。这是一个重要的分类——剪贴板是系统级服务,与 UI 框架无关,理论上可以在非 UI 线程中调用(虽然 HarmonyKit 不涉及多线程场景)。

引入 pasteboard 后,可以使用其几个核心 API:

  • pasteboard.createData(mimeType, data):创建一个剪贴板数据对象
  • pasteboard.getSystemPasteboard():获取系统剪贴板实例
  • sysPasteboard.setData(data, callback):将数据写入系统剪贴板
  • sysPasteboard.getData(callback):从系统剪贴板读取数据

在 HarmonyKit 中,由于工具都是"产出型"(生成结果供复制),因此只使用了 setData 写入操作。如果未来需要"粘贴板输入"功能(如从剪贴板读取文本自动填充到 TextArea 中),则需使用 getData 方法。

pasteboard.createData 的 MIME 类型

createData 的第一个参数是 MIME 类型。pasteboard.MIMETYPE_TEXT_PLAIN 是纯文本类型,对应 MIME text/plain。鸿蒙 pasteboard 还支持其他 MIME 类型:

  • pasteboard.MIMETYPE_TEXT_HTML:HTML 格式文本
  • pasteboard.MIMETYPE_TEXT_WANT:WANT 格式(鸿蒙特有的意图格式)
  • pasteboard.MIMETYPE_IMAGE_PNG:PNG 图片数据
  • pasteboard.MIMETYPE_IMAGE_JPEG:JPEG 图片数据

HarmonyKit 的工具输出均为纯文本(JSON 字符串、哈希值、Base64 编码等),因此统一使用 MIMETYPE_TEXT_PLAIN。这里有一个微妙的设计选择:虽然可以指定 MIMETYPE_TEXT_HTML 并将结果包装为 <pre> 标签以供富文本编辑器使用,但这样做会限制粘贴场景——许多输入框不支持 HTML 粘贴。选择纯文本保证了最大兼容性。

getSystemPasteboard 的单例模式

pasteboard.getSystemPasteboard() 返回系统剪贴板的单例实例。这是合理的设计——操作系统中只有一个剪贴板,所有应用共享。每次调用都返回同一个实例的引用,不会创建新的剪贴板对象。

在 CopyButton 的实现中,每次点击都调用 getSystemPasteboard() 而不是缓存实例。这是因为剪贴板状态可能在不同时间点有变化(例如用户切换到其他应用复制了内容),始终获取当前系统剪贴板实例更加可靠。此外,缓存实例对性能的收益可以忽略不计——getSystemPasteboard() 只是返回一个引用,没有重量级操作。

setData 的回调式 API

sysPasteboard.setData(data, callback) 使用回调函数异步通知写入结果:
在这里插入图片描述

sysPasteboard.setData(data, (err) => {
  if (err) {
    // 写入失败处理
  } else {
    // 写入成功处理
  }
});

err 参数在成功时为 undefined,失败时包含错误信息。这个回调模式是鸿蒙 API 的常见风格——类似于 Node.js 的错误优先回调(error-first callback)。不过鸿蒙已逐步推广 Promise 风格的异步 API,新版本的 pasteboard 可能提供 setData 的 Promise 版本。

CopyButton 对两种结果分别显示了不同的 Toast 提示:“复制失败"和"已复制到剪贴板”。这种明确的成功/失败反馈是良好 UX 的基础——用户不会疑惑"我刚刚点击了复制,成功了吗?"

promptAction 的迁移路径

this.getUIContext().getPromptAction().showToast() 这个调用链值得拆解。在鸿蒙的早期版本中,showToast 是通过 promptAction.showToast() 直接调用的全局方法。但在较新版本中,推荐通过 UIContext 获取 PromptAction 实例来调用。

这种迁移反映了鸿蒙 UI 框架的一个重要架构变更:将全局 API 上下文化,使每个 UI 实例拥有自己的 prompt action 上下文。这样做的好处是:

  1. 多窗口支持:每个窗口有自己的 UIContext,Toast 会显示在正确的窗口上
  2. 生命周期绑定:UIContext 随窗口销毁而释放,避免全局状态泄漏
  3. 测试友好:mock UIContext 比 mock 全局函数更容易

在 HarmonyKit 中,由于没有多窗口场景,getUIContext().getPromptAction().showToast() 的行为与旧的 promptAction.showToast() 完全一致。但使用新 API 是面向未来的正确选择。

showToast 的参数中,message 是显示文本,duration 是显示时长(毫秒)。默认的 1500ms 足以让用户阅读"已复制到剪贴板"这样简短的消息,又不会停留太久阻塞操作。

空文本保护

CopyButton 在复制操作前做了空文本检查:

if (this.text.length > 0) {
  // 执行复制
}

如果 this.text 为空字符串,按钮点击无任何效果——不复制、不提示。这是一种"静默忽略"策略:复制空内容没有意义,显示错误提示反而让用户困惑(“为什么复制失败?我什么都没做啊”)。在这个场景下,静默忽略优于显式报错。

但如果业务需要,也可以改为:当文本为空时,按钮显示为禁用态的灰色或其他视觉提示,让用户提前知道"当前没有可复制的内容"。CopyButton 选择不这样做,因为工具的输出区域本身就是条件渲染的(有结果时才显示),CopyButton 也只在有结果时出现,所以空文本情况极少触发。

CopyButton 的跨工具复用

在 HarmonyKit 的 10 个工具中,CopyButton 以两种模式被使用:

行内复制按钮:每个独立的结果行右侧有一个复制按钮,文本为"复制",复制该行的结果值。例如进制转换的四个进制结果每一行都有独立的复制按钮。

全部复制按钮:在列表型结果(UUID 列表、正则匹配列表)的底部,有一个 btnText='复制全部' 的按钮,文本为换行符拼接的全部结果。

这两种模式共享同一个 CopyButton 组件,通过 btnText 属性区分。这种设计体现了"通过参数化而非分支化"的组件复用哲学:同一个组件通过不同的参数表现出不同的外观和行为,而非在组件内部用 if 判断创建两个变体。

剪贴板 API 的权限问题

鸿蒙系统中,剪贴板操作通常不需要显式申请权限。对于 setData(写入剪贴板),系统认为是正常的用户交互行为(用户点击了复制按钮),不需要额外的权限声明。对于 getData(读取剪贴板),在某些鸿蒙版本中可能需要用户授权——因为读取剪贴板涉及隐私(可能读取到其他应用复制的内容)。

HarmonyKit 目前只使用 setData,因此不涉及权限申请。如果未来添加"从剪贴板粘贴"功能,需要在 module.json5 中声明相关权限,并在首次使用时触发系统权限弹窗。鸿蒙的权限体系分为 system_grant(系统授权,安装时授予)和 user_grant(用户授权,运行时弹窗),剪贴板读取通常属于 user_grant 类型。

剪贴板内容的生命周期

写入系统剪贴板的数据会一直保留,直到:

  1. 用户复制了新内容(覆盖)
  2. 系统重启(剪贴板数据通常存储在内存中)
  3. 某些系统清理操作

这意味着即使用户关闭了 HarmonyKit 应用,之前复制的内容仍然在剪贴板中可用——用户切换到其他应用后可以直接粘贴。这与 Web 剪贴板 API(navigator.clipboard.writeText)的异步特性不同,鸿蒙的 pasteboard 写入是持久性的。

从 promptAction 到 UIContext 的迁移细节

对于从旧版 HarmonyOS 迁移代码的开发者,以下是常见的 API 对照表:

旧 API(已弃用或即将弃用):

import { promptAction } from '@kit.ArkUI';
promptAction.showToast({ message: '已复制', duration: 1500 });

新 API(推荐):

this.getUIContext().getPromptAction().showToast({ message: '已复制', duration: 1500 });

关键变化在于:新 API 通过组件的 UIContext 实例获取 PromptAction,而非从全局模块导入。getUIContext() 在 Component 的成员方法中可用(包括 build() 和事件回调),因为 @Component 装饰的 struct 在运行时具有关联的 UIContext。

对于不在组件上下文中的代码(如 utils 类中的静态方法),无法直接使用 getUIContext()。如果需要在这些位置显示 Toast,需要将 UIContext 作为参数传入或使用全局回调模式。这是架构设计中需要注意的约束——UI 反馈逻辑应该放在组件层而非工具层。

Toast 的替代方案

showToast 是轻量级的操作反馈方式,但并非所有场景都适合。HarmonyKit 仅在"复制"这个高频微操作上使用 Toast,因为:

  • 复制操作的结果简单(成功/失败)
  • 用户需要即时反馈但不希望被打断
  • 反馈信息非常短(4-6 个字)

对于更复杂的反馈场景(如表单验证结果、操作确认),promptAction 还提供了 showDialog 方法。在 HarmonyKit 中,JSON 格式化的错误提示直接显示在页面内容区(红色文字),而不使用 Dialog 打断用户——因为格式化操作不是破坏性的,失败原因也很明确(JSON 格式不合法),不需要弹窗式的强制注意。

小结

CopyButton 组件虽然只有 30 行代码,但它串联了鸿蒙系统的两个核心 API——pasteboard(系统剪贴板服务)和 promptAction(用户操作反馈)。它的设计展示了几个值得借鉴的实践:

  1. 封装系统 API:将剪贴板操作封装在独立组件中,工具页面只需声明 CopyButton({ text: ... }) 即可享受完整的复制能力
  2. 参数化变体:通过 btnText 参数区分"复制"和"复制全部",避免组件内部分支
  3. API 向前兼容:使用 getUIContext().getPromptAction() 而非废弃的全局 API
  4. 静默忽略:对无意义的操作(复制空文本)选择静默忽略而非报错

pasteboard API 的使用门槛不高,但将其与 UI 反馈恰当结合、与组件复用模式匹配,就能从一个简单的"复制"按钮升华为整个工具集的基础设施。

项目仓库:https://atomgit.com/VON-/harmony-kit

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐