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

如果你要做的是系统级别的悬浮窗,就需要判断是否具备悬浮窗权限。然而这又不是一个标准的动态权限,你需要兼容各种奇葩机型的悬浮窗权限判断。

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

在鸿蒙上实现悬浮窗相对就要简单的多。

对于系统级别弹窗,仍然需要权限,但也不至于那么麻烦的适配。

对于应用内全局弹出,鸿蒙提供了 应用子窗口 可以直接实现。

本文主要介绍如何利用应用子窗口实现应用内全局悬浮窗。

创建应用子窗口需要先拿到窗口管理器 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()
  }
})

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/514009.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

IDEA 解决 java: 找不到符号 符号: 类 __ (使用了lombok的注解)

原因IDEA版本太高&#xff0c;在 ProcessingEnvironement 预编译的时候是以代理的方式来执行的&#xff0c;不再是直接 javac方式, lombok依赖的 javac方式的 annotation processors 不再生效了 解决办法&#xff1a;下面这一句&#xff0c;加在下图中 -Djps.track.ap.depen…

权限提升-Linux系统权限提升篇VulnhubRbash绕过DockerLXD容器History泄漏shell交互

知识点 1、普通用户到Linux-泄漏-History 2、普通用户到Linux-限制-Rbash绕过 3、普通用户到Linux-容器-LXD&Docker 4.Linux系统提权-web/普通用户-docker逃逸&提权&shell交互 章节点&#xff1a; 1、Web权限提升及转移 2、系统权限提升及转移 3、宿主权限提升及…

[计算机效率] 格式转换工具:格式工厂

3.14 格式转换工具&#xff1a;格式工厂 格式工厂是一款功能强大的多媒体格式转换软件&#xff0c;可以实现音频、视频、图片等多种格式的转换。它支持几乎所有类型的多媒体格式&#xff0c;包括视频、音频、图片、字幕等&#xff0c;可以轻松实现格式之间的转换&#xff0c;并…

java Web 健身管理系统idea开发mysql数据库LayUI框架java编程计算机网页源码maven项目

一、源码特点 java Web健身管理系统是一套完善的信息管理系统&#xff0c;结合java 开发技术和bootstrap完成本系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 前段主要技术 layUI bootst…

【前端面试3+1】10 npm run dev 发生了什么、vue的自定义指令如何实现、js的数据类型有哪些及其不同、【最长公共前缀】

一、npm run dev发生了什么 运行npm run dev时&#xff0c;通常是在一个基于Node.js的项目中&#xff0c;用来启动开发服务器或者执行一些开发环境相关的任务。下面是一般情况下npm run dev会执行的步骤&#xff1a; 1. 查找package.json中的scripts字段&#xff1a; npm会在项…

SQL server 查询数据库中所有的表名及行数

SQL server 查询数据库中所有的表名及行数 select a.name,b.rows from sysobjects as ainner join sysindexes as bon a.id b.id where (a.type u)and (b.indid in (0, 1)) and b.rows<50 and b.rows>20 order by a.name, b.rows desc;

elementui 左侧或水平导航菜单栏与main区域联动

系列文章目录 一、elementui 导航菜单栏和Breadcrumb 面包屑关联 二、elementui 左侧导航菜单栏与main区域联动 三、elementui 中设置图片的高度并支持PC和手机自适应 四、elementui 实现一个固定位置的Pagination&#xff08;分页&#xff09;组件 文章目录 系列文章目录…

glm2大语言模型服务环境搭建

一、模型介绍 ChatGLM2-6B 是开源中英双语对话模型 ChatGLM-6B 的第二代版本&#xff0c;在保留了初代模型对话流畅、部署门槛较低等众多优秀特性的基础之上&#xff0c;ChatGLM2-6B 引入了如下新特性&#xff1a; 更强大的性能&#xff1a;基于 ChatGLM 初代模型的开发经验&…

当Pycharm中右键运行python程序时出现Run ‘pytest in tests ***py‘,如何解决?

1、在Pycharm中右键运行python程序时出现Run pytest in tests ***py &#xff0c;这是进入了Pytest模式。 2、解决办法 进入到File->Settings->Tools->Python integrated Tools页面或者快捷键&#xff08;CtrlAltS&#xff09; 找到Testing下的Default test runner …

用QT调用FFMPEG的接口

