如果你要做的是系統(tǒng)級別的懸浮窗,就需要判斷是否具備懸浮窗權(quán)限。然而這又不是一個標(biāo)準(zhǔn)的動態(tài)權(quán)限,你需要兼容各種奇葩機(jī)型的懸浮窗權(quán)限判斷,下面的代碼來自于某著名開源庫: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)的全局懸浮窗,那么對不起,不支持,自己想辦法。普遍的做法是在根布局 DecorView 直接塞進(jìn)去。
遙遙領(lǐng)先qr23.cn/AKFP8k
獲取
或者加mau123789是v直接領(lǐng)取!
在鴻蒙上實現(xiàn)懸浮窗相對就要簡單的多。
對于系統(tǒng)級別彈窗,仍然需要權(quán)限,但也不至于那么麻煩的適配。
對于應(yīng)用內(nèi)全局彈出,鴻蒙提供了 應(yīng)用子窗口 可以直接實現(xiàn)。
本文主要介紹如何利用應(yīng)用子窗口實現(xiàn)應(yīng)用內(nèi)全局懸浮窗。
創(chuàng)建應(yīng)用子窗口需要先拿到窗口管理器 WindowStage 對象,在 EntryAbility.onWindowStageCreate()
回調(diào)中取。
FloatManager.init(windowStage)
init(windowStage: window.WindowStage) {
this.windowStage_ = windowStage
}
然后通過 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)用,實現(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")
});
});
})
}
}
這樣就可以在指定位置顯示指定大小的的懸浮窗了。
然后再接著完善手勢拖動和點擊事件。
既要監(jiān)聽拖動,又要監(jiān)聽手勢,就需要通過 GestoreGroup
,并把設(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)聽拖動
PanGesture()
.onActionUpdate((event: GestureEvent | undefined) = > {
if (event) {
// 更新懸浮窗位置
FloatManager.updateLocation(event.offsetX, event.offsetY)
}
}),
// 監(jiān)聽點擊
TapGesture({ count: 1 })
.onAction(() = > {
router.pushUrl(...)
}))
)
}
}
在拖動手勢 PanGesture
的 onActionUpdate()
回調(diào)中,可以實時拿到拖動的距離,然后通過 Window.moveWindowTo()
就可以實時更新懸浮窗的位置了。
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) = > {
......
});
}
}
在點擊手勢 TapGesture
中,我的需求是路由到指定頁面,直接調(diào)用 router.pushUrl()
。看似很正常的調(diào)用,在這里確得到了意想不到的結(jié)果。
發(fā)生頁面跳轉(zhuǎn)的并不是預(yù)期中的應(yīng)用主窗口,而是應(yīng)用子窗口。
把問題拋到群里之后,得到了群友的熱心解答。
每個 Window 對應(yīng)自己的 UIContext,UIContext 持有自己的 Router ,所以應(yīng)用主窗口和應(yīng)用子窗口的 Router 是相互獨立的。
那么,問題就變成了如何在子窗口中讓主窗口進(jìn)行路由跳轉(zhuǎn)?通過 EventHub
或者 emitter
都可以。emiiter 可以跨線程,這里并不需要,EventHub 寫起來更簡單。我們在點擊手勢中發(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()
之后獲取:
windowStage.loadContent(pages/Index', (err, data) = > {
this.mainRouter = this.windowClass!.getUIContext().getRouter()
});
最后還有一個小細(xì)節(jié),如果在拖動懸浮窗之后,再使用系統(tǒng)的返回手勢,按照預(yù)期應(yīng)該是主窗口的頁面返回,但這時候焦點在子窗口,主窗口并不會響應(yīng)返回手勢。
我們需要在子窗口承載的 Page 頁面監(jiān)聽 onBackPress()
,并通過 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文章
2307瀏覽量
42739
發(fā)布評論請先 登錄
相關(guān)推薦
評論