0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

鸿蒙实战开发:【实现应用悬浮窗】

jf_46214456 来源:jf_46214456 作者:jf_46214456 2024-04-03 22:18 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

如果你要做的是系统级别的悬浮窗,就需要判断是否具备悬浮窗权限。然而这又不是一个标准的动态权限,你需要兼容各种奇葩机型的悬浮窗权限判断,下面的代码来自于某著名开源库: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获取

.png

或者加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(...)
          }))
    )
  }
}

在拖动手势 PanGestureonActionUpdate() 回调中,可以实时拿到拖动的距离,然后通过 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()
  }
})

应用内全局,可拖拽的悬浮窗就完成了。

审核编辑 黄宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 鸿蒙
    +关注

    关注

    60

    文章

    2858

    浏览量

    45354
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    鸿蒙5开发宝藏案例分享---一多开发实例(音乐)

    各位开发者小伙伴们好呀!今天咱们来点硬核干货!最近在鸿蒙文档中心挖到一座“金矿”——官方竟然暗藏了100+实战案例,从分布式架构到交互动效优化应有尽有!这些案例不仅藏着华为工程师的私房技巧,还直接
    的头像 发表于 06-30 11:54 619次阅读

    HarmonyOS实战实现任意拖动的应用悬浮窗口

    为了增加应用程序功能的丰富性和便利性,很多应用都会提供一个悬浮窗口实现多页面显示。特别是一些性能检测工具,比如 dokit 。在鸿蒙上怎么实现类似的全局
    的头像 发表于 06-24 17:04 1139次阅读

    鸿蒙5开发宝藏案例分享---埋点开发实战指南

    鸿蒙埋点开发宝藏指南:官方案例实战解析,轻松搞定数据追踪! 大家好呀!我是HarmonyOS开发路上的探索者。最近在折腾应用埋点时,意外发现了鸿蒙
    发表于 06-12 16:30

    鸿蒙5开发宝藏案例分享---切面编程实战揭秘

    鸿蒙切面编程(AOP)实战指南:隐藏的宝藏功能大揭秘! 大家好!今天在翻鸿蒙开发者文档时,意外发现了官方埋藏的「切面编程」宝藏案例!实际开发
    发表于 06-12 16:21

    鸿蒙5开发宝藏案例分享---应用架构实战技巧

    大家好! 今天咱们聊聊鸿蒙开发中那些“官方文档提了但实际开发难找”的架构设计技巧。结合官方文档,我会用 真实代码案例+通俗讲解 ,帮你把分层架构和线程通信落地到项目里,告别“理论会了,代码不会
    发表于 06-12 16:14

    鸿蒙5开发宝藏案例分享---PC开发案例解析

    鸿蒙PC/2in1开发宝藏指南:官方案例实战解析 大家好呀! 最近在折腾鸿蒙的PC/2in1应用开发,才发现官方文档里藏了一堆超实用的案例!
    发表于 06-12 16:07

    鸿蒙5开发宝藏案例分享---平板开发实践

    以下是根据官方文档整理的鸿蒙平板开发实战指南,结合代码解析和避坑要点,帮你高效实现“一次开发,多端部署”? 一、开篇:为什么平板
    发表于 06-12 15:49

    鸿蒙5开发宝藏案例分享---Pura X开发案例分享

    ?** 鸿蒙宝藏案例分享:Pura X 外屏开发实战解析** 大家好!我是你们的鸿蒙开发小伙伴。今天在翻阅官方文档时,意外发现了华为藏着的\
    发表于 06-12 11:47

    鸿蒙5开发宝藏案例分享---一多开发实例(游戏)

    ?【开发者必看】鸿蒙隐藏宝箱大公开!这些实战案例让你的开发效率翻倍! 哈喽各位开发者小伙伴!今天要和大家分享一个让我拍大腿的发现——原来
    发表于 06-03 18:22

    鸿蒙5开发案例分享揭秘---一多开发实例(商务办公)

    ?【鸿蒙开发宝藏案例大揭秘】原来官方文档里藏了这么多好东西! 大家好呀~最近在肝鸿蒙项目时意外扒出了官方文档里的\"藏宝库\"!原来那些让人头秃的跨端适配难题,官方早就准备好
    发表于 06-03 16:24

    鸿蒙5开发宝藏案例分享---一多开发实例(地图导航)

    ? 鸿蒙开发隐藏宝藏大公开!手把手教你玩转\"一多\"地图导航案例 ? 大家好呀!我是你们的老朋友,今天要给大家扒一扒鸿蒙官方文档里那些\"藏得深\"的实战
    发表于 06-03 16:17

    鸿蒙5开发宝藏案例分享---一多开发实例(购物比价)

    者文档里的实战教程,简直就是搞定多端开发的通关秘籍! ? 一、这些功能也太酷了吧! 这个案例完美诠释了鸿蒙\"一次开发,多端部署\"的超能力,这些神仙功能你一定要知道: 智能分屏比价
    发表于 06-03 16:07

    鸿蒙5开发宝藏案例分享---折叠屏悬停态开发实践

    ?【鸿蒙折叠屏开发宝藏指南】原来官方藏了这么多好东西!手把手教你玩转悬停态开发**?** Hey小伙伴们!我是你们的老朋友XX,最近在肝鸿蒙折叠屏项目时,意外挖到了官方文档里的隐藏宝藏
    发表于 06-03 12:04

    鸿蒙5开发宝藏案例分享---一多分栏开发实践

    ?【HarmonyOS开发者的宝藏指南】一次搞定多设备分栏布局,原来还能这么玩! 大家好呀!今天在鸿蒙社区挖到一个超实用的大宝藏——原来官方早就藏了一堆分栏布局的实战案例!作为被多端适配折磨
    发表于 06-03 12:03

    鸿蒙Flutter实战:14-现有Flutter 项目支持鸿蒙 II

    分别安装官方的3.22版本,以及鸿蒙社区的 3.22.0 版本 3.搭建 Flutter鸿蒙开发环境 参考文章《鸿蒙Flutter实战:0
    发表于 12-26 14:59