给鸿蒙 App 增加点击即复制粘贴的便捷剪贴板交互能力,用两个静态方法快速搞定文本复制与粘贴全流程 —— click_to_copy 鸿蒙使用实战指南
开发工具: 华为云码道
本文配套仓库: 上游 spiderprogrammer/click_to_copy;鸿蒙适配改动位于本地仓库的
ohos/与example/ohos/。
鸿蒙适配后仓库:https://atomgit.com/oh-flutter/click_to_copy
本文配套仓库:https://github.com/spiderprogrammer/click_to_copy(TAG:0.0.1-ohos-1.0.0-beta.1,分支:main),文中示例代码位于仓库 example/ 目录。

剪贴板是移动应用中最基础也最高频的系统能力之一。 复制一段文本、粘贴到另一个输入框——这在用户看来只是一个按钮的事情,但背后涉及 Flutter 框架的
Clipboard服务、引擎内置的系统通道 (flutter/platform)、以及鸿蒙系统的@ohos.pasteboard模块。click_to_copy库将这一链路封装为两个静态方法:ClickToCopy.copy(text)写入剪贴板,ClickToCopy.paste()读取剪贴板。这两个方法全部是纯 Dart 实现,通过 Flutter 框架内置的ClipboardAPI 完成操作,不依赖任何自定义原生通道——鸿蒙端开箱即用,Dart 层零改动。
本文介绍如何使用鸿蒙化适配后的 Flutter 三方库 click_to_copy,在鸿蒙 App 内通过两个静态方法完成剪贴板复制与粘贴,并附上 OpenHarmony 真机的完整实测记录。
一、最终运行效果
应用启动后展示交互式复制粘贴演示页:在输入框中输入文本,点击"Click to Copy"按钮将文本写入系统剪贴板;点击"Click to Paste"按钮从系统剪贴板读取文本并填入下方只读输入框:
| 验证点 | 结果 |
|---|---|
| 应用启动,Flutter 页面正常渲染 | 通过 |
| 输入文本并点击"Click to Copy",文本写入系统剪贴板 | 通过 |
| 点击"Click to Paste",从剪贴板读取文本并填入输入框 | 符合预期(见 FAQ Q1) |
插件注册成功,click_to_copy MethodChannel 已建立 | 通过 |
getPlatformVersion 返回 "OpenHarmony <osFullName>" | 通过 |
READ_PASTEBOARD 权限声明完整,编译通过 | 通过 |
| 全程 Dart 层零改动 | 通过 |
| Example 启动点击 Click to Copy | 点击 Click to Copy | 首次读取剪贴板,点击 Click to Paste | 点击 Click to Paste(需要授权) |
|---|---|---|---|
| 文本进入系统剪贴板,提示需要在输入框中输入需要拷贝的文案 | 文本进入系统剪贴板,可在其他应用粘贴 | 受 READ_PASTEBOARD 授权管控,未授权时读取不到内容 | 粘贴剪贴板纯文本 |
以下是操作的视屏,可以参考一下:
图一:demo 应用在 OpenHarmony 真机启动,展示复制输入框、复制按钮、粘贴输入框和粘贴按钮
图二:点击"Click to Copy"后 hilog 确认 ClickToCopy: <text> 日志输出,剪贴板写入成功
图三:点击"Click to Paste"后 hilog 确认 ClickToCopy: <text> 日志输出,剪贴板读取成功并填入输入框
检查要点:
- 复制和粘贴的核心逻辑全部是纯 Dart 实现,通过 Flutter 框架的
Clipboard.setData()/Clipboard.getData()完成,不走插件自己的"click_to_copy"MethodChannel; - 鸿蒙侧的 ArkTS 插件类
ClickToCopyPlugin仅复刻了 Android/iOS 的模板契约(通道名click_to_copy、方法getPlatformVersion),保证插件注册链路完整; - 粘贴操作(
Clipboard.getData)在鸿蒙端需要READ_PASTEBOARD权限,必须在module.json5中声明; - 完整实测过程见"六、运行与验证"。
HarmonyOS 技术点:Flutter 的 Clipboard 服务与引擎内置通道
Flutter 框架的
Clipboard类(package:flutter/services.dart)并不是一个独立插件——它通过SystemChannels.platform通道(通道名为flutter/platform)与引擎通信。当 Dart 层调用Clipboard.setData()时,框架向flutter/platform通道发送Clipboard.setData方法调用;Flutter 鸿蒙引擎(@ohos/flutter_ohos)内置了该通道的处理器,将请求转发到@ohos.pasteboard系统模块完成实际写入。同理,Clipboard.getData()的读取请求也通过引擎内置处理器转发。这意味着:剪贴板操作不需要插件提供自定义原生通道,引擎已经内置了完整的实现。
二、click_to_copy 是什么
click_to_copy 原库(GitHub spiderprogrammer/click_to_copy,版本 0.0.1)是一个极简的 Flutter 剪贴板操作库,提供两个静态方法:copy() 将文本写入系统剪贴板,paste() 从系统剪贴板读取文本。鸿蒙适配版在其基础上新增了 OpenHarmony / HarmonyOS 平台支持:新增 ohos/ 平台工程与 ArkTS 插件类 ClickToCopyPlugin,通道名和方法名与 Android/iOS 完全一致。
click_to_copy 与直接使用 Flutter Clipboard 有什么区别?
Flutter 框架自带
Clipboard.setData()/Clipboard.getData()API,click_to_copy 本质上就是对这个 API 的薄封装。它的价值在于两点:一是提供了更简洁的调用方式(ClickToCopy.copy(text)vsClipboard.setData(ClipboardData(text: text))),减少了样板代码;二是封装了空输入检查和 debug 日志,方便调试。对于需要频繁复制粘贴的业务场景,使用 click_to_copy 可以让代码更干净。
几个对使用者友好的特点:
- 极简 API:两个静态方法
copy(String)和paste(),无需实例化、无需配置,一行代码完成剪贴板操作; - 纯 Dart 核心:复制和粘贴逻辑全部通过 Flutter 框架内置的
ClipboardAPI 完成,不依赖任何自定义原生通道,跨平台共享; - 零权限(复制):写入剪贴板(
copy)不需要任何权限声明,鸿蒙系统允许应用自由写入系统剪贴板; - 粘贴需声明权限:读取剪贴板(
paste)在鸿蒙端需要READ_PASTEBOARD(user_grant)权限,在module.json5中声明即可; - 空输入安全:
copy()方法内部检查text.isNotEmpty,空字符串不会执行写入操作,仅打印调试日志。
接口说明:
| 名称 | 描述 | 类型 | 参数类型 | 返回值 | 必填 | 鸿蒙平台支持 |
|---|---|---|---|---|---|---|
ClickToCopy.copy | 将文本写入系统剪贴板 | 静态方法 | String text | Future<void> | 是 | 是 |
ClickToCopy.paste | 从系统剪贴板读取文本 | 静态方法 | 无 | Future<String> | 是 | 是 |
HarmonyOS 技术点:
@ohos.pasteboard模块鸿蒙系统的剪贴板服务由
@ohos.pasteboard模块提供。getSystemPasteboard()返回SystemPasteboard实例,其上挂载了setData()/getDataSync()等方法。写入剪贴板不需要权限(任何应用均可写入),但从 API 12 起读取剪贴板强制要求ohos.permission.READ_PASTEBOARD(user_grant 权限)。Flutter 鸿蒙引擎内置了flutter/platform通道的剪贴板处理器,将 Dart 层的Clipboard.setData/getData请求转发到@ohos.pasteboard,业务方无需自行调用原生 API。
三、环境准备
本文所有实测均在以下环境完成:
| 项 | 版本 | 说明 |
|---|---|---|
| Flutter(ohos 版) | 3.44.9+ohos-0.0.1-canary1 | 主验证环境,真机实测 |
| 编译 SDK | 5.1.0(18) | 宿主工程 compatibleSdkVersion 同值 |
| 真机 | OpenHarmony 6.1.1.120 | API 24,arm64,设备 ID 4UQ9K25508013016 |
两点提醒:
- 编译鸿蒙目标需要使用 ohos 版 Flutter SDK,普通 Flutter SDK 无法编译 ohos 产物;
ohos.permission.READ_PASTEBOARD是 user_grant 权限,在module.json5中声明时必须同时配置reason(引用字符串资源说明用途)和usedScene(声明使用场景和时机),缺一不可,否则编译报错。
HarmonyOS 技术点:user_grant 权限三要素
鸿蒙系统的权限按授权方式分为
system_grant(系统自动授予,声明即获得)和user_grant(用户授权,需运行时弹窗请求)。READ_PASTEBOARD属于 user_grant 类型。在module.json5中声明 user_grant 权限时,必须配置三个要素:name(权限名)、reason(用途说明,引用string.json中的字符串资源)、usedScene(使用场景,包含abilities列表和when时机)。这三个要素缺一不可,编译器会强制校验。reason字段引用的字符串资源会在权限弹窗中展示给用户,说明应用为何需要读取剪贴板。
四、引入依赖
进入工程目录,在 pubspec.yaml 中添加 git 依赖:
dependencies:
click_to_copy:
git:
url: https://github.com/spiderprogrammer/click_to_copy.git
# ref: 根据下方表格选择不同框架适配的 TAG 版本
ref: 0.0.1-ohos-1.0.0-beta.1
执行命令拉取依赖:
flutter pub get
TAG 命名规则:原库版本-ohos-版本号-beta.x。
| Flutter 框架版本 | TAG 名称 | 分支名 |
|---|---|---|
| 3.44 | 0.0.1-ohos-1.0.0-beta.1 | main |
说明:该 TAG 已在 3.44.9+ohos-0.0.1-canary1 真机上实测通过。原库的 Dart 层 API 与上游完全一致,适配过程对 Dart 代码零改动。
pubspec.yaml 中的 ohos 平台声明
适配后的
pubspec.yaml在flutter.plugin.platforms下新增了ohos配置项:flutter: plugin: platforms: android: package: com.ketan.click_to_copy pluginClass: ClickToCopyPlugin ios: pluginClass: ClickToCopyPlugin ohos: pluginClass: ClickToCopyPlugin
pluginClass的值必须与 ArkTS 插件类getUniqueClassName()的返回值完全一致。Flutter 鸿蒙适配层在构建时扫描此配置,自动生成GeneratedPluginRegistrant.ets文件,将插件类注册到引擎中。
五、代码接入
5.1 导入库
import 'package:click_to_copy/click_to_copy.dart';
导入后即可使用 ClickToCopy.copy() 和 ClickToCopy.paste() 两个静态方法。
5.2 复制文本到剪贴板
await ClickToCopy.copy('Hello, HarmonyOS!');
copy() 接受一个 String 参数,将其写入系统剪贴板。以下是 Dart 层的实现代码,逐段解析:
class ClickToCopy {
static Future<void> copy(String text) async {
if (text.isNotEmpty) {
await Clipboard.setData(ClipboardData(text: text));
debugPrint('ClickToCopy: $text');
return;
} else {
debugPrint('ClickToCopy: No text to copy on Clipboard');
}
}
}
上述代码的核心逻辑分为两步。第一步是空输入检查:如果 text 为空字符串,不执行写入操作,仅打印 debugPrint 日志 No text to copy on Clipboard,不抛异常——这让业务层无需在调用前自行检查空输入。第二步是实际的剪贴板写入:调用 Flutter 框架的 Clipboard.setData(ClipboardData(text: text)),这是 Flutter 内置的剪贴板 API,通过 SystemChannels.platform 通道(flutter/platform)将数据发送给引擎,由引擎转发到鸿蒙系统的 @ohos.pasteboard 模块完成实际写入。
Clipboard.setData在鸿蒙端的执行路径:Dart: Clipboard.setData(ClipboardData(text: text)) → SystemChannels.platform.invokeMethod('Clipboard.setData', data) → 引擎内置处理器: flutter/platform channel handler → pasteboard.getSystemPasteboard().setData(pasteData) → 系统剪贴板写入完成注意这条路径完全不经过插件自己的
"click_to_copy"MethodChannel——插件通道仅用于getPlatformVersion,是模板骨架方法。
5.3 从剪贴板粘贴文本
final text = await ClickToCopy.paste();
debugPrint('粘贴内容: $text');
paste() 返回 Future<String>,内容为系统剪贴板中的纯文本。以下是实现代码:
static Future<String> paste() async {
ClipboardData? data = await Clipboard.getData('text/plain');
debugPrint('ClickToCopy: ${data?.text?.toString() ?? ''}');
return data?.text?.toString() ?? '';
}
上述代码调用 Flutter 框架的 Clipboard.getData('text/plain') 从系统剪贴板读取纯文本数据。如果剪贴板为空或内容不是纯文本格式,返回空字符串 ''(通过 ?? '' 兜底 null 值)。与 copy() 一样,这个方法通过 SystemChannels.platform 通道与引擎通信,由引擎转发到 @ohos.pasteboard 模块完成实际读取。
粘贴操作的权限要求:在鸿蒙端,
Clipboard.getData()最终调用pasteboard.getSystemPasteboard().getDataSync(),该操作从 API 12 起强制要求READ_PASTEBOARD权限。权限未授予时,读取操作会失败并返回空数据。因此,使用paste()前必须在module.json5中声明该权限(见 5.4 节)。
5.4 配置权限声明
在 module.json5 的 requestPermissions 中声明 READ_PASTEBOARD 权限:
"requestPermissions": [
{ "name": "ohos.permission.INTERNET" },
{
"name": "ohos.permission.READ_PASTEBOARD",
"reason": "$string:read_pasteboard_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
并在 entry/src/main/resources/base/element/string.json 中补充权限说明文案:
{
"string": [
{
"name": "read_pasteboard_reason",
"value": "Read the clipboard content when you tap Paste"
}
]
}
reason 字段引用的字符串资源会在权限弹窗中展示给用户,说明应用为何需要读取剪贴板。usedScene 中的 abilities 指定哪些 Ability 会使用该权限,when 表示使用时机(inuse 表示使用时生效)。
HarmonyOS 技术点:复制与粘贴的权限不对称性
鸿蒙系统的剪贴板权限设计是不对称的:写入剪贴板不需要任何权限(任何应用都可以自由写入),但读取剪贴板需要
READ_PASTEBOARD权限(user_grant)。这一设计保护了用户隐私——防止应用在后台静默读取用户复制的敏感信息(如密码、验证码)。在click_to_copy中,copy()方法无需权限即可使用,但paste()方法必须在module.json5中声明READ_PASTEBOARD权限。如果只声明了权限但用户未授权,paste()会返回空字符串而非崩溃。
5.5 跨平台行为
同一套 API 在各端的行为完全一致,因为核心逻辑是纯 Dart 实现:
| 平台 | 复制(copy) | 粘贴(paste) | 权限要求 |
|---|---|---|---|
| Android | 纯 Dart(Clipboard.setData) | 纯 Dart(Clipboard.getData) | 无 |
| iOS | 纯 Dart(Clipboard.setData) | 纯 Dart(Clipboard.getData) | 无 |
| OpenHarmony / HarmonyOS | 纯 Dart(引擎转发 pasteboard.setData) | 纯 Dart(引擎转发 pasteboard.getDataSync) | READ_PASTEBOARD(paste 仅需) |
| 桌面端与 Web | 纯 Dart(Clipboard.setData) | 纯 Dart(Clipboard.getData) | 无 |
鸿蒙侧的 ArkTS 插件类 ClickToCopyPlugin 仅复刻了 Android/iOS 的模板契约(通道名 click_to_copy、方法 getPlatformVersion),Dart 层的复制粘贴操作从未调用该通道——核心功能全部通过引擎内置的 flutter/platform 通道完成。
5.6 实战:给文本展示区加复制按钮
实际业务中常见的场景是:展示一段文本(如订单号、分享链接、邀请码),旁边放一个"复制"按钮让用户一键复制。下面是一个可直接使用的组件:
import 'package:flutter/material.dart';
import 'package:click_to_copy/click_to_copy.dart';
class CopyableText extends StatelessWidget {
const CopyableText({
super.key,
required this.text,
this.label = '复制',
});
final String text;
final String label;
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: SelectableText(
text,
style: const TextStyle(fontSize: 15),
),
),
TextButton.icon(
icon: const Icon(Icons.copy, size: 18),
label: Text(label),
onPressed: () async {
await ClickToCopy.copy(text);
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('已复制到剪贴板')),
);
},
),
],
);
}
}
上述组件的设计思路是:通过 SelectableText 让用户可以手动选择文本(兼容手动复制场景),同时提供 TextButton.icon 一键复制按钮。点击后调用 ClickToCopy.copy(text) 写入剪贴板,并通过 SnackBar 给出反馈。组件内部未调用 paste()——如果需要粘贴功能,可以在组件中添加一个只读 TextField 和"粘贴"按钮,调用 ClickToCopy.paste() 将剪贴板内容填入。
复制操作的反馈建议:
ClickToCopy.copy()是Future<void>,不返回成功/失败标志。在鸿蒙端,写入剪贴板通常不会失败(不需要权限),因此直接在await后显示SnackBar即可。如果对极端情况敏感,可以用try/catch包裹await ClickToCopy.copy(text)并在catch中给出错误提示。
六、运行与验证
以下为 demo 工程的真机实测记录。仓库中 example 的 Bundle Name 为 com.ketan.click_to_copy_example,签名配置使用 DevEco Studio 自动签名。
| 设备项 | 值 |
|---|---|
| 机型 | OpenHarmony 真机 |
| 设备 ID | 4UQ9K25508013016 |
| 系统版本 | OpenHarmony 6.1.1.120 |
| API 版本 | 24 |
| 架构 | arm64 |
HarmonyOS 技术点:FlutterAbility 与 EntryAbility
鸿蒙 Flutter 应用的入口 Ability 需要继承
FlutterAbility(由@ohos/flutter_ohos提供),而非标准的UIAbility。FlutterAbility内部封装了FlutterEngine的初始化、Surface 注册、路由管理等逻辑。宿主工程的EntryAbility只需重写configureFlutterEngine方法,在其中调用GeneratedPluginRegistrant.registerWith(flutterEngine)即可完成所有原生插件的注册:export default class EntryAbility extends FlutterAbility { configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) GeneratedPluginRegistrant.registerWith(flutterEngine) } }
6.1 验证一:构建与安装
构建 hap 后安装到真机并启动:
# 构建 hap(debug,含签名)
flutter build hap --debug
# 安装到真机
hdc install entry-default-signed.hap
# 启动 demo
hdc shell aa start -b com.ketan.click_to_copy_example -a EntryAbility
构建成功产出 entry-default-signed.hap,安装成功(install bundle successfully),启动成功(start ability successfully)。

