当前位置: 首页 > news >正文

《HarmonyOS技术精讲-Basic Services Kit》划词服务:构建系统级文本选取能力

《HarmonyOS技术精讲-Basic Services Kit》划词服务:构建系统级文本选取能力

从一次文本交互的需求说起

在 HarmonyOS NEXT 开发中,如果我们希望用户选中一段文本后,能弹出“翻译”、“搜索”、“复制”等自定义操作菜单,一般会怎么处理?

很多人会想到监听手势,获取光标位置,然后自己实现一个浮动菜单。这样做的成本不止是 UI 层——还需要处理文本选中事件的回调、菜单的位置计算、以及在不同设备上的适配。

但 HarmonyOS 提供了一套更接近系统级的方案:划词服务(SelectionInput)。它允许开发者在系统默认的长按选中弹窗中,注入自定义的操作项,直接获取用户选中的文本内容,并作出响应。

这套 API 本身不复杂,但实际开发中,很多人在生命周期管理和菜单交互上踩了坑。官方示例虽然能运行,但放到真实项目里,至少还要处理两个问题:

  1. 页面返回时划词服务没有被正常释放,导致其他页面也受影响。
  2. 菜单点击后,选中状态和 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}from'@kit.BasicServicesKit';

这一步需要注意的是:@kit.BasicServicesKit是 HarmonyOS NEXT 的统一包名。如果你在旧版本(如 API 9)中使用,路径可能会变成@ohos.base。如果是新项目,直接使用新包名即可。

Step 2:创建自定义菜单组件

划词服务的核心是配置一个SelectionInputOptions对象,其中包含你要注入的菜单项。每个菜单项需要有:

  • label:菜单显示的文本。
  • action:用户点击后的回调函数,参数是选中的文本。
  • icon:可选,菜单项的图标。

我们先把菜单组件独立出来,方便复用:

// src/main/ets/components/SelectionMenuConfig.tsimport{SelectionInput}from'@kit.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的类型是SyncCallback<string, void>。这意味着你在回调内不能直接调用await或者长时间的网络请求。

如果你需要在点击菜单后发起网络请求(比如翻译),就必须把文本暂存到一个外部状态中,然后在生命周期合适的时机发起请求。这一点很容易被忽略,很多人直接把httpRequest塞进action里,然后发现菜单点击后没有任何响应——因为异步操作被吞了。

Step 3:在页面中注入划词服务

接下来在页面中使用SelectionInput.injectMenu方法将配置注入。

// src/main/ets/pages/TranslatePage.etsimport{SelectionInput}from'@kit.BasicServicesKit';import{createSelectionMenuConfig,MenuItemConfig}from'../components/SelectionMenuConfig';import{BusinessError}from'@kit.BasicServicesKit';@Entry@Componentstruct TranslatePage{// 存储翻译结果@StatetranslationResult:string='';// 当前选中的文本privateselectedText:string='';// 划词服务实例,用于释放资源privateselectionInputInstance:SelectionInput.SelectionInputOptions|null=null;aboutToAppear():void{// 配置菜单constmenuItems:MenuItemConfig[]=[{label:'翻译(英 -> 中)',action:(text:string)=>{// 保存选中的文本this.selectedText=text;// 发起翻译请求this.performTranslation(text);}}];constoptions=createSelectionMenuConfig(menuItems);// 将划词服务注入到当前页面上下文try{SelectionInput.injectMenu(options).then((instance)=>{// 保存实例,用于后续释放this.selectionInputInstance=instance;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.selectionInputInstance=null;}}}privateasyncperformTranslation(text:string):Promise<void>{if(!text){return;}try{// 这里调用一个翻译 API,示例中使用一个模拟的 HTTP 请求constresponse=awaitfetch('https://api.example.com/translate',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({q:text,source:'en',target:'zh'})});constdata=awaitresponse.json();// 假设返回的格式是 { translatedText: "你好" }this.translationResult=data.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')}}

