鸿蒙OS开发实战:【悬浮窗口】

 背景

悬浮视图或者窗体,在Android和iOS两大移动平台均有使用,HarmonyOS 也实现了此功能,如下为大家分享一下效果

准备

  1. 熟读HarmonyOS 悬浮窗口指导

  2. 熟读HarmonyOS 手势指导

  3. 熟读ALC签名指导,用于可以申请 “ohos.permission.SYSTEM_FLOAT_WINDOW” 权限。

  4. 熟悉的文档在下方

鸿蒙OS开发更多内容↓点击HarmonyOS与OpenHarmony技术
鸿蒙技术文档开发知识更新库gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md在这。或+mau123789学习,是v喔

搜狗高速浏览器截图20240326151547.png

实践代码

  1. 如果开启了悬浮窗口,任何界面的物理返回键事件都会被悬浮窗口拦截掉,即 手势返回废了

  2. 参数类型易混淆, 拖动 PanGesture 中的onActionUpdate接口,数据单位为vp,window中的 moveWindowTo接口参数,数据单位为px

  3. 采用moveWindowTo实现的窗口拖动效果十分不平滑

  4. 通过 requestPermissionsFromUser 申请 ohos.permission.SYSTEM_FLOAT_WINDOW 权限时,无法弹出系统权限提示框

片段代码

配置module.json5

{
  "module": {
    "name": "entry",
    "type": "entry",
    "description": "$string:module_desc",
    "mainElement": "EntryAbility",
    "deviceTypes": [
      "phone",
      "tablet"
    ],
    "deliveryWithInstall": true,
    "installationFree": false,
    "pages": "$profile:main_pages",
    "abilities": [
......
      {
        "name": "FloatWindowAbility",
        "srcEntry": "./ets/myentryability/FloatWindowAbility.ts",
        "description": "$string:FloatWindowAbility_desc",
        "icon": "$media:icon",
        "label": "$string:FloatWindowAbility_label",
        "startWindowIcon": "$media:icon",
        "startWindowBackground": "$color:start_window_background",
      },
    ],
    "requestPermissions": [
       {
        "name": "ohos.permission.SYSTEM_FLOAT_WINDOW",
        "usedScene": {
          "abilities": [
            "FloatWindowAbility"
          ],
          "when": "always"
        }
      }
    ]
  }
}

悬浮窗口UIAbility

import window from '@ohos.window';
import BaseUIAbility from '../baseuiability/BaseUIAbility';
import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
import bundleManager from '@ohos.bundle.bundleManager';

const permissions: Array<Permissions> = ['ohos.permission.SYSTEM_FLOAT_WINDOW'];

export default class FloatWindowAbility extends BaseUIAbility {

  onWindowStageCreate(windowStage: window.WindowStage) {
    // Main window is created, set main page for this ability
    let context = this.context;
    let atManager = abilityAccessCtrl.createAtManager();

    checkPermissions().then((result)=>{
      if(result){
        // requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
        atManager.requestPermissionsFromUser(context, permissions).then((data) => {
          let grantStatus: Array<number> = data.authResults;
          let length: number = grantStatus.length;

          for (let i = 0; i < length; i++) {
            if (grantStatus[i] === 0) {
              // 用户授权,可以继续访问目标操作
              console.log('用户授权,可以继续访问目标操作')
            } else {
              // 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限
              console.log('用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限')
              return;
            }
          }

          // 授权成功
          // 1.创建悬浮窗。
          let windowClass = null;
          let config = {name: "floatWindow", windowType: window.WindowType.TYPE_FLOAT, ctx: this.context};
          window.createWindow(config, (err, data) => {
            if (err.code) {
              console.error('Failed to create the floatWindow. Cause: ' + JSON.stringify(err));
              return;
            }
            console.info('Succeeded in creating the floatWindow. Data: ' + JSON.stringify(data));
            windowClass = data;

            // 2.悬浮窗窗口创建成功后,设置悬浮窗的位置、大小及相关属性等。
            windowClass.moveWindowTo(0, 200, (err) => {
              if (err.code) {
                console.error('Failed to move the window. Cause:' + JSON.stringify(err));
                return;
              }
              console.info('Succeeded in moving the window.');
            });
            windowClass.resize(1080, 151, (err) => {
              if (err.code) {
                console.error('Failed to change the window size. Cause:' + JSON.stringify(err));
                return;
              }
              console.info('Succeeded in changing the window size.');

            });

            // 3.为悬浮窗加载对应的目标页面。
            windowClass.setUIContent("custompages/FloatPage", (err) => {
              if (err.code) {
                console.error('Failed to load the content. Cause:' + JSON.stringify(err));
                return;
              }
              console.info('Succeeded in loading the content.');
              // 3.显示悬浮窗。
              windowClass.showWindow((err) => {
                if (err.code) {
                  console.error('Failed to show the window. Cause: ' + JSON.stringify(err));
                  return;
                }
                console.info('Succeeded in showing the window.');
              });

              try {
                windowClass.setWindowBackgroundColor('#00000000')
              } catch (exception) {
                console.error('Failed to set the background color. Cause: ' + JSON.stringify(exception));
              }

            });

          })

        }).catch((err) => {
          console.error(`requestPermissionsFromUser failed, code is ${err.code}, message is ${err.message}`);
        })

      }
    })

  }

}

