如果你要做的是系統(tǒng)級(jí)別的懸浮窗,就需要判斷是否具備懸浮窗權(quán)限。然而這又不是一個(gè)標(biāo)準(zhǔn)的動(dòng)態(tài)權(quán)限,你需要兼容各種奇葩機(jī)型的懸浮窗權(quán)限判斷,下面的代碼來(lái)自于某著名開源庫(kù):EasyFloat[1] 。
fun checkPermission(context: Context): Boolean =
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) when {
RomUtils.checkIsHuaweiRom() - > huaweiPermissionCheck(context)
RomUtils.checkIsMiuiRom() - > miuiPermissionCheck(context)
RomUtils.checkIsOppoRom() - > oppoROMPermissionCheck(context)
RomUtils.checkIsMeizuRom() - > meizuPermissionCheck(context)
RomUtils.checkIs360Rom() - > qikuPermissionCheck(context)
else - > true
} else commonROMPermissionCheck(context)
private fun commonROMPermissionCheck(context: Context): Boolean =
if (RomUtils.checkIsMeizuRom()) meizuPermissionCheck(context) else {
var result = true
if (Build.VERSION.SDK_INT >= 23) try {
val clazz = Settings::class.java
val canDrawOverlays = clazz.getDeclaredMethod("canDrawOverlays", Context::class.java)
result = canDrawOverlays.invoke(null, context) as Boolean
} catch (e: Exception) {
Log.e(TAG, Log.getStackTraceString(e))
}
result
}
如果你要做的是應(yīng)用內(nèi)的全局懸浮窗,那么對(duì)不起,不支持,自己想辦法。普遍的做法是在根布局 DecorView 直接塞進(jìn)去。
遙遙領(lǐng)先qr23.cn/AKFP8k
獲取
或者加mau123789是v直接領(lǐng)?。?
在鴻蒙上實(shí)現(xiàn)懸浮窗相對(duì)就要簡(jiǎn)單的多。
對(duì)于系統(tǒng)級(jí)別彈窗,仍然需要權(quán)限,但也不至于那么麻煩的適配。
對(duì)于應(yīng)用內(nèi)全局彈出,鴻蒙提供了 應(yīng)用子窗口 可以直接實(shí)現(xiàn)。
本文主要介紹如何利用應(yīng)用子窗口實(shí)現(xiàn)應(yīng)用內(nèi)全局懸浮窗。
創(chuàng)建應(yīng)用子窗口需要先拿到窗口管理器 WindowStage 對(duì)象,在 EntryAbility.onWindowStageCreate()
回調(diào)中取。
FloatManager.init(windowStage)
init(windowStage: window.WindowStage) {
this.windowStage_ = windowStage
}
然后通過(guò) WindowStage.createSubWindow()
創(chuàng)建子窗口。
// 創(chuàng)建子窗口
showSubWindow() {
if (this.windowStage_ == null) {
Log.error(TAG, 'Failed to create the subwindow. Cause: windowStage_ is null');
} else {
this.windowStage_.createSubWindow("HarmonyWorld", (err: BusinessError, data) = > {
...
this.sub_windowClass = data;
// 子窗口創(chuàng)建成功后,設(shè)置子窗口的位置、大小及相關(guān)屬性等
// moveWindowTo 和 resize 都可以重復(fù)調(diào)用,實(shí)現(xiàn)拖拽效果
this.sub_windowClass.moveWindowTo(this.locationX, this.locationY, (err: BusinessError) = > {
...
});
this.sub_windowClass.resize(this.size, this.size, (err: BusinessError) = > {
...
});
// 給子窗口設(shè)置內(nèi)容
this.sub_windowClass.setUIContent("pages/float/FloatPage", (err: BusinessError) = > {
...
// 顯示子窗口。
(this.sub_windowClass as window.Window).showWindow((err: BusinessError) = > {
...
// 設(shè)置透明背景
data.setWindowBackgroundColor("#00000000")
});
});
})
}
}
這樣就可以在指定位置顯示指定大小的的懸浮窗了。
然后再接著完善手勢(shì)拖動(dòng)和點(diǎn)擊事件。
既要監(jiān)聽拖動(dòng),又要監(jiān)聽手勢(shì),就需要通過(guò) GestoreGroup
,并把設(shè)置模式設(shè)置為 互斥識(shí)別 。
@Entry
@Component
export struct FloatPage {
private context = getContext(this) as common.UIAbilityContext
build() {
Column() {
Image($r('app.media.mobile_dev'))
.width('100%')
.height('100%')
}
.gesture(
GestureGroup(GestureMode.Exclusive,
// 監(jiān)聽拖動(dòng)
PanGesture()
.onActionUpdate((event: GestureEvent | undefined) = > {
if (event) {
// 更新懸浮窗位置
FloatManager.updateLocation(event.offsetX, event.offsetY)
}
}),
// 監(jiān)聽點(diǎn)擊
TapGesture({ count: 1 })
.onAction(() = > {
router.pushUrl(...)
}))
)
}
}
在拖動(dòng)手勢(shì) PanGesture
的 onActionUpdate()
回調(diào)中,可以實(shí)時(shí)拿到拖動(dòng)的距離,然后通過(guò) Window.moveWindowTo()
就可以實(shí)時(shí)更新懸浮窗的位置了。
updateLocation(offSetX: number, offsetY: number) {
if (this.sub_windowClass != null) {
this.locationX = this.locationX + offSetX
this.locationY = this.locationY + offsetY
this.sub_windowClass.moveWindowTo(this.locationX, this.locationY, (err: BusinessError) = > {
......
});
}
}
在點(diǎn)擊手勢(shì) TapGesture
中,我的需求是路由到指定頁(yè)面,直接調(diào)用 router.pushUrl()
??此坪苷5恼{(diào)用,在這里確得到了意想不到的結(jié)果。
發(fā)生頁(yè)面跳轉(zhuǎn)的并不是預(yù)期中的應(yīng)用主窗口,而是應(yīng)用子窗口。
把問(wèn)題拋到群里之后,得到了群友的熱心解答。
每個(gè) Window 對(duì)應(yīng)自己的 UIContext,UIContext 持有自己的 Router ,所以應(yīng)用主窗口和應(yīng)用子窗口的 Router 是相互獨(dú)立的。
那么,問(wèn)題就變成了如何在子窗口中讓主窗口進(jìn)行路由跳轉(zhuǎn)?通過(guò) EventHub
或者 emitter
都可以。emiiter 可以跨線程,這里并不需要,EventHub 寫起來(lái)更簡(jiǎn)單。我們?cè)邳c(diǎn)擊手勢(shì)中發(fā)送事件:
TapGesture({ count: 1 })
.onAction(() = > {
this.context.eventHub.emit("event_click_float")
})
在 EntryAbility
中訂閱事件:
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
eventHub.on("event_click_float", () = > {
if (this.mainRouter) {
this.mainRouter.pushUrl(...)
}
})
}
這里的 mainRouter
我們可以提前在主 Window 調(diào)用 loadContent()
之后獲?。?/p>
windowStage.loadContent(pages/Index', (err, data) = > {
this.mainRouter = this.windowClass!.getUIContext().getRouter()
});
最后還有一個(gè)小細(xì)節(jié),如果在拖動(dòng)懸浮窗之后,再使用系統(tǒng)的返回手勢(shì),按照預(yù)期應(yīng)該是主窗口的頁(yè)面返回,但這時(shí)候焦點(diǎn)在子窗口,主窗口并不會(huì)響應(yīng)返回手勢(shì)。
我們需要在子窗口承載的 Page 頁(yè)面監(jiān)聽 onBackPress()
,并通過(guò) EventHub 通知主窗口。
onBackPress(): boolean | void {
this.context.eventHub.emit("float_back")
}
主窗口接收到通知后,調(diào)用 mainRouter.back 。
eventHub.on("clickFloat", () = > {
if (this.mainRouter) {
this.mainRouter.back()
}
})
應(yīng)用內(nèi)全局,可拖拽的懸浮窗就完成了。
審核編輯 黃宇
-
鴻蒙
+關(guān)注
關(guān)注
57文章
2339瀏覽量
42805
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論