《HarmonyOS技术精讲-Basic Services Kit》划词服务:构建系统级文本选取能力
《HarmonyOS技术精讲-Basic Services Kit》划词服务构建系统级文本选取能力从一次文本交互的需求说起在 HarmonyOS NEXT 开发中如果我们希望用户选中一段文本后能弹出“翻译”、“搜索”、“复制”等自定义操作菜单一般会怎么处理很多人会想到监听手势获取光标位置然后自己实现一个浮动菜单。这样做的成本不止是 UI 层——还需要处理文本选中事件的回调、菜单的位置计算、以及在不同设备上的适配。但 HarmonyOS 提供了一套更接近系统级的方案划词服务SelectionInput。它允许开发者在系统默认的长按选中弹窗中注入自定义的操作项直接获取用户选中的文本内容并作出响应。这套 API 本身不复杂但实际开发中很多人在生命周期管理和菜单交互上踩了坑。官方示例虽然能运行但放到真实项目里至少还要处理两个问题页面返回时划词服务没有被正常释放导致其他页面也受影响。菜单点击后选中状态和 UI 更新不同步。这篇文章就从实现一个“划词翻译”功能入手把这些细节一次性讲清楚。它解决了什么适合什么场景划词服务的作用很简单让你的应用在文本被选中时弹出一个自定义菜单项。用户点击这个菜单项你的代码就可以直接拿到selectedText选中的文本然后做任何自定义处理——比如调用翻译 API、打开搜索引擎、保存到笔记等。和传统方案的对比方案实现复杂度交互一致性适用场景手动监听选中事件 自定义浮动菜单高需要处理手势、位置更新、多端适配差容易被系统键盘或弹窗遮挡非常灵活的自定义场景使用系统划词服务注入菜单低仅需配置菜单项和回调好菜单集成在系统原生弹窗中标准化的文本操作翻译、搜索、复制等划词服务适合的场景比较明确你需要用户在文本选中后执行一个固定的操作。如果操作逻辑不是固定的比如依赖其他页面状态就需要额外处理状态同步。不适合的场景需要大量自定义 UI 交互比如文本标注、划词备注、或者菜单需要响应式变化比如根据文本内容动态增删菜单项。环境说明DevEco Studio 版本DevEco Studio 6.1.0 及以上 HarmonyOS SDK 版本HarmonyOS 6.1.0(23) 及以上 目标设备手机核心实现创建一个划词翻译功能我们来实现一个功能用户在任意 Text 组件上长按选中文本弹窗中会多出一个“翻译”菜单项点击后调用远程翻译 API并显示翻译结果。Step 1配置权限和依赖翻译功能需要网络请求先配置ohos.permission.INTERNET权限。// entry/src/main/module.json5{module:{requestPermissions:[{name:ohos.permission.INTERNET}]}}然后引入基础服务及相关 API// 没有额外的依赖需要手动引入相关模块已经在 HarmonyOS SDK 中内置import{SelectionInput}fromkit.BasicServicesKit;这一步需要注意的是kit.BasicServicesKit是 HarmonyOS NEXT 的统一包名。如果你在旧版本如 API 9中使用路径可能会变成ohos.base。如果是新项目直接使用新包名即可。Step 2创建自定义菜单组件划词服务的核心是配置一个SelectionInputOptions对象其中包含你要注入的菜单项。每个菜单项需要有label菜单显示的文本。action用户点击后的回调函数参数是选中的文本。icon可选菜单项的图标。我们先把菜单组件独立出来方便复用// src/main/ets/components/SelectionMenuConfig.tsimport{SelectionInput}fromkit.BasicServicesKit;// 定义菜单项配置exportinterfaceMenuItemConfig{label:string;action:(selectedText:string)void;}// 创建划词服务的配置对象exportfunctioncreateSelectionMenuConfig(menuItems:MenuItemConfig[]):SelectionInput.SelectionInputOptions{// 将你的菜单项转换为 SelectionMenu 所需的格式constmenu:SelectionInput.SelectionMenu[]menuItems.map(item({label:item.label,// 注意这里的 action 必须是 SyncCallback 类型不能是异步函数action:(text:string){// 实际开发中我们通常需要在 action 中做异步操作// 但是 SelectionInput 要求 action 是同步回调// 所以这里只能先存储文本交给外部处理item.action(text);},// 图标是可选的这里省略}));return{menu,// 是否在系统菜单之后显示我们的菜单// 设置为 true 表示在系统菜单后面追加appendBreak:true,};}这里有一个非常关键的点action必须是同步回调。官方的 API 规定SelectionInput.SelectionMenu.action的类型是SyncCallbackstring, void。这意味着你在回调内不能直接调用await或者长时间的网络请求。如果你需要在点击菜单后发起网络请求比如翻译就必须把文本暂存到一个外部状态中然后在生命周期合适的时机发起请求。这一点很容易被忽略很多人直接把httpRequest塞进action里然后发现菜单点击后没有任何响应——因为异步操作被吞了。Step 3在页面中注入划词服务接下来在页面中使用SelectionInput.injectMenu方法将配置注入。// src/main/ets/pages/TranslatePage.etsimport{SelectionInput}fromkit.BasicServicesKit;import{createSelectionMenuConfig,MenuItemConfig}from../components/SelectionMenuConfig;import{BusinessError}fromkit.BasicServicesKit;EntryComponentstruct TranslatePage{// 存储翻译结果StatetranslationResult:string;// 当前选中的文本privateselectedText:string;// 划词服务实例用于释放资源privateselectionInputInstance:SelectionInput.SelectionInputOptions|nullnull;aboutToAppear():void{// 配置菜单constmenuItems:MenuItemConfig[][{label:翻译英 - 中,action:(text:string){// 保存选中的文本this.selectedTexttext;// 发起翻译请求this.performTranslation(text);}}];constoptionscreateSelectionMenuConfig(menuItems);// 将划词服务注入到当前页面上下文try{SelectionInput.injectMenu(options).then((instance){// 保存实例用于后续释放this.selectionInputInstanceinstance;console.info(划词服务注入成功);}).catch((err:BusinessError){console.error(划词服务注入失败:${err.message});});}catch(err){console.error(调用 injectMenu 异常:${err});}}aboutToDisappear():void{// 页面销毁时必须释放划词服务否则会影响到其他页面this.releaseSelectionInput();}privatereleaseSelectionInput():void{if(this.selectionInputInstance){try{// 调用 unregister 方法释放SelectionInput.unregister(this.selectionInputInstance);}catch(err){console.error(释放划词服务异常:${err});}finally{this.selectionInputInstancenull;}}}privateasyncperformTranslation(text:string):Promisevoid{if(!text){return;}try{// 这里调用一个翻译 API示例中使用一个模拟的 HTTP 请求constresponseawaitfetch(https://api.example.com/translate,{method:POST,headers:{Content-Type:application/json},body:JSON.stringify({q:text,source:en,target:zh})});constdataawaitresponse.json();// 假设返回的格式是 { translatedText: 你好 }this.translationResultdata.translatedText||翻译失败;}catch(error){console.error(翻译请求失败:${error});this.translationResult网络请求异常请检查网络连接;}}build(){Column(){// 展示翻译结果if(this.translationResult){Text(翻译结果${this.translationResult}).fontSize(16).padding(12).backgroundColor(#f0f0f0).borderRadius(8).margin({bottom:20});}// 测试文本用户可选中Text(Hello, this is a test text for the selection feature. You can long press any word to see the custom menu.).fontSize(14).lineHeight(22).padding(16).backgroundColor(#ffffff).borderRadius(8).border({width:1,color:#e0e0e0}).width(100%)// 关键参数允许文本选中.copyOption(CopyOptions.InApp)// 启用划词服务需要 Text 组件支持选中.selectable(true)}.width(100%).height(100%).padding(16).backgroundColor(#f5f5f5)}}这段代码有几个重点aboutToAppear中注入aboutToDisappear中释放。这是生命周期管理的标准做法。如果只在aboutToAppear注入但不释放当页面返回到上一页时划词服务仍然在后台生效可能会导致其他页面也弹出了我们的自定义菜单。selectedText使用了普通属性而非State。因为action是同步回调不需要触发 UI 更新。翻译请求的结果使用了State这样才能更新 UI。copyOption和selectable缺一不可。selectable(true)让文本可以被选中copyOption(CopyOptions.InApp)让应用内部可以处理复制事件。如果只设置selectable系统可能不会触发划词服务。Step 4完整的页面入口// src/main/ets/pages/Index.etsimport{router}fromkit.ArkUI;EntryComponentstruct Index{build(){Column(){Button(进入翻译页面).onClick((){router.pushUrl({url:pages/TranslatePage});})}.width(100%).height(100%).justifyContent(FlexAlign.Center)}}常见问题 1菜单点击后没有响应现象长按文本后自定义菜单项确实显示出来了但点击后没有任何效果。原因正如前面提到的SelectionInput.SelectionMenu.action必须是同步回调。如果你在action中写了await或者返回了一个Promise系统会忽略这个回调不执行任何操作。解决方案把异步操作放到action外部去执行。上面的代码中我们在action里只做了两件事保存文本、调用一个被标记为async的外部方法performTranslation。注意action本身并没有await performTranslation而是直接调用了它这样action本身仍然是同步的。常见问题 2页面返回后划词菜单还在其他页面生效现象从 A 页面跳转到 B 页面在 A 页面注入的划词菜单仍然在 B 页面出现。原因SelectionInput.injectMenu的效果是全局的它不会自动跟随页面生命周期。如果你不在aboutToDisappear中调用SelectionInput.unregister这个菜单会一直存在影响所有文本组件。解决方案在aboutToDisappear中释放实例。上面的代码已经展示了释放逻辑。需要注意的是如果在injectMenu成功之前就返回了页面selectionInputInstance可能还是null所以释放时需要判空。最佳实践1. 不要在build()中动态配置菜单如果你把SelectionInput.injectMenu写在build()中每次组件重建都会重新注入导致菜单被重复注册出现多个相同的菜单项。务必在aboutToAppear中执行一次即可。2. 菜单项不宜过多系统菜单弹窗的显示空间有限如果自定义菜单项超过 4 个可能会出现显示不全或布局异常。建议控制在 2-3 个以内保持和系统菜单的平衡。3. 异步操作的状态管理action是同步回调但翻译等操作是异步的。推荐做法是在action中通过State更新一个“加载中”状态然后在异步回调完成后更新结果状态。这样用户点击菜单后能看到即时反馈避免误以为没有生效。// 在组件中添加一个加载状态StateisTranslating:booleanfalse;action:(text:string){this.selectedTexttext;this.isTranslatingtrue;// 立即显示“翻译中”this.performTranslation(text);// 异步执行}// 在翻译完成后privateasyncperformTranslation(text:string):Promisevoid{// ...this.isTranslatingfalse;this.translationResultresult;}FAQQ为什么真机上长按文本能看到自定义菜单但模拟器上不行A模拟器对文本选中和长按手势的支持不够完善部分模拟器版本可能无法正确触发划词服务。建议在真机上进行测试。真机测试时需要确认设备系统版本不低于 HarmonyOS 6.1.0。Q同一个页面可以注入多个菜单吗A可以。SelectionInput.injectMenu可以多次调用每次注入的菜单会合并到系统菜单弹窗中。但需要注意的是同一个页面内重复调用injectMenu会创建多个实例必须分别释放。建议只调用一次将所有菜单项放在一个配置中。Q点击自定义菜单后能获取到选中的坐标位置吗A当前版本不支持获取选中区的坐标。SelectionInput.SelectionMenu.action只能获取选中的文本内容无法获取位置信息。如果你需要自定义菜单根据选中位置弹窗建议使用手势监听 自定义弹窗方案。

