闪控球是一种在设备屏幕上悬浮的非全屏应用窗口,为应用提供临时的全局能力,完成跨应用交互。应用可以将关键信息以小窗(闪控球)模式呈现。切换为小窗(闪控球)模式后,用户可以进行其他界面操作,提升使用体验。
演示

演示.gif
使用限制
1.API 20开始,支持使用闪控球能力
2.需要具有ohos.permission.USE_FLOAT_BALL权限
3.同一个应用只能启动一个闪控球,同一个设备最多同时存在两个闪控球,超出覆盖旧的
4.目前仅支持手机和平板设备
5.支持在DevEco Studio 6.0.1 Release及以上版本的模拟器中使用闪控球相关功能。
启动和更新闪控球的配置参数FloatingBallParams
| 名称 | 说明 |
|---|---|
| template | 闪控球模板 |
| title | 闪控球标题 |
| content | 闪控球内容 |
| backgroundColor | 闪控球背景颜色 |
| icon | 闪控球图标 |
支持模板
闪控球模板类型的枚举FloatingBallTemplate目前支持四种闪控球模板布局
| 名称 | 布局 | 说明 | 必传参数 |
|---|---|---|---|
| STATIC | 静态布局 | 支持标题和图标 | title、icon |
| NORMAL | 普通文本布局 | 支持标题和内容 | title |
| EMPHATIC | 强调文本布局 | 支持图标、标题和内容 | title |
| SIMPLE | 纯文本布局 | 只支持标题 | title |
闪控球控制器FloatingBallController
| 方法 | 说明 |
|---|---|
| startFloatingBall(params: FloatingBallParams): Promise<void> | 启动闪控球 |
| updateFloatingBall(params: FloatingBallParams): Promise<void> | 更新闪控球 |
| stopFloatingBall(): Promise<void> | 停止闪控球 |
| on(type: 'stateChange', callback: Callback<FloatingBallState>): void | 监听生命周期状态变化 |
| off(type: 'stateChange', callback?: Callback<FloatingBallState>): void | 取消监听 |
| on(type: 'click', callback: Callback<void>): void | 点击监听事件 |
| off(type: 'click', callback?: Callback<void>): void | 取消监听 |
| getFloatingBallWindowInfo(): Promise<FloatingBallWindowInfo> | 获得闪控球窗口信息 |
| restoreMainWindow(want: Want): Promise<void> | 恢复应用主窗口并加载指定页面 |
源码
page
import { FloatingBallUtils } from '../utils/FloatingBallUtils'
import { MyNavigation } from '../utils/MyAttributeModifier'
import { image } from '@kit.ImageKit'
import { floatingBall } from '@kit.ArkUI'
import { WindowUtils } from '../utils/WindowUtils'
import { DateUtil } from '../utils/DateUtil'
@Entry
@ComponentV2
struct FloatingBallTest{
pathStack : NavPathStack = new NavPathStack()
ballIcon:image.PixelMap | undefined = FloatingBallUtils.getRawfilePixelMapSync('icon128.png')
// 声明闪控球控制器
floatingBallController: floatingBall.FloatingBallController | undefined = undefined
private timerId: number = -1
async aboutToAppear(): Promise<void> {
if (!this.floatingBallController) {
this.floatingBallController = await floatingBall.create({
context: WindowUtils.getUIAbilityContext()
})
}
}
build() {
Navigation(this.pathStack){
Column({space:10}){
Button('关闭').onClick(()=>{
if (this.timerId > 0) {
clearInterval(this.timerId)
this.timerId = -1
}
FloatingBallUtils.onStopFloatingBall(this.floatingBallController)
})
Button('静态布局').onClick(async ()=>{
FloatingBallUtils.onCreateFloatingBall(this.floatingBallController,floatingBall.FloatingBallTemplate.STATIC,()=>{},'静态布局-标题','内容','',this.ballIcon)
})
Row({space:10}){
Button('普通文本布局').onClick(async ()=>{
FloatingBallUtils.onCreateFloatingBall(this.floatingBallController,floatingBall.FloatingBallTemplate.NORMAL,()=>{},'普通文本布局-标题','内容','#0ae49d',this.ballIcon)
})
Button('更新').onClick(()=>{
if (this.timerId > 0) {
clearInterval(this.timerId)
this.timerId = -1
}
this.timerId = setInterval(() => {
FloatingBallUtils.onUpdateFloatingBall(this.floatingBallController,floatingBall.FloatingBallTemplate.NORMAL,'时间',DateUtil.format(new Date().getTime(),'HH:mm:ss'),this.ballIcon)
}, 1000)
})
}
Button('强调文本布局').onClick(async ()=>{
FloatingBallUtils.onCreateFloatingBall(this.floatingBallController,floatingBall.FloatingBallTemplate.EMPHATIC,()=>{},'强调文本布局-标题','内容','#cc3217',this.ballIcon)
})
Button('纯文本布局').onClick(async ()=>{
FloatingBallUtils.onCreateFloatingBall(this.floatingBallController,floatingBall.FloatingBallTemplate.SIMPLE,()=>{},'纯文本布局-标题','内容','#4617cc',this.ballIcon)
})
}
}
.attributeModifier(new MyNavigation(this.pathStack))
}
aboutToDisappear() {
if (this.timerId > 0) {
clearInterval(this.timerId)
this.timerId = -1
}
FloatingBallUtils.onStopFloatingBall(this.floatingBallController)
}
}
FloatingBallUtils
// 该页面提供工具类,展示闪控球的创建、更新、关闭逻辑
import image from '@ohos.multimedia.image';
import { BusinessError } from '@kit.BasicServicesKit';
import { floatingBall } from '@kit.ArkUI';
import { Want ,bundleManager } from '@kit.AbilityKit';
import { WindowUtils } from './WindowUtils';
let BUNDLE_NAME: string=''
let bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT;
try {
bundleManager.getBundleInfoForSelf(bundleFlags).then((data) => {
BUNDLE_NAME = data.name
}).catch((err: BusinessError) => {
});
} catch (err) {
}
export class FloatingBallUtils {
public static getRawfilePixelMapSync(path: string): image.PixelMap {
try {
const BUFFER = WindowUtils.getUIAbilityContext()!.resourceManager.getRawFileContentSync(path);
const IMAGE_SOURCE: image.ImageSource = image.createImageSource(BUFFER.buffer as ArrayBuffer);
return IMAGE_SOURCE.createPixelMapSync();
} catch (e) {
throw e as Error;
}
}
// 闪控球启动逻辑
public static async onCreateFloatingBall(
floatingBallController: floatingBall.FloatingBallController | undefined,
template: floatingBall.FloatingBallTemplate,
onActiveRowChange: (value: number) => void, // 接收状态更新回调函数
title: string,
content?: string,
backgroundColor: string = '#ffffff',
icon?: image.PixelMap): Promise<void> {
// 注册 监听点击回调事件
floatingBallController?.on('click', () => {
let want: Want = {
bundleName: BUNDLE_NAME,
abilityName: 'EntryAbility'
}
// 使用promise异步回调
floatingBallController?.restoreMainWindow(want)
.then(() => {
}).catch((err: BusinessError) => {
})
})
// 注册 监听状态变化事件
floatingBallController?.on('stateChange',
(state: floatingBall.FloatingBallState) => {
if(state === floatingBall.FloatingBallState.STOPPED) {
floatingBallController?.off('click')
floatingBallController?.off('stateChange')
floatingBallController = undefined;
// 执行状态更新回调
onActiveRowChange?.(-1);
}
})
// 最后启动闪控球
let startParams: floatingBall.FloatingBallParams = icon? {
template: template,
title: title,
content: content,
backgroundColor: backgroundColor,
icon: icon
} : {
template: template,
title: title,
content: content,
backgroundColor: backgroundColor
}
try {
floatingBallController?.startFloatingBall(startParams)
.then(() => {
console.log(`succeed in starting FloatingBall`)
}).catch((err: BusinessError) => {
console.error(`failed to start FloatingBall. code: ${err.code}, message: ${err.message}`)
})
} catch (e) {
console.error('startFloatingBall Error', e)
}
}
// 闪控球更新逻辑
public static onUpdateFloatingBall(
floatingBallController: floatingBall.FloatingBallController | undefined,
template: floatingBall.FloatingBallTemplate,
title: string,
content?: string ,
icon?: image.PixelMap): void {
let updateParams: floatingBall.FloatingBallParams = icon ? {
template: template,
title: title,
content: content,
backgroundColor: '#ffffff',
icon: icon
} : {
template: template,
title: title ,
content: content,
backgroundColor: '#ffffff',
}
try {
floatingBallController?.updateFloatingBall(updateParams).then(() => {
}).catch((err: BusinessError) => {
console.error('updateFloatingBall Error:', err)
})
} catch (e) {
console.error('updateFloatingBall Error:', e)
}
}
// 闪控球停止逻辑
public static onStopFloatingBall(floatingBallController: floatingBall.FloatingBallController | undefined): void {
// stop 是异步流程,需要通过 stateChange 状态回调获取实际删除结果
floatingBallController?.stopFloatingBall().then(() => {
}).catch((err: BusinessError) => {
console.error('stopFloatingBall Error:', err)
})
}
}