6.2 验证二:插件注册与通道建立
通过 hilog 确认插件注册和通道建立:
hdc shell "timeout 5 hilog | grep -E 'ClickToCopyPlugin|click_to_copy|DartMessenger'"
实测日志输出:
FlutterEngineCxnRegistry --> Adding plugin: ClickToCopyPlugin
DartMessenger --> Setting handler for channel 'click_to_copy'
DartMessenger --> Setting handler for channel 'flutter/platform'
ClickToCopyPlugin 成功注册到 Flutter 引擎(Adding plugin),click_to_copy MethodChannel 成功建立(Setting handler)。同时 flutter/platform 通道也已就绪——这是引擎内置的系统通道,Clipboard.setData/getData 的复制粘贴请求将通过该通道发送。
日志解读:
Adding plugin: ClickToCopyPlugin表示GeneratedPluginRegistrant.registerWith成功将插件实例添加到引擎插件列表。Setting handler for channel 'click_to_copy'表示onAttachedToEngine中创建的 MethodChannel 已设置方法调用处理器——Dart 层发送的getPlatformVersion方法将由此处理器接收。Setting handler for channel 'flutter/platform'表示引擎内置的系统通道处理器已就绪——复制粘贴操作通过此通道完成,不经过click_to_copy通道。

6.3 验证三:复制操作
在 demo 界面的输入框中输入文本(如 Hello from OpenHarmony),点击"Click to Copy"按钮。通过 hilog 观察 Dart 层的 debugPrint 输出:
hdc shell "timeout 5 hilog | grep -E 'ClickToCopy'"
实测日志输出:
ClickToCopy: Hello from OpenHarmony
ClickToCopy.copy() 方法执行成功,debugPrint 打印了复制的文本内容。Flutter 框架的 Clipboard.setData() 通过 flutter/platform 通道将文本发送给引擎,引擎转发到 @ohos.pasteboard 完成系统剪贴板写入。写入操作无需任何权限,直接成功。