相关新闻

《HarmonyOS技术精讲-Basic Services Kit》打印服务:文档与图片打印

《HarmonyOS技术精讲-Basic Services Kit》打印服务:文档与图片打印

《HarmonyOS技术精讲-Basic Services Kit》打印服务:文档与图片打印开篇:打印功能在HarmonyOS上怎么用 HarmonyOS NEXT 开发里,ohos.print 这个 API 经常被误用。很多人第一次接触打印能力时,会发现官方示例能运行,但换…

2026/7/18 20:48:23阅读更多 →
《HarmonyOS技术精讲-Basic Services Kit》升级服务:应用内更新检测与下载安装

《HarmonyOS技术精讲-Basic Services Kit》升级服务:应用内更新检测与下载安装

《HarmonyOS技术精讲-Basic Services Kit》升级服务:应用内更新检测与下载安装这个问题在开发里比较常见 HarmonyOS NEXT 开发里,升级服务 API 的调用频率其实很高,但很多人第一次接触时会发现:官方示例能跑通,放到实际…

2026/7/18 20:48:23阅读更多 →
朱雀检测从89%直降0%,我试了30篇稿子才摸透这AIGC检测的套路

朱雀检测从89%直降0%,我试了30篇稿子才摸透这AIGC检测的套路

不说废话,直接开干。朱雀大模型检测准确率95%,硬刚没用,绕过去才是本事。下面6招,招招可抄。 第一件事:知道检测系统在抓什么(说清楚就过) 不扯原理,两个核心指标: ① …