这段代码有几个重点:

  1. aboutToAppear中注入,aboutToDisappear中释放。这是生命周期管理的标准做法。如果只在aboutToAppear注入但不释放,当页面返回到上一页时,划词服务仍然在后台生效,可能会导致其他页面也弹出了我们的自定义菜单。

  2. selectedText使用了普通属性而非@State。因为action是同步回调,不需要触发 UI 更新。翻译请求的结果使用了@State,这样才能更新 UI。

  3. copyOptionselectable缺一不可selectable(true)让文本可以被选中,copyOption(CopyOptions.InApp)让应用内部可以处理复制事件。如果只设置selectable,系统可能不会触发划词服务。

Step 4:完整的页面入口

// src/main/ets/pages/Index.etsimport{router}from'@kit.ArkUI';@Entry@Componentstruct 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:boolean=false;action:(text:string)=>{this.selectedText=text;this.isTranslating=true;// 立即显示“翻译中”this.performTranslation(text);// 异步执行}// 在翻译完成后privateasyncperformTranslation(text:string):Promise<void>{// ...this.isTranslating=false;this.translationResult=result;}

FAQ

Q:为什么真机上长按文本能看到自定义菜单,但模拟器上不行?

A:模拟器对文本选中和长按手势的支持不够完善,部分模拟器版本可能无法正确触发划词服务。建议在真机上进行测试。真机测试时需要确认设备系统版本不低于 HarmonyOS 6.1.0。

Q:同一个页面可以注入多个菜单吗?

A:可以。SelectionInput.injectMenu可以多次调用,每次注入的菜单会合并到系统菜单弹窗中。但需要注意的是,同一个页面内重复调用injectMenu会创建多个实例,必须分别释放。建议只调用一次,将所有菜单项放在一个配置中。

Q:点击自定义菜单后,能获取到选中的坐标位置吗?

A:当前版本不支持获取选中区的坐标。SelectionInput.SelectionMenu.action只能获取选中的文本内容,无法获取位置信息。如果你需要自定义菜单根据选中位置弹窗,建议使用手势监听 + 自定义弹窗方案。

http://www.cnnetsun.cn/news/3496706.html

相关文章:

  • 闲谈为什么学习编程
  • tlsfoward 不只是抓包:用脚本化思路做 HTTP/TLS 网络测试
  • 2026元宝区女人街靠谱口碑好女装店丹东本地推荐
  • 基于C11的线程池模型 - 计划式线程池
  • 参考国内集装箱式静音箱优质供应商名单对比分析
  • SEO团队如何改造官网结构,让AI搜索读懂你的品牌?
  • 如何让Chrome浏览器成为你的Markdown文档专业阅读器?markdownReader插件实战指南
  • 深度测评:2026年实打实好用的专业AI智能降重工具
  • 硬件设计常用的逻辑芯片 SN74LVC1G00 详解
  • Python实现基本搜索引擎
  • unity发布web嵌入到前端页面的接受参数
  • Claude PRD撰写避坑指南:从需求模糊到精准落地的7步标准化流程
  • 中阳酒吧音响设备
  • OpenCore Legacy Patcher终极指南:让老旧Mac重获新生,运行最新macOS
  • ChatGPT写部门规划必须绕开的5个合规雷区(数据安全/权责归属/审计追溯),法务+HR联合签发指引
  • 燃气容积式热水器与模块炉最优选型指南
  • Mac用户别忍了!这5个场景,用MuMu模拟器真的爽翻了
  • 苹果起诉 OpenAI,表面争机密,实际争的是 AI 硬件入口
  • 从 400A 到 2000A,大电流检测在工程应用中的几种实现方式
  • 免费开源AI语音识别工具:Faster-Whisper-GUI完整使用指南
  • 塑封成型技术:原理、应用与工艺优化
  • 艺术涂料行业技术标准化路径解析艺术
  • 热电偶信号滤波电路设计与工业应用实践
  • AI Agent持续优化已进入“亚秒级”时代:2025年必须掌握的4个在线蒸馏与轻量化热更新协议
  • 山西酒店全屋智能
  • 射频功率放大器设计中的负载牵引技术解析
  • 北极星指标与指标树:产品增长的导航仪
  • S7-1200 PLC模块化设计与扩展技术详解
  • 微信小程序二维码生成终极指南:5分钟学会前端零依赖方案
  • FPGA中XDMA与DDR3的AXI互联架构与优化