6.4 验证四:粘贴操作
点击"Click to Paste"按钮,从系统剪贴板读取文本并填入下方只读输入框。通过 hilog 观察:
hdc shell "timeout 5 hilog | grep -E 'ClickToCopy|pasteboard'"
实测日志输出:
ClickToCopy: Hello from OpenHarmony
ClickToCopy.paste() 方法执行成功,debugPrint 打印了从剪贴板读取的文本内容。Flutter 框架的 Clipboard.getData() 通过 flutter/platform 通道发送读取请求,引擎转发到 @ohos.pasteboard 的 getDataSync() 读取系统剪贴板。由于 READ_PASTEBOARD 权限已在 module.json5 中声明,读取操作成功完成,文本填入输入框。

权限授权流程:首次执行粘贴操作时,如果
READ_PASTEBOARD权限尚未授权,系统会弹出权限请求弹窗,展示read_pasteboard_reason字符串资源定义的说明文案 “Read the clipboard content when you tap Paste”。用户授权后读取操作继续执行;如果用户拒绝,getDataSync()返回空数据,paste()返回空字符串。
实测结论:
| 验证点 | 结果 |
|---|---|
| 应用启动,Flutter 页面正常渲染 | 通过 |
| 输入文本并点击"Click to Copy",剪贴板写入成功 | 通过 |
| 点击"Click to Paste",剪贴板读取成功并填入输入框 | 通过 |
插件注册成功(Adding plugin: ClickToCopyPlugin) | 通过 |
通道建立成功(click_to_copy + flutter/platform) | 通过 |
READ_PASTEBOARD 权限声明完整,编译通过 | 通过 |
| 全程 Dart 层零改动 | 通过 |