async function checkAccessToken(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
  let atManager = abilityAccessCtrl.createAtManager();
  let grantStatus: abilityAccessCtrl.GrantStatus;

  // 获取应用程序的accessTokenID
  let tokenId: number;
  try {
    let bundleInfo: bundleManager.BundleInfo = await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
    let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
    tokenId = appInfo.accessTokenId;
  } catch (err) {
    console.error(`getBundleInfoForSelf failed, code is ${err.code}, message is ${err.message}`);
  }

  // 校验应用是否被授予权限
  try {
    grantStatus = await atManager.checkAccessToken(tokenId, permission);
  } catch (err) {
    console.error(`checkAccessToken failed, code is ${err.code}, message is ${err.message}`);
  }

  return grantStatus;
}

async function checkPermissions(): Promise<boolean> {
  const permissions: Array<Permissions> = ['ohos.permission.SYSTEM_FLOAT_WINDOW'];
  let grantStatus: abilityAccessCtrl.GrantStatus = await checkAccessToken(permissions[0]);

  if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
    // 已经授权,可以继续访问目标操作
    console.log('没有授权')
    return true
  } else {
    // 申请日历权限
    console.log('已授权')
    return false
  }
}

悬浮窗口页面

import common from '@ohos.app.ability.common';
import window from '@ohos.window';

@Entry
@Component
struct Index {
  @State lasttime: number = 0

  @State message: string = '悬浮窗'
  @State foldStatus: boolean = false
  @State idleName: string = '收起'
  @State floatWindowWidth: number = 0
  @State offsetX: number = 0
  @State offsetY: number = 0
  @State positionX: number = 0
  @State positionY: number = 0
  @State windowPosition: Position = { x: 0, y: 0 };

  private context = getContext(this) as common.UIAbilityContext;
  private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.All });

  floatWindow: window.Window

  aboutToAppear(){
    this.eventHubFunc()
    this.floatWindow = window.findWindow("floatWindow")
    this.floatWindowWidth = 1080
    this.panOption.setDistance(1)
  }

  onBackPress(){
    console.log('返回')
  }

  build() {
    Row() {

         Text('X').width(px2vp(140))
           .textAlign(TextAlign.Center)
           .fontColor(Color.Red).onClick(()=>{
           //关闭所依赖的UIAbility
           this.context.terminateSelf()
           //销毁悬浮窗。当不再需要悬浮窗时,可根据具体实现逻辑,使用destroy对其进行销毁。
           this.floatWindow.destroyWindow((err) => {
             if (err.code) {
               console.error('Failed to destroy the window. Cause: ' + JSON.stringify(err));
               return;
             }
             console.info('Succeeded in destroying the window.');
           });
         })

         Text(this.idleName)
           .width(px2vp(140))
           .height('100%')
           .fontSize(18)
           .fontColor(Color.White)
           .textAlign(TextAlign.Center)
           .backgroundColor(Color.Gray)
           .onClick(()=>{
             this.foldStatus = !this.foldStatus

             if(this.foldStatus){
               this.idleName = "展开"
               this.floatWindowWidth = 280
             } else {
               this.idleName = "收起"
               this.floatWindowWidth = 1080
             }
           })

         Divider().vertical(true).color(Color.Red)

         if(!this.foldStatus) {
           Text(this.message)
             .width(px2vp(800))
             .fontSize(18)
             .fontColor(Color.White)
             .padding('12vp')
         }

    }
    .width(px2vp(this.floatWindowWidth))
    .height(px2vp(150))
    .borderRadius('12vp')
    .backgroundColor(Color.Green)
    .gesture(
      // 绑定PanGesture事件,监听拖拽动作
      PanGesture(this.panOption)
        .onActionStart((event: GestureEvent) => {
          console.info('Pan start');
        })
          // 发生拖拽时,获取到触摸点的位置,并将位置信息传递给windowPosition
        .onActionUpdate((event: GestureEvent) => {

          console.log(event.offsetX +' ' + event.offsetY)

          this.offsetX = this.positionX + event.offsetX
          this.offsetY = this.positionY + event.offsetY

          this.floatWindow.moveWindowTo(vp2px(this.offsetX), vp2px(this.offsetY));

        })
        .onActionEnd(() => {
          this.positionX = this.offsetX
          this.positionY = this.offsetY
          console.info('Pan end');
        })
    )

  }

  eventHubFunc() {
    this.context.eventHub.on('info', (data) => {
        this.message = data
    });
  }

}

