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

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

3天内不再提示

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

jf_46214456 来源:jf_46214456 作者:jf_46214456 2024-03-28 20:39 次阅读

背景

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

准备

  1. 熟读HarmonyOS 悬浮窗口指导
  2. 熟读HarmonyOS 手势指导
  3. 熟读ALC签名指导,用于可以申请 “ohos.permission.SYSTEM_FLOAT_WINDOW” 权限。
  4. 熟悉的文档在下方
    |

搜狗高速浏览器截图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
    });
  }

}

审核编辑 黄宇

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

    关注

    55

    文章

    1637

    浏览量

    42120
  • HarmonyOS
    +关注

    关注

    79

    文章

    1853

    浏览量

    29267
  • 鸿蒙OS
    +关注

    关注

    0

    文章

    147

    浏览量

    4279
收藏 人收藏

    评论

    相关推荐

    鸿蒙OS元服务开发:【(Stage模型)设置悬浮窗】

    悬浮窗可以在已有的任务基础上,创建一个始终在前台显示的窗口。即使创建悬浮窗的任务退至后台,悬浮窗仍然可以在前台显示。通常悬浮窗位于所有应用
    的头像 发表于 04-03 15:32 731次阅读
    <b class='flag-5'>鸿蒙</b><b class='flag-5'>OS</b>元服务<b class='flag-5'>开发</b>:【(Stage模型)设置<b class='flag-5'>悬浮</b>窗】

    鸿蒙实战基础(ArkTS)-窗口管理

    (CommonConstants.HOME_PAGE_ACTION); }, CommonConstants.LOGIN_WAIT_TIME); } 本文主要是对基于窗口能力,实现验证码登录的场景。有关鸿蒙的进阶技能大家可以前往主页查看更多
    发表于 01-12 17:51

    鸿蒙应用/元服务开发-窗口(Stage模型)设置悬浮

    一、设置悬浮窗说明 悬浮窗可以在已有的任务基础上,创建一个始终在前台显示的窗口。即使创建悬浮窗的任务退至后台,悬浮窗仍然可以在前台显示。通常
    发表于 02-04 14:05

    鸿蒙实战项目开发:【短信服务】

    数据管理 电话服务 分布式应用开发 通知与窗口管理 多媒体技术 安全技能 任务管理 WebGL 国际化开发 应用测试 DFX面向未来设计 鸿蒙系统移植和裁剪定制 …… ​ 《
    发表于 03-03 21:29

    鸿蒙OS应用程序开发

    这份学习文档主要是带领大家在鸿蒙OS上学习开发一个应用程序,主要知识点如下:1、U-Boot引导文件烧写方式;2、内核镜像烧写方式;3、镜像运行。
    发表于 09-11 14:39

    鸿蒙OS适用的全场景到底什么意思?

    鸿蒙系统(HarmonyOS),第一款基于微内核的全场景分布式OS,是华为自主研发的操作系统。华为在开发者大会HDC.2019上正式发布了鸿蒙系统,该系统将率先部署在智慧屏、车载终端、
    发表于 09-25 09:25

    鸿蒙 OS 应用开发初体验

    的操作系统平台和开发框架。HarmonyOS 的目标是实现跨设备的无缝协同和高性能。 DevEco Studio 对标 Android Studio,开发鸿蒙 OS 应用的 IDE。
    发表于 11-02 19:38

    鸿蒙OS2.0手机开发者Beta版登场

    12 月 16 日,华为宣布正式推出鸿蒙 OS 的手机开发者 Beta 版,并正式面向个人/企业开发者公测鸿蒙 2.0,
    的头像 发表于 12-16 14:39 1918次阅读

    华为鸿蒙OS 2.0开发者公测版本大批量向已申请开发者推送

    4 月 27 日,华为鸿蒙 OS 2.0 开发者公测版本大批量向已申请的开发者推送,随后网上出现大量鸿蒙
    的头像 发表于 05-12 09:26 2849次阅读

    鸿蒙OS千呼万唤始出来

    从2019年在华为开发者大会上公布鸿蒙OS开始,围绕着鸿蒙OS的话题始终没有停止过,而华为也坚持不懈地为
    的头像 发表于 06-01 15:56 2337次阅读

    鸿蒙os怎么升级

    6月2日,华为正式发布了鸿蒙armonyOS 2系统,那么鸿蒙os如何升级?现将鸿蒙os升级方式告知如下。
    的头像 发表于 06-08 16:26 2488次阅读

    鸿蒙os和安卓的区别,鸿蒙与安卓比有什么优势

    华为鸿蒙系统已经发布,鸿蒙系统和安卓都是在Linux基础上开发出来的,所以很多小伙伴都想知道鸿蒙os和安卓系统之间的区别是什么?华为
    的头像 发表于 06-17 09:58 1.3w次阅读

    华为开发者大会2021鸿蒙os在哪场

    华为开发者大会2021将在10月22日-24日举办,地点为东莞松山湖,鸿蒙os 3.0或将与我们见面,那么华为开发者大会2021鸿蒙
    的头像 发表于 10-22 15:24 1635次阅读

    RISC-V MCU开发实战 (三):移植鸿蒙OS项目

    移植鸿蒙OS项目
    的头像 发表于 11-01 11:08 2612次阅读
    RISC-V MCU<b class='flag-5'>开发</b><b class='flag-5'>实战</b> (三):移植<b class='flag-5'>鸿蒙</b><b class='flag-5'>OS</b>项目

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

    如果你要做的是系统级别的悬浮窗,就需要判断是否具备悬浮窗权限。然而这又不是一个标准的动态权限,你需要兼容各种奇葩机型的悬浮窗权限判断。
    的头像 发表于 04-03 22:18 61次阅读
    <b class='flag-5'>鸿蒙</b><b class='flag-5'>实战</b><b class='flag-5'>开发</b>:【实现应用<b class='flag-5'>悬浮</b>窗】