1、调用FFmpeg接口 打开Qt&#xff0c;新建工程&#xff1b; 在工程的pro文件中&#xff0c;添加如下字段&#xff1b; TEMPLATE app CONFIG console CONFIG - app_bundle CONFIG - qtSOURCES \main.c INCLUDEPATH /home/wxw/ffmpeg_build/includeLIBS /home/wxw/ffmpe…

centOS如何升级python

centOS下升级python版本的详细步骤 1、可利用linux自带下载工具wget下载&#xff0c;如下所示&#xff1a; 笔者安装的是最小centos系统&#xff0c;所以使用编译命令前&#xff0c;必须安装wget服务&#xff0c;读者如果安装的是界面centos系统&#xff0c;或者使用过编译工具…

drawio画图编辑图形颜色

drawio画图编辑图形颜色 团队的安全第一图表。将您的存储空间带到我们的在线工具中&#xff0c;或使用桌面应用程序进行本地保存。 1.安装准备 1.1安装平台 多平台 1.2在线使用 浏览器打开网页使用 1.3软件下载 drawio官网github仓库下载 2.在浏览器的网页中使用drawio…

c++20协程详解(三)

前言 前面两节我们已经能够实现一个可用的协程框架了。但我们一定还想更深入的了解协程&#xff0c;于是我们就想尝试下能不能co_await一个协程。下面会涉及到部分模板编程的知识&#xff0c;主要包括&#xff08;模板偏特化&#xff0c;模板参数列表传值&#xff0c;模板函数…

redis事务(redis features)

redis支持事务&#xff0c;也就是可以在一次请求中执行多个命令。redis中的事务主要是通过MULTI和EXEC这两个命令来实现的。 MULTI命令用来开启一个事务&#xff0c;事务开启之后&#xff0c;所有的命令就都会被放入到一个队列中&#xff0c;最后通过一个EXEC命令来执行事务中…

【MATLAB源码-第29期】基于matlab的MIMO,MISO,SIMO,SISO瑞利rayleigh信道容量对比。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 1. SISO&#xff08;单输入单输出&#xff09;&#xff1a; - SISO 是指在通信系统中&#xff0c;只有一个天线用于传输信号&#xff0c;也只有一个天线用于接收信号的情况。这是最简单的通信方式。 2. SIMO&#xff08;单…

基于AI智能识别技术的智慧展览馆视频监管方案设计

一、建设背景 随着科技的不断进步和社会安全需求的日益增长&#xff0c;展览馆作为展示文化、艺术和科技成果的重要场所&#xff0c;其安全监控系统的智能化升级已成为当务之急。为此&#xff0c;旭帆科技&#xff08;TSINGSEE青犀&#xff09;基于视频智能分析技术推出了展览…

单例模式(加深版)

单例模式&#xff08;加深版&#xff09; 饿汉模式 缺点&#xff1a;造成资源的浪费 示例&#xff1a; ## 懒汉模式 示例&#xff1a; 枚举类型单例模式 示例&#xff1a;

ElasticSearch7.8的下载与安装和Kibana 7.8.0工具使用安装

1、ElasticSearch7.8.0下载 elasticsearch: 官方下载地址&#xff1a;https://www.elastic.co/cn/downloads/elasticsearch 链接: https://pan.baidu.com/s/1wAKQoB3nhLhcnBlPfVOLxQ 提取码: t83n kibana: 链接: https://pan.baidu.com/s/156aD9zDdvUv8LFgDEIPoSw 提取码:…

案例分析-程序的机器级表示

案例一&#xff1a;关于编译优化 请自写一段if- else简单分支程序&#xff0c;分别尝试对它进行不带优化、-O1优化和-O2优化&#xff0c;比较它们的机器级表达&#xff0c;并讨论优劣。 图一为不带优化、图二为O1优化、图三为O2优化、图四为原始C代码。 &#xff08;1&#xff…

Docker实战教程 第1章 Linux快速入门

2-1 Linux介绍 为什么要学Linux 三个不得不学习 课程需要&#xff1a;Docker开发最好在Linux环境下。 开发需要&#xff1a;作为一个后端程序员&#xff0c;是必须要掌握Linux的&#xff0c;这是找工作的基础门槛。 运维需要&#xff1a;在服务器端&#xff0c;主流的大型服…