鸿蒙Next核心技术分享

1、鸿蒙基础知识←《鸿蒙NEXT星河版开发学习文档》

2、鸿蒙ArkUI←《鸿蒙NEXT星河版开发学习文档》

3、鸿蒙进阶技术←《鸿蒙NEXT星河版开发学习文档》

 4、鸿蒙就业高级技能←《鸿蒙NEXT星河版开发学习文档》 

 5、鸿蒙多媒体技术←《鸿蒙NEXT星河版开发学习文档》 

6、鸿蒙南向驱动开发←《鸿蒙NEXT星河版开发学习文档》  

7、鸿蒙南向内核设备开发←《鸿蒙NEXT星河版开发学习文档》  

 8、鸿蒙系统裁剪与移植←《鸿蒙NEXT星河版开发学习文档》  

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

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

相关文章

github | ssh拉取github仓库报错connect to host github.com port 22: Connection refused

配置ssh key 通过 ssh key 解决本地和服务器连接的问题 $ cd ~/. ssh #检查本机已存在的ssh密钥 如果提示 No such file or directory 则表示第一次使用git 输入&#xff1a; ssh-keygen -t rsa -C "邮件地址" 并且连续3次回车&#xff0c;最终会生成一个文件&am…

如何在Flutter中进行网络请求?

Hello&#xff01;大家好&#xff0c;我是咕噜铁蛋&#xff0c;你们的好朋友&#xff01;今天&#xff0c;我想和大家分享一下在Flutter中如何进行网络请求。Flutter作为一个跨平台的开发框架&#xff0c;网络请求是其实现数据交互的重要一环。下面&#xff0c;我将详细介绍几种…

JVM实战之性能调优[2](线程转储案例认识和分析)

文章目录 版权声明案例1&#xff1a;CPU占用率高问题问题描述解决思路补充内容 案例2&#xff1a;接口响应时间长问题问题描述解决思路Arthas trace命令Arthas watch命令解决问题 案例3&#xff1a;定位偏底层性能问题问题描述解决思路&#xff1a;Arthas火焰图问题解决 案例4&…

Siemens S7-1500TCPU 运动机构系统功能简介

目录 引言&#xff1a; 1.0 术语定义 2.0 基本知识 2.1 运动系统工艺对象 2.2 坐标系与标架 3.0 运动机构系统类型 3.1 直角坐标型 3.2 轮腿型 3.3 平面关节型 3.4 关节型 3.5 并联型 3.6 圆柱坐标型 3.7 三轴型 4.0 运动系统的运动 4.1 运动类型 4.1.1 线性运动…

ArcGIS Pro横向水平图例

终于知道ArcGIS Pro怎么调横向图例了&#xff01; 简单的像0一样 旋转&#xff0c;左转右转随便转 然后调整图例项间距就可以了&#xff0c;参数太多就随便试&#xff0c;总有一款适合你&#xff01; 要调整长度&#xff0c;就调整图例块的大小。完美&#xff01; 好不容易…

win10+cuda11.8+cudnn8.6.0安装

目录 一、NVIDIA 驱动程序下载 二、cuda11.8下载 三、cudnn8.6.0下载 四、确认cuda和cudnn是否安装成功 一、NVIDIA 驱动程序下载 1、查看显卡类型&#xff1a;连续按下CTRLALTDELETE -> 选择任务管理器 -> 性能 -> GPU -> 右上角 2、下载地址&#xff1a;官方…

阿里云CentOS7安装Hadoop3伪分布式

ECS准备 开通阿里云ECS 略 控制台设置密码 连接ECS 远程连接工具连接阿里云ECS实例&#xff0c;这里远程连接工具使用xshell 根据提示接受密钥 根据提示写用户名和密码 用户名&#xff1a;root 密码&#xff1a;在控制台设置的密码 修改主机名 将主机名从localhost改为需要…

iPhone用GPT替代Siri