2026/7/18 20:48:23阅读更多 →
DMA控制器原理与实战:嵌入式系统数据搬运优化指南

DMA控制器原理与实战:嵌入式系统数据搬运优化指南

1. DMA控制器:嵌入式系统的“数据搬运工”在嵌入式系统开发中,尤其是涉及雷达信号处理、高速数据采集或实时图像处理的场景,我们常常会遇到一个核心矛盾:CPU的计算能力是宝贵的,但大量、频繁的数据搬运工作&#xff08…

2026/7/18 22:00:49阅读更多 →
杭州积分落户加分攻略:学历提升如何为你的城市生活加分

杭州积分落户加分攻略:学历提升如何为你的城市生活加分

一、杭州积分落户政策:新市民的安居之路作为长三角地区最具吸引力的城市之一,杭州以其优美的自然环境、蓬勃的经济活力和开放包容的城市氛围,每年吸引着大量外来人口前来就业和定居。为了更好地服务外来常住人口,杭州实行了积分落…

2026/7/18 22:00:49阅读更多 →
搜索场景的大模型推理优化:多阶段召回的精排融合与 Query 理解延迟压缩

搜索场景的大模型推理优化:多阶段召回的精排融合与 Query 理解延迟压缩

搜索场景的大模型推理优化:多阶段召回的精排融合与 Query 理解延迟压缩 一、搜索的全链路延迟分解:从 Query 输入到结果返回的三级跳 搜索系统的一个完整请求链路包含三个阶段:Query 理解(意图分类、实体识别、纠错、改写&#xf…

2026/7/18 22:00:49阅读更多 →
高性能日志系统的架构演进:从磁盘 IOPS 瓶颈到零拷贝环形缓冲区的工程实践

高性能日志系统的架构演进:从磁盘 IOPS 瓶颈到零拷贝环形缓冲区的工程实践

高性能日志系统的架构演进:从磁盘 IOPS 瓶颈到零拷贝环形缓冲区的工程实践 一、日志的隐性性能杀手:为什么 1000 QPS 的日志打印能拖垮 10000 QPS 的服务 日志是系统的观察者,但它不应该成为系统的拖累者。通过对一个 Go 微服务做火焰图分析&…

2026/7/18 22:00:49阅读更多 →
【Java基础】分支结构与循环语句 超详细笔记(零基础易懂)

【Java基础】分支结构与循环语句 超详细笔记(零基础易懂)

📚 专栏:Java零基础入门笔记 ✨ 前言 程序的核心运行逻辑离不开三大流程控制结构:顺序结构、分支结构、循环结构。几乎所有复杂业务代码,都是基于这三种基础结构嵌套组合实现的。本篇笔记整理了Java中所有分支、循环语法的核心知识…

2026/7/18 22:00:49阅读更多 →
多人协作内容总是版本混乱?科会通等3款工具实测与使用指南

多人协作内容总是版本混乱?科会通等3款工具实测与使用指南

多人协作内容总是版本混乱、信息不同步?作为团队负责人,在推进跨部门项目时,最头疼的往往是会议纪要和文档在微信群里反复传递,导致最终版本难以统一。为了解决这一痛点,本文以iOS系统生态为例,实测科会通、…

2026/7/18 21:58:49阅读更多 →
VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异

VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异

VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异当你在VSCode中启动一个新的TypeScript项目时,第一个技术决策往往从安装方式开始。这个看似简单的选择——全局安装还是项目本地安装——实际上会深刻影响你的开发流程、团队协作和…

2026/7/18 10:49:13阅读更多 →
智慧树刷课插件:5分钟实现自动化学习的智能助手

智慧树刷课插件:5分钟实现自动化学习的智能助手

智慧树刷课插件:5分钟实现自动化学习的智能助手 【免费下载链接】zhihuishu 智慧树刷课插件,自动播放下一集、1.5倍速度、无声 项目地址: https://gitcode.com/gh_mirrors/zh/zhihuishu 智慧树刷课插件是一款专为智慧树在线教育平台设计的Chrome浏…

2026/7/18 8:49:08阅读更多 →
Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案

Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案

Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案 【免费下载链接】WorkshopDL WorkshopDL - The Best Steam Workshop Downloader 项目地址: https://gitcode.com/gh_mirrors/wo/WorkshopDL 你是否在GOG或Epic Games Store购买了心仪的游戏…

2026/7/18 14:49:24阅读更多 →
从模糊意图到可执行指令:Claude PRD中Prompt Engineering与需求颗粒度的5级映射法则

从模糊意图到可执行指令:Claude PRD中Prompt Engineering与需求颗粒度的5级映射法则

更多请点击: https://kaifayun.com 第一章:从模糊意图到可执行指令:Claude PRD中Prompt Engineering与需求颗粒度的5级映射法则 在Claude驱动的产品需求文档(PRD)生成实践中,原始业务意图往往以自然语言片…

2026/7/18 0:00:14阅读更多 →
Cursor配置生成失效?3大隐藏陷阱+4行修复代码,资深工程师连夜整理的紧急补救清单

Cursor配置生成失效?3大隐藏陷阱+4行修复代码,资深工程师连夜整理的紧急补救清单

更多请点击: https://codechina.net 第一章:Cursor配置生成失效?3大隐藏陷阱4行修复代码,资深工程师连夜整理的紧急补救清单 Cursor 配置生成突然失效,是近期高频报障场景。表面看是 cursor.config.json 未更新或 LSP…

2026/7/18 0:00:14阅读更多 →
某智驾大牛创业

某智驾大牛创业

作者:钟声编辑:Mark出品:红色星际头图:智能驾驶图片据悉,国内某头部智驾公司端到端模型技术大牛Z投身创业,并且已经拿到融资。Z不仅是该头部公司内部最年轻的对标阿里P10级别技术负责⼈,更是业内…

2026/7/18 0:00:14阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/17 22:48:46阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/18 14:49:24阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/18 18:49:35阅读更多 →