ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

HarmonyOS 闪控球 + 闪控窗:两种悬浮形态如何完成状态切换【鸿蒙心迹】

HarmonyOS 闪控球 + 闪控窗:两种悬浮形态如何完成状态切换【鸿蒙心迹】 你好欢迎来到我的博客我是【菜鸟学鸿蒙】我是一名在路上的移动端开发者正从传统“小码农”转向鸿蒙原生开发的进阶之旅。为了把学习过的知识沉淀下来也为了和更多同路人互相启发我决定把探索 HarmonyOS 的过程都记录在这里。️主要方向ArkTS 语言基础、HarmonyOS 原生应用Stage 模型、UIAbility/ServiceAbility、分布式能力与软总线、元服务/卡片、应用签名与上架、性能与内存优化、项目实战以及 Android → 鸿蒙的迁移踩坑与复盘。内容节奏从基础到实战——小示例拆解框架认知、专项优化手记、实战项目拆包、面试题思考与复盘让每篇都有可落地的代码与方法论。 我相信写作是把知识内化的过程分享是让生态更繁荣的方式。如果你也想拥抱鸿蒙、热爱成长欢迎关注我一起交流进步前言用户把应用退到后台任务还在跑——这时候怎么让他感知到进度又不强迫他回到应用这正是闪控球和闪控窗要解决的问题。一个是常驻屏幕边缘的小球承担最简进度提示另一个是点击小球后弹出的展开面板展示更多信息并提供操作按钮。两者分工明确配合使用形成一套完整的后台任务可见性方案。本文围绕官方文档提供的接口用一个下载任务场景把从创建控制器到状态切换、再到退出清理的完整开发流程串联起来。一、两种形态各自承担什么闪控球是一个系统级全局应用已由系统集成开发者不需要自己实现它的 UI只需要通过接口向它传递进度值、颜色、图标和标签它就会把这些信息渲染在小球上。闪控窗是应用自己展示的悬浮窗内容完全由开发者控制。通过ComponentContent绑定两个Builder组件——一个进度条区域、一个操作按钮区域——然后在应用退到后台后通过FlashControlWindowController.showWindow()弹出。两者的联动关系是用户点击闪控球闪控窗展开或收起收起时闪控球继续显示进度。状态变化通过flashWindowStatusListener回调通知给应用。这里有一点容易理解偏差闪控球不是应用自己画的FlashBallConfig只是配置数据实际渲染由系统接管。二、官方规定的前置条件在动手写代码之前几个限制条件需要先确认清楚。项目要求API Level从 API version 18 开始支持支持设备仅 Phone权限ohos.permission.FLASH_CONTROLshowWindow调用时机只能在应用处于后台时调用系统能力SystemCapability.ArkUI.ArkUI.Fullimport 来源kit.ArkUIshowWindow这个限制是关键——如果在前台调用接口会失败。这意味着显示闪控窗的逻辑必须放在UIAbility的onBackground之后触发不能提前。三、最小场景设定以一个文件下载任务为例。目标是用户在应用前台发起下载切换到后台后屏幕边缘出现闪控球显示下载进度0100点击闪控球弹出闪控窗展示进度条和暂停/取消按钮再次点击小球收起闪控窗应用回到前台闪控窗隐藏应用销毁清理控制器。四、核心代码实现4.1 权限配置在module.json5的requestPermissions字段中添加{module:{requestPermissions:[{name:ohos.permission.FLASH_CONTROL}]}}4.2 定义进度条和操作按钮的 Builder 组件这两个组件将通过ComponentContent注入到闪控窗中是闪控窗展开后用户看到的内容。import{FlashControlWindowController,FlashBallConfig,FlashWindowConfig,FlashWindowStatus}fromkit.ArkUI;import{UIAbilityContext}fromkit.AbilityKit;// 进度条区域参数类型interfaceProgressParams{progress:number;color:ResourceColor;label:string;}// 操作按钮区域参数类型interfaceButtonParams{onPause:()void;onCancel:()void;}// 进度条组件BuilderfunctionProgressBarBuilder(params:ProgressParams){Column(){Progress({value:params.progress,total:100,type:ProgressType.Linear}).width(100%).height(8).color(params.color)Text(params.label).fontSize(14).fontColor(#333333).margin({top:8})}.padding(16).width(100%)}// 操作按钮组件BuilderfunctionStartButtonBuilder(params:ButtonParams){Row(){Button(暂停).onClick(()params.onPause()).margin({right:8})Button(取消).onClick(()params.onCancel())}.padding(16).justifyContent(FlexAlign.Center).width(100%)}4.3 在 UIAbility 中管理控制器生命周期这一段代码是整个流程的核心放在EntryAbility.ets中importUIAbilityfromohos.app.ability.UIAbility;importwindowfromohos.window;import{FlashControlWindowController,FlashWindowConfig,FlashWindowStatus,FlashBallConfig}fromkit.ArkUI;exportdefaultclassEntryAbilityextendsUIAbility{privateflashController:FlashControlWindowController|nullnull;privatecurrentProgress:number0;// 应用切入后台创建控制器、绑定组件、显示闪控窗asynconBackground(){try{// 1. 创建控制器实例this.flashControllerawaitFlashControlWindowController.create();// 2. 构建 ComponentContent需要 UIContext从 windowStage 获取constuiContextthis.context.getApplicationContext().getRunningProcessInformation;// 注意ComponentContent 需要从 windowStage.getMainWindowSync().getUIContext() 获取// 具体 UIContext 的获取方式取决于工程实际结构此处作为示意// 3. 绑定进度条和操作按钮ComponentContent 的实际构造依赖 UIContext// 下方展示逻辑结构UIContext 的获取见 4.4 节说明// 4. 注册状态监听this.flashController.flashWindowStatusListener((status:FlashWindowStatus){this.onFlashWindowStatusChanged(status);});// 5. 配置闪控球进度 颜色 图标 标签constballConfig:FlashBallConfig{progressValue:this.currentProgress,progressColor:#007DFF,icon:$r(app.media.startIcon),label:下载中};awaitthis.flashController.setFlashBallConfig(ballConfig);// 6. 显示闪控窗必须在后台调用constwindowConfig:FlashWindowConfig{defaultHeight:200,maxHeight:400};awaitthis.flashController.showWindow(this.context,windowConfig);}catch(err){console.error(FlashControl onBackground error:,JSON.stringify(err));}}// 应用回到前台隐藏闪控窗asynconForeground(){if(this.flashController){try{awaitthis.flashController.hideWindow();}catch(err){console.error(FlashControl hideWindow error:,JSON.stringify(err));}}}// 应用销毁清理控制器asynconDestroy(){if(this.flashController){try{this.flashController.off(flashWindowStatusChange);awaitthis.flashController.destroy();}catch(err){console.error(FlashControl destroy error:,JSON.stringify(err));}finally{this.flashControllernull;}}}// 处理闪控窗状态变化privateonFlashWindowStatusChanged(status:FlashWindowStatus){switch(status){caseFlashWindowStatus.SHOW:// 闪控窗展开可以在这里刷新 ComponentContent 的数据console.info(FlashWindow: expanded);break;caseFlashWindowStatus.HIDE:// 闪控窗收起闪控球继续显示console.info(FlashWindow: collapsed, ball visible);break;caseFlashWindowStatus.DESTROY:// 系统触发销毁如用户手动关闭清理本地引用console.info(FlashWindow: destroyed by system);this.flashControllernull;break;}}}4.4 ComponentContent 的构造说明bindProgressBar和bindStartButton需要传入ComponentContentobject实例。构造ComponentContent需要UIContext对象通常从windowStage.getMainWindowSync().getUIContext()获取。在UIAbility.onWindowStageCreate阶段保存这个引用是比较可靠的做法privateuiContext:UIContext|nullnull;onWindowStageCreate(windowStage:window.WindowStage){windowStage.loadContent(pages/Index,(err){if(err.code){return;}// 保存 UIContext 供后续构造 ComponentContent 使用this.uiContextwindowStage.getMainWindowSync().getUIContext();});}// 在 onBackground 中使用asynconBackground(){if(!this.uiContext||!this.flashController){return;}constprogressContentnewComponentContent(this.uiContext,wrapBuilder(ProgressBarBuilder),{progress:this.currentProgress,color:#007DFF,label:下载中...}asProgressParams);constbuttonContentnewComponentContent(this.uiContext,wrapBuilder(StartButtonBuilder),{onPause:()this.handlePause(),onCancel:()this.handleCancel()}asButtonParams);this.flashController.bindProgressBar(progressContent);this.flashController.bindStartButton(buttonContent);}4.5 动态更新闪控球进度下载进度更新时通过setFlashBallConfig把最新进度同步给闪控球// 在下载进度回调中调用asyncupdateDownloadProgress(progress:number){this.currentProgressprogress;if(this.flashController){awaitthis.flashController.setFlashBallConfig({progressValue:progress,progressColor:#007DFF,icon:$r(app.media.startIcon),label:下载中${progress}%});}}五、几个关键点拆开看5.1 showWindow 只能在后台调用这是整个接口设计里最需要关注的约束。showWindow的设计意图就是服务于后台任务如果应用仍在前台调用会报错。因此showWindow的调用必须发生在onBackground触发之后不能在前台预先调用。5.2 闪控球不由应用绘制FlashBallConfig只是配置数据进度值、颜色、图标、标签闪控球的实际渲染由系统接管。开发者无法自定义小球的形状、位置或交互逻辑这些都归系统管理。这意味着用户点击小球的行为不需要应用监听由系统触发闪控窗的展开/收起并通过FlashWindowStatus回调通知应用。5.3 FlashWindowStatus.DESTROY 不等于应用主动销毁DESTROY状态可以由系统主动触发例如用户手动关闭闪控窗此时控制器已经失效。如果代码在收到DESTROY之后还继续调用setFlashBallConfig或hideWindow会产生异常。建议在收到DESTROY回调时立即将本地flashController引用置为null并停止后续对控制器的调用。5.4 组件绑定在显示之前完成bindProgressBar和bindStartButton需要在showWindow之前调用否则闪控窗展开时内容为空。这两个绑定方法是同步的返回void不需要await但顺序不能颠倒。5.5 仅支持 Phone这个能力明确只支持 Phone 设备Tablet、折叠屏大屏模式、2in1 均不在支持范围内。如果应用需要跨设备运行这里应该做设备类型判断避免在不支持的设备上调用接口导致异常。六、容易踩坑的地方权限未在 module.json5 中声明ohos.permission.FLASH_CONTROL缺失时showWindow调用会因权限检查失败报错。这个权限不需要动态申请normal 级别但必须在module.json5的requestPermissions中静态声明漏掉这一步非常常见。在前台调用 showWindow如果任务在前台启动然后立刻调用showWindow接口会失败。正确做法是在前台只做初始化准备创建控制器、绑定组件等onBackground触发后再调用showWindow。UIContext 的获取时机ComponentContent的构造依赖UIContext而UIContext在onWindowStageCreate之后才可用。如果在onCreate阶段就尝试构造ComponentContentUIContext为空会导致构造失败。建议在onWindowStageCreate中保存好UIContext引用。off 的参数是字符串字面量取消监听的正确写法是controller.off(flashWindowStatusChange)注意这里传的是字符串不是枚举值写错大小写或者传错参数会导致监听无法取消内存泄漏风险较高。七、排查思路如果闪控窗没有出现按以下顺序排查API 版本确认工程 compileSdkVersion 和 targetSdkVersion 均 18设备类型确认运行设备是 Phone不是模拟器或 Tablet权限检查module.json5中ohos.permission.FLASH_CONTROL是否存在调用时机确认showWindow是在onBackground触发之后调用的不是在前台组件绑定顺序确认bindProgressBar和bindStartButton在showWindow之前调用UIContext 是否有效确认ComponentContent构造时传入的UIContext不为 null查看控制台日志FlashControlWindowController.create()和showWindow()都返回Promise需要try/catch捕获异常看是否有报错信息。开发经验总结闪控球是系统组件不需要开发者实现FlashBallConfig只是向系统传递显示数据控制权在系统侧。showWindow的后台限制是整个开发流程中最核心的约束生命周期管理必须以onBackground/onForeground为节点来设计。FlashWindowStatus.DESTROY是系统主动触发的与应用调用destroy()是两条独立路径两种情况都需要清理本地引用。ComponentContent的UIContext依赖建议在onWindowStageCreate阶段统一处理好。该能力当前仅支持 Phone跨设备应用需要做设备判断。如果你正在做类似后台任务进度展示的需求可以思考一下任务进度回调的更新频率如果很高比如每秒多次setFlashBallConfig是否需要做节流避免频繁调用影响系统交互体验——这个地方官方文档没有明确限制但在实际设备上值得关注。 写在最后如果你觉得这篇文章对你有帮助或者有任何想法、建议欢迎在评论区留言交流你的每一个点赞 、收藏 ⭐、关注 ❤️都是我持续更新的最大动力我是一个在代码世界里不断摸索的小码农愿我们都能在成长的路上越走越远越学越强感谢你的阅读我们下篇文章再见✍️ 作者菜鸟不学编程 本文原创转载请注明出处。
返回列表