shigen坚持更新文章的博客写手&#xff0c;擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长&#xff0c;分享认知&#xff0c;留住感动。 个人IP&#xff1a;shigen 前一段时间&#xff0c;因为iCloud协议的更新&#xff0c;我的云盘空间无法正常…

骗子查询系统源码

源码简介 小权云黑管理系统 V1.0 功能如下&#xff1a; 1.添加骗子&#xff0c;查询骗子 2.可添加团队后台方便审核用 3.在线反馈留言系统 4.前台提交骗子&#xff0c;后台需要审核才能过 5.后台使用光年UI界面 6.新增导航列表&#xff0c;可给网站添加导航友链 7.可添加云黑类…

Nginx第三方模块---nginx-sticky-module的使用(基于cookie的会话保持)

目录 Nginx和Sticky介绍 编译安装sticky的nginx环境 安装过程碰到的问题和编译安装过程遇到的错误&#xff1a; &#xff08;1&#xff09;第一个错误&#xff1a;修改源代码ngx_http_sticky_module.c &#xff08;2&#xff09;第二个错误&#xff1a;修改源代码ngx_http_s…

课堂练习——4、实验环境练习

任务描述 本关任务&#xff1a;修改 Linux 0.11 内核程序&#xff0c;将系统启动时显示的字符串由原来的Partition table ok.变为Hello, world!。 通过本关任务&#xff0c;可以熟悉 Linux 0.11 内核实验环境&#xff0c;掌握内核程序的编辑、编译和测试方法。 相关知识 为了…

Java基础(概念,环境,包,IDEA,)

目录 什么是Java 什么是程序 Java简史 Java技术体系平台 Java语言的特点 搭建环境 搭建Java开发环境 理解三个核心概念 安装Java环境 配置环境变量 编写第一段代码&#xff1a;HelloWorld 创建源代码文件 编写代码 保存文件 编译Java程序 运行程序 查看输出 编…

C#String的remove的用法

string test "abc";string temp test;temp.Remove(0, 1);temp temp.Remove(0, 1);Console.WriteLine(temp);Console.WriteLine(test);执行结果

前端基础知识html

一.基础标签 1.<h1>-<h6>:定义标题&#xff0c;h最大&#xff0c;h最小 2.<font>&#xff1a;定义文本的字体&#xff0c;尺寸&#xff0c;颜色 3.<b>&#xff1a;定义粗体文本 4.<i>&#xff1a;定义斜体文本 5.<u>&#xff1a;定义文本下…

MGRE实验

MGRE实验 1、实验要求 2、实验分析 IP地址分类 私网IP&#xff1a;192.168.1.0等隧道IP&#xff1a;192.168.5.0和192.168.6.0公网IP&#xff1a;15.0.0.1等 配置IP地址 配置acl访问控制列表 用于将内部网络中的私有IP地址转换为公共IP地址&#xff0c;以实现与外部网络的通…

[flink 实时流基础系列]揭开flink的什么面纱基础一

Apache Flink 是一个框架和分布式处理引擎&#xff0c;用于在无边界和有边界数据流上进行有状态的计算。Flink 能在所有常见集群环境中运行&#xff0c;并能以内存速度和任意规模进行计算。 文章目录 0. 处理无界和有界数据无界流有界流 1. Flink程序和数据流图2. 为什么一定要…

多焦点图像融合文献学习(一)

本文介绍的是一篇明为"A convolutional neural network-based conditional random field model for structured multi-focus image fusion robust to noise."的文献&#xff0c;主要包括文献的摘要、前言摘选、主要贡献、网络结构、实验结果及结论等方面。 文献名称摘…

浅谈Mysql(三)——MySQL/InnoDB 事务隔离级别分享

主要内容 事务特性 • 原子性&#xff08;Atomicity • 一致性&#xff08;Consistency&#xff09; • 隔离性&#xff08;Isolation&#xff09; • 持久性&#xff08;Durability 日志体系-更新语句的执行过程 • redo log • binlog 事务隔离 隔离性遇见的问题 隔离级…

Golang-Gin光速入门

安装 go get -u github.com/gin-gonic/gin初始化项目并启动服务 go mod init gin-project package mainimport "github.com/gin-gonic/gin"func main() {r : gin.Default()r.GET("/ping", func(c *gin.Context) {c.JSON(200, gin.H{"message"…

2.11 Python关键字(保留字)

Python关键字&#xff08;保留字&#xff09;一览表 保留字是Python 语言中一些已经被赋予特定意义的单词&#xff0c;这就要求开发者在开发程序时&#xff0c;不能用这些保留字作为标识符给变量、函数、类、模板以及其他对象命名。 Python 包含的保留字可以执行如下命令进行…