如果你要做的是系統級別的懸浮窗,就需要判斷是否具備懸浮窗權限。然而這又不是一個標準的動態權限,你需要兼容各種奇葩機型的懸浮窗權限判斷,下面的代碼來自于某著名開源庫: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
}
如果你要做的是應用內的全局懸浮窗,那么對不起,不支持,自己想辦法。普遍的做法是在根布局 DecorView 直接塞進去。
遙遙領先qr23.cn/AKFP8k
獲取
或者加mau123789是v直接領取!
在鴻蒙上實現懸浮窗相對就要簡單的多。
對于系統級別彈窗,仍然需要權限,但也不至于那么麻煩的適配。
對于應用內全局彈出,鴻蒙提供了 應用子窗口 可以直接實現。
本文主要介紹如何利用應用子窗口實現應用內全局懸浮窗。
創建應用子窗口需要先拿到窗口管理器 WindowStage 對象,在 EntryAbility.onWindowStageCreate()
回調中取。
FloatManager.init(windowStage)
init(windowStage: window.WindowStage) {
this.windowStage_ = windowStage
}
然后通過 WindowStage.createSubWindow()
創建子窗口。
// 創建子窗口
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;
// 子窗口創建成功后,設置子窗口的位置、大小及相關屬性等
// moveWindowTo 和 resize 都可以重復調用,實現拖拽效果
this.sub_windowClass.moveWindowTo(this.locationX, this.locationY, (err: BusinessError) = > {
...
});
this.sub_windowClass.resize(this.size, this.size, (err: BusinessError) = > {
...
});
// 給子窗口設置內容
this.sub_windowClass.setUIContent("pages/float/FloatPage", (err: BusinessError) = > {
...
// 顯示子窗口。
(this.sub_windowClass as window.Window).showWindow((err: BusinessError) = > {
...
// 設置透明背景
data.setWindowBackgroundColor("#00000000")
});
});
})
}
}
這樣就可以在指定位置顯示指定大小的的懸浮窗了。
然后再接著完善手勢拖動和點擊事件。
既要監聽拖動,又要監聽手勢,就需要通過 GestoreGroup
,并把設置模式設置為 互斥識別 。
@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,
// 監聽拖動
PanGesture()
.onActionUpdate((event: GestureEvent | undefined) = > {
if (event) {
// 更新懸浮窗位置
FloatManager.updateLocation(event.offsetX, event.offsetY)
}
}),
// 監聽點擊
TapGesture({ count: 1 })
.onAction(() = > {
router.pushUrl(...)
}))
)
}
}
在拖動手勢 PanGesture
的 onActionUpdate()
回調中,可以實時拿到拖動的距離,然后通過 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
中,我的需求是路由到指定頁面,直接調用 router.pushUrl()
。看似很正常的調用,在這里確得到了意想不到的結果。
發生頁面跳轉的并不是預期中的應用主窗口,而是應用子窗口。
把問題拋到群里之后,得到了群友的熱心解答。
每個 Window 對應自己的 UIContext,UIContext 持有自己的 Router ,所以應用主窗口和應用子窗口的 Router 是相互獨立的。
那么,問題就變成了如何在子窗口中讓主窗口進行路由跳轉?通過 EventHub
或者 emitter
都可以。emiiter 可以跨線程,這里并不需要,EventHub 寫起來更簡單。我們在點擊手勢中發送事件:
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 調用 loadContent()
之后獲取:
windowStage.loadContent(pages/Index', (err, data) = > {
this.mainRouter = this.windowClass!.getUIContext().getRouter()
});
最后還有一個小細節,如果在拖動懸浮窗之后,再使用系統的返回手勢,按照預期應該是主窗口的頁面返回,但這時候焦點在子窗口,主窗口并不會響應返回手勢。
我們需要在子窗口承載的 Page 頁面監聽 onBackPress()
,并通過 EventHub 通知主窗口。
onBackPress(): boolean | void {
this.context.eventHub.emit("float_back")
}
主窗口接收到通知后,調用 mainRouter.back 。
eventHub.on("clickFloat", () = > {
if (this.mainRouter) {
this.mainRouter.back()
}
})
應用內全局,可拖拽的懸浮窗就完成了。
審核編輯 黃宇
-
鴻蒙
+關注
關注
57文章
2339瀏覽量
42805
發布評論請先 登錄
相關推薦
評論