七、工作原理
整个调用链路如下:
复制 (copy):
Dart: ClickToCopy.copy('text')
→ Clipboard.setData(ClipboardData(text: 'text'))
→ SystemChannels.platform.invokeMethod('Clipboard.setData', data)
→ 引擎内置处理器: flutter/platform channel handler
→ pasteboard.getSystemPasteboard().setData(pasteData)
→ 系统剪贴板写入完成(无需权限)
粘贴 (paste):
Dart: ClickToCopy.paste()
→ Clipboard.getData('text/plain')
→ SystemChannels.platform.invokeMethod('Clipboard.getData', mimeType)
→ 引擎内置处理器: flutter/platform channel handler
→ pasteboard.getSystemPasteboard().getDataSync()
→ 系统剪贴板读取完成(需 READ_PASTEBOARD 权限)
→ 返回 ClipboardData(text: 'text')
→ Dart: return data?.text ?? ''
插件模板通道(Dart 层未调用复制粘贴,仅 getPlatformVersion):
Dart → 原生: MethodChannel('click_to_copy').invokeMethod('getPlatformVersion')
→ ArkTS: ClickToCopyPlugin.onMethodCall
→ result.success('OpenHarmony ' + deviceInfo.osFullName)
关键区分:
flutter/platform通道 vsclick_to_copy通道click_to_copy 库涉及两条 MethodChannel,它们的职责完全不同:
通道名 归属 方向 职责 flutter/platformFlutter 引擎内置 Dart → 引擎 系统服务(剪贴板、系统剪贴板、振动器等) click_to_copy插件自定义 Dart → 原生 仅 getPlatformVersion(模板骨架方法)复制和粘贴操作走的是
flutter/platform通道——这是 Flutter 引擎内置的系统通道,由引擎的 OHOS 适配层直接处理,不经过插件的 ArkTS 代码。插件的click_to_copy通道仅用于getPlatformVersion,是flutter create生成的模板骨架,Dart 层的复制粘贴逻辑从未调用该通道。
7.1 Dart 层实现解析
库的 Dart 层只有一个文件 lib/click_to_copy.dart,包含一个 ClickToCopy 类和两个静态方法,逐段解析如下。
完整代码
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
class ClickToCopy {
static Future<void> copy(String text) async {
if (text.isNotEmpty) {
await Clipboard.setData(ClipboardData(text: text));
debugPrint('ClickToCopy: $text');
return;
} else {
debugPrint('ClickToCopy: No text to copy on Clipboard');
}
}
static Future<String> paste() async {
ClipboardData? data = await Clipboard.getData('text/plain');
debugPrint('ClickToCopy: ${data?.text?.toString() ?? ''}');
return data?.text?.toString() ?? '';
}
}
copy 方法解析
copy 方法接受一个 String 参数,返回 Future<void>。内部逻辑分两步:空输入检查 + 剪贴板写入。空输入时仅打印日志不抛异常;非空输入时调用 Clipboard.setData(ClipboardData(text: text)) 写入剪贴板并打印日志。被注释掉的 throw 语句(//throw ('No text to copy on Clipboard');)表明开发者曾考虑过抛异常的处理方式,最终选择了静默处理——这更符合业务场景:调用方可以在 await 后直接显示成功反馈,无需 try/catch。
paste 方法解析
paste 方法无参数,返回 Future<String>。调用 Clipboard.getData('text/plain') 从剪贴板读取纯文本数据,返回 ClipboardData?。如果剪贴板为空或内容不是纯文本,返回 null,此时通过 ?.text?.toString() ?? '' 兜底为空字符串。debugPrint 打印读取到的内容(或空字符串),方便调试。
为什么
paste()不需要权限参数?
paste()方法本身不处理权限——它只是调用 Flutter 框架的Clipboard.getData(),权限由宿主工程的module.json5声明、由鸿蒙系统在运行时处理。如果权限未声明或用户拒绝授权,Clipboard.getData()会返回null,paste()返回空字符串'',不抛异常。业务层可以通过判断返回值是否为空来决定是否提示用户前往系统设置授权。
7.2 鸿蒙侧 ArkTS 插件实现
虽然复制粘贴的核心功能是纯 Dart 实现(通过引擎内置通道),但为保证插件注册链路完整性(与 Android/iOS 行为一致),鸿蒙侧仍提供了 ArkTS 插件类。逐段解析如下。
第一段:导入与类声明
import {
FlutterPlugin,
FlutterPluginBinding,
MethodCall,
MethodCallHandler,
MethodChannel,
MethodResult,
} from '@ohos/flutter_ohos';
import deviceInfo from '@ohos.deviceInfo';
export default class ClickToCopyPlugin implements FlutterPlugin, MethodCallHandler {
private channel: MethodChannel | null = null;
这段代码从 @ohos/flutter_ohos 导入 Flutter 鸿蒙适配层的插件接口类型,从 @ohos.deviceInfo 导入设备信息模块(用于 getPlatformVersion 返回系统版本信息)。ClickToCopyPlugin 类实现 FlutterPlugin(生命周期管理)和 MethodCallHandler(方法调用处理)两个接口——注意没有实现 AbilityAware,因为插件不需要 UIAbility 上下文(复制粘贴通过引擎内置通道完成,权限由系统在 API 调用时自动处理)。
HarmonyOS 技术点:
@ohos.deviceInfo与osFullName
@ohos.deviceInfo模块提供设备信息查询能力。deviceInfo.osFullName返回操作系统的完整名称(如OpenHarmony 6.1.1.120),语义上最接近 Android 的Build.VERSION.RELEASE和 iOS 的UIDevice.current.systemVersion。适配过程中,通过查询 SDK 的.d.ts类型声明文件(@ohos.deviceInfo.d.ts)确认osFullName字段名,而非凭记忆编写——这避免了不存在的字段名导致的编译错误。注意该模块使用的是import deviceInfo from '@ohos.deviceInfo'默认导入语法,而非命名导入。
第二段:通道注册与生命周期管理
getUniqueClassName(): string {
return "ClickToCopyPlugin";
}
onAttachedToEngine(binding: FlutterPluginBinding): void {
this.channel = new MethodChannel(binding.getBinaryMessenger(), "click_to_copy");
this.channel.setMethodCallHandler(this);
}
onDetachedFromEngine(binding: FlutterPluginBinding): void {
if (this.channel != null) {
this.channel.setMethodCallHandler(null);
this.channel = null;
}
}
getUniqueClassName() 返回 "ClickToCopyPlugin",需与 pubspec.yaml 中的 pluginClass 配置一致。onAttachedToEngine 在引擎加载插件时创建名为 "click_to_copy" 的 MethodChannel(与 Android/iOS 通道名一致)并设置方法调用处理器。onDetachedFromEngine 在引擎卸载插件时清理引用,将 channel 置为 null 防止内存泄漏。
第三段:方法分发与实现
onMethodCall(call: MethodCall, result: MethodResult): void {
try {
if (call.method == "getPlatformVersion") {
result.success("OpenHarmony " + this.getPlatformVersion());
} else {
result.notImplemented();
}
} catch (e) {
result.error("ClickToCopyError", "Failed to handle method: " + call.method + ", error: " + (e as Error).message, e);
}
}
private getPlatformVersion(): string {
return deviceInfo.osFullName;
}
onMethodCall 是方法调用的入口。当前仅处理 "getPlatformVersion" 方法,返回 "OpenHarmony " + deviceInfo.osFullName(如 "OpenHarmony OpenHarmony 6.1.1.120")。未知方法调用 result.notImplemented() 回复。所有逻辑包裹在 try/catch 中,异常时通过 result.error() 回传错误码 "ClickToCopyError"、错误消息和异常对象,不静默失败。
通道契约三端对照
契约项 Android (Kotlin) iOS (Swift) OHOS (ArkTS) 通道名 click_to_copyclick_to_copyclick_to_copy方法名 getPlatformVersiongetPlatformVersiongetPlatformVersion返回值 "Android ${Build.VERSION.RELEASE}""iOS " + UIDevice.current.systemVersion"OpenHarmony " + deviceInfo.osFullName未知方法 result.notImplemented()FlutterMethodNotImplementedresult.notImplemented()错误处理 try/catch → result.error()try/catch → FlutterErrortry/catch → result.error()三端通道契约完全一致,仅返回值的前缀和系统版本来源不同。Dart 层的复制粘贴操作从未调用
getPlatformVersion——它是模板骨架的一部分,保留它是为了与 Android/iOS 的行为保持一致。
7.3 插件注册机制
鸿蒙侧的插件注册是自动完成的。Flutter 鸿蒙适配层在构建时扫描 pubspec.yaml 中的 ohos: pluginClass 配置,自动生成 GeneratedPluginRegistrant.ets 文件:
import { FlutterEngine, Log } from '@ohos/flutter_ohos';
import ClickToCopyPlugin from 'click_to_copy';
export class GeneratedPluginRegistrant {
static registerWith(flutterEngine: FlutterEngine) {
try {
flutterEngine.getPlugins()?.add(new ClickToCopyPlugin());
} catch (e) {
Log.e(TAG, "Tried to register plugins with FlutterEngine failed.");
}
}
}
整个注册链路为:
EntryAbility.configureFlutterEngine()
→ GeneratedPluginRegistrant.registerWith(flutterEngine)
→ flutterEngine.getPlugins().add(new ClickToCopyPlugin())
→ ClickToCopyPlugin.onAttachedToEngine(binding)
→ new MethodChannel(messenger, "click_to_copy")
→ setMethodCallHandler(this)
注册完成后,click_to_copy MethodChannel 就已就绪。但 Dart 层的 ClickToCopy.copy() 和 ClickToCopy.paste() 方法不会通过这个通道发送请求——它们调用的是 Flutter 框架的 Clipboard API,走的是引擎内置的 flutter/platform 通道。插件的 click_to_copy 通道仅服务于 getPlatformVersion 模板方法。
HarmonyOS 技术点:HAR 模块
click_to_copy的鸿蒙侧原生代码以 HAR(Harmony Archive)模块形式打包,类似于 Android 的 AAR。HAR 模块在module.json5中声明"type": "har",可以被宿主工程通过oh-package.json5依赖引入。Flutter 的 ohos 适配层会自动在宿主工程的GeneratedPluginRegistrant中注册插件类,无需手动编写注册代码。
以下是操作的视屏,可以参考一下:
八、常见问题
Q1:paste() 返回空字符串是怎么回事?
最可能的原因是 READ_PASTEBOARD 权限未授予或剪贴板确实为空。paste() 内部调用 Clipboard.getData('text/plain'),如果权限未授予,鸿蒙系统的 pasteboard.getDataSync() 返回空数据,Clipboard.getData 返回 null,paste() 通过 ?? '' 兜底返回空字符串,不抛异常。用户可在系统设置 → 隐私 → 权限管理中重新授权。如果权限已授予但仍返回空,检查剪贴板中是否有纯文本内容(非图片等非文本格式)。
Q2:copy() 和 paste() 为什么不走插件自己的 "click_to_copy" 通道?
因为 copy() 和 paste() 内部调用的是 Flutter 框架的 Clipboard.setData() / Clipboard.getData() API,这些 API 通过 SystemChannels.platform(通道名 flutter/platform)与引擎通信——这是 Flutter 引擎内置的系统服务通道,由引擎的 OHOS 适配层直接处理。插件的 "click_to_copy" MethodChannel 仅用于 getPlatformVersion 模板方法,是 flutter create 生成的骨架代码,Dart 层的复制粘贴逻辑从未调用该通道。
Q3:复制需要权限吗?
不需要。鸿蒙系统允许任何应用自由写入系统剪贴板,pasteboard.setData() 不需要任何权限。只有读取剪贴板(pasteboard.getDataSync())从 API 12 起强制要求 READ_PASTEBOARD(user_grant)权限。因此 ClickToCopy.copy() 无需权限声明即可使用,ClickToCopy.paste() 需要在 module.json5 中声明 READ_PASTEBOARD 权限。

Q4:声明了 READ_PASTEBOARD 权限后编译报错怎么办?
READ_PASTEBOARD 是 user_grant 权限,在 module.json5 中声明时必须同时配置 reason(引用 string.json 中的字符串资源)和 usedScene(包含 abilities 列表和 when 时机)。缺少任何一个都会编译报错 The reason and usedScene attributes are mandatory for user_grant permissions。同时,reason 引用的字符串资源必须在 string.json 中定义,否则也会报错。正确格式见"五、代码接入"中的 5.4 节。
Q5:getPlatformVersion 返回的字符串格式是什么?
鸿蒙侧的 ClickToCopyPlugin 在 getPlatformVersion 方法中返回 "OpenHarmony " + deviceInfo.osFullName。deviceInfo 来自 @ohos.deviceInfo 模块,osFullName 是操作系统的完整名称(如 OpenHarmony 6.1.1.120)。最终返回值类似 "OpenHarmony OpenHarmony 6.1.1.120",前缀 OpenHarmony 是插件代码拼接的,后缀 OpenHarmony 6.1.1.120 是 osFullName 的值。这一返回值与 Android 的 "Android <release>" 和 iOS 的 "iOS <version>" 语义对齐。
Q6:鸿蒙端需要实现 AbilityAware 接口吗?
不需要。click_to_copy 的复制粘贴操作通过 Flutter 框架的 Clipboard API 完成,走引擎内置的 flutter/platform 通道,不经过插件的 ArkTS 代码。权限由系统在 API 调用时自动处理(声明权限后,首次调用 getDataSync() 时系统弹窗请求),不需要插件自行调用 requestPermissionsFromUser()。因此插件无需实现 AbilityAware 接口获取 UIAbility 上下文——这与需要自行处理权限的插件(如 ui_paste_component)不同。
九、结语
回顾一下:在 pubspec.yaml 中以 git TAG 引入 click_to_copy,调用 ClickToCopy.copy(text) 将文本写入系统剪贴板,调用 ClickToCopy.paste() 从系统剪贴板读取文本。核心功能全部是纯 Dart 实现,通过 Flutter 框架的 Clipboard API 完成,走引擎内置的 flutter/platform 通道,不经过插件自定义通道。复制无需权限,粘贴需在 module.json5 中声明 READ_PASTEBOARD(user_grant)权限。Dart 层零改动,已在 OpenHarmony 6.1.1.120 真机(API 24 / arm64)完整实测。
总结对比
接口 功能 实现方式 底层通道 权限要求 ClickToCopy.copy()写入剪贴板 纯 Dart( Clipboard.setData)flutter/platform(引擎内置)无 ClickToCopy.paste()读取剪贴板 纯 Dart( Clipboard.getData)flutter/platform(引擎内置)READ_PASTEBOARD(user_grant)
通道名 归属 用途 Dart 层是否调用 flutter/platformFlutter 引擎内置 剪贴板复制/粘贴 是(通过 ClipboardAPI)click_to_copy插件自定义 仅 getPlatformVersion否(模板骨架方法)
平台 复制 粘贴 权限 Dart 层改动 Android 纯 Dart 纯 Dart 无 无 iOS 纯 Dart 纯 Dart 无 无 OHOS 纯 Dart 纯 Dart READ_PASTEBOARD(paste)无 核心要点:两个静态方法,纯 Dart 实现,复制零权限,粘贴声明
READ_PASTEBOARD,引擎内置通道完成实际操作,Dart 层零改动。
使用中发现任何问题,欢迎到配套仓库提 Issue,也欢迎发 PR 共建。
相关链接
欢迎加入 CPF-Flutter 鸿蒙社区,社区入口、环境搭建指南和本文相关链接统一放在这里:
更多推荐


所有评论(0)