閃控球是一種在設(shè)備屏幕上懸浮的非全屏應(yīng)用窗口,為應(yīng)用提供臨時(shí)的全局能力,完成跨應(yīng)用交互。應(yīng)用可以將關(guān)鍵信息以小窗(閃控球)模式呈現(xiàn)。切換為小窗(閃控球)模式后,用戶可以進(jìn)行其他界面操作,提升使用體驗(yàn)。
演示

演示.gif
使用限制
1.API 20開始,支持使用閃控球能力
2.需要具有ohos.permission.USE_FLOAT_BALL權(quán)限
3.同一個(gè)應(yīng)用只能啟動一個(gè)閃控球,同一個(gè)設(shè)備最多同時(shí)存在兩個(gè)閃控球,超出覆蓋舊的
4.目前僅支持手機(jī)和平板設(shè)備
5.支持在DevEco Studio 6.0.1 Release及以上版本的模擬器中使用閃控球相關(guān)功能。
啟動和更新閃控球的配置參數(shù)FloatingBallParams
| 名稱 | 說明 |
|---|---|
| template | 閃控球模板 |
| title | 閃控球標(biāo)題 |
| content | 閃控球內(nèi)容 |
| backgroundColor | 閃控球背景顏色 |
| icon | 閃控球圖標(biāo) |
支持模板
閃控球模板類型的枚舉FloatingBallTemplate目前支持四種閃控球模板布局
| 名稱 | 布局 | 說明 | 必傳參數(shù) |
|---|---|---|---|
| STATIC | 靜態(tài)布局 | 支持標(biāo)題和圖標(biāo) | title、icon |
| NORMAL | 普通文本布局 | 支持標(biāo)題和內(nèi)容 | title |
| EMPHATIC | 強(qiáng)調(diào)文本布局 | 支持圖標(biāo)、標(biāo)題和內(nèi)容 | title |
| SIMPLE | 純文本布局 | 只支持標(biāo)題 | title |
閃控球控制器FloatingBallController
| 方法 | 說明 |
|---|---|
| startFloatingBall(params: FloatingBallParams): Promise<void> | 啟動閃控球 |
| updateFloatingBall(params: FloatingBallParams): Promise<void> | 更新閃控球 |
| stopFloatingBall(): Promise<void> | 停止閃控球 |
| on(type: 'stateChange', callback: Callback<FloatingBallState>): void | 監(jiān)聽生命周期狀態(tài)變化 |
| off(type: 'stateChange', callback?: Callback<FloatingBallState>): void | 取消監(jiān)聽 |
| on(type: 'click', callback: Callback<void>): void | 點(diǎn)擊監(jiān)聽事件 |
| off(type: 'click', callback?: Callback<void>): void | 取消監(jiān)聽 |
| getFloatingBallWindowInfo(): Promise<FloatingBallWindowInfo> | 獲得閃控球窗口信息 |
| restoreMainWindow(want: Want): Promise<void> | 恢復(fù)應(yīng)用主窗口并加載指定頁面 |
源碼
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('關(guān)閉').onClick(()=>{
if (this.timerId > 0) {
clearInterval(this.timerId)
this.timerId = -1
}
FloatingBallUtils.onStopFloatingBall(this.floatingBallController)
})
Button('靜態(tài)布局').onClick(async ()=>{
FloatingBallUtils.onCreateFloatingBall(this.floatingBallController,floatingBall.FloatingBallTemplate.STATIC,()=>{},'靜態(tài)布局-標(biāo)題','內(nèi)容','',this.ballIcon)
})
Row({space:10}){
Button('普通文本布局').onClick(async ()=>{
FloatingBallUtils.onCreateFloatingBall(this.floatingBallController,floatingBall.FloatingBallTemplate.NORMAL,()=>{},'普通文本布局-標(biāo)題','內(nèi)容','#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,'時(shí)間',DateUtil.format(new Date().getTime(),'HH:mm:ss'),this.ballIcon)
}, 1000)
})
}
Button('強(qiáng)調(diào)文本布局').onClick(async ()=>{
FloatingBallUtils.onCreateFloatingBall(this.floatingBallController,floatingBall.FloatingBallTemplate.EMPHATIC,()=>{},'強(qiáng)調(diào)文本布局-標(biāo)題','內(nèi)容','#cc3217',this.ballIcon)
})
Button('純文本布局').onClick(async ()=>{
FloatingBallUtils.onCreateFloatingBall(this.floatingBallController,floatingBall.FloatingBallTemplate.SIMPLE,()=>{},'純文本布局-標(biāo)題','內(nèi)容','#4617cc',this.ballIcon)
})
}
}
.attributeModifier(new MyNavigation(this.pathStack))
}
aboutToDisappear() {
if (this.timerId > 0) {
clearInterval(this.timerId)
this.timerId = -1
}
FloatingBallUtils.onStopFloatingBall(this.floatingBallController)
}
}
FloatingBallUtils
// 該頁面提供工具類,展示閃控球的創(chuàng)建、更新、關(guān)閉邏輯
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, // 接收狀態(tài)更新回調(diào)函數(shù)
title: string,
content?: string,
backgroundColor: string = '#ffffff',
icon?: image.PixelMap): Promise<void> {
// 注冊 監(jiān)聽點(diǎn)擊回調(diào)事件
floatingBallController?.on('click', () => {
let want: Want = {
bundleName: BUNDLE_NAME,
abilityName: 'EntryAbility'
}
// 使用promise異步回調(diào)
floatingBallController?.restoreMainWindow(want)
.then(() => {
}).catch((err: BusinessError) => {
})
})
// 注冊 監(jiān)聽狀態(tài)變化事件
floatingBallController?.on('stateChange',
(state: floatingBall.FloatingBallState) => {
if(state === floatingBall.FloatingBallState.STOPPED) {
floatingBallController?.off('click')
floatingBallController?.off('stateChange')
floatingBallController = undefined;
// 執(zhí)行狀態(tài)更新回調(diào)
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 狀態(tài)回調(diào)獲取實(shí)際刪除結(jié)果
floatingBallController?.stopFloatingBall().then(() => {
}).catch((err: BusinessError) => {
console.error('stopFloatingBall Error:', err)
})
}
}