0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

鴻蒙OS開發(fā)實戰(zhàn):【懸浮窗口】

jf_46214456 ? 來源:jf_46214456 ? 作者:jf_46214456 ? 2024-03-28 20:39 ? 次閱讀

背景

懸浮視圖或者窗體,在AndroidiOS兩大移動平臺均有使用,HarmonyOS 也實現(xiàn)了此功能,如下為大家分享一下效果

準(zhǔn)備

  1. 熟讀HarmonyOS 懸浮窗口指導(dǎo)
  2. 熟讀HarmonyOS 手勢指導(dǎo)
  3. 熟讀ALC簽名指導(dǎo),用于可以申請 “ohos.permission.SYSTEM_FLOAT_WINDOW” 權(quán)限。
  4. 熟悉的文檔在下方
    |

搜狗高速瀏覽器截圖20240326151547.png

實踐代碼

  1. 如果開啟了懸浮窗口,任何界面的物理返回鍵事件都會被懸浮窗口攔截掉,即 手勢返回廢了
  2. 參數(shù)類型易混淆, 拖動 PanGesture 中的onActionUpdate接口,數(shù)據(jù)單位為 vp ,window中的 moveWindowTo接口參數(shù),數(shù)據(jù)單位為px
  3. 采用moveWindowTo實現(xiàn)的窗口拖動效果十分不平滑
  4. 通過 requestPermissionsFromUser 申請 ohos.permission.SYSTEM_FLOAT_WINDOW 權(quán)限時,無法彈出系統(tǒng)權(quán)限提示框

片段代碼

配置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會判斷權(quán)限的授權(quán)狀態(tài)來決定是否喚起彈窗
        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) {
              // 用戶授權(quán),可以繼續(xù)訪問目標(biāo)操作
              console.log('用戶授權(quán),可以繼續(xù)訪問目標(biāo)操作')
            } else {
              // 用戶拒絕授權(quán),提示用戶必須授權(quán)才能訪問當(dāng)前頁面的功能,并引導(dǎo)用戶到系統(tǒng)設(shè)置中打開相應(yīng)的權(quán)限
              console.log('用戶拒絕授權(quán),提示用戶必須授權(quán)才能訪問當(dāng)前頁面的功能,并引導(dǎo)用戶到系統(tǒng)設(shè)置中打開相應(yīng)的權(quán)限')
              return;
            }
          }

          // 授權(quán)成功
          // 1.創(chuàng)建懸浮窗。
          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.懸浮窗窗口創(chuàng)建成功后,設(shè)置懸浮窗的位置、大小及相關(guān)屬性等。
            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.為懸浮窗加載對應(yīng)的目標(biāo)頁面。
            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;

  // 獲取應(yīng)用程序的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}`);
  }

  // 校驗應(yīng)用是否被授予權(quán)限
  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) {
    // 已經(jīng)授權(quán),可以繼續(xù)訪問目標(biāo)操作
    console.log('沒有授權(quán)')
    return true
  } else {
    // 申請日歷權(quán)限
    console.log('已授權(quán)')
    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(()= >{
           //關(guān)閉所依賴的UIAbility
           this.context.terminateSelf()
           //銷毀懸浮窗。當(dāng)不再需要懸浮窗時,可根據(jù)具體實現(xiàn)邏輯,使用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事件,監(jiān)聽拖拽動作
      PanGesture(this.panOption)
        .onActionStart((event: GestureEvent) = > {
          console.info('Pan start');
        })
          // 發(fā)生拖拽時,獲取到觸摸點的位置,并將位置信息傳遞給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
    });
  }

}

審核編輯 黃宇

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 鴻蒙
    +關(guān)注

    關(guān)注

    57

    文章

    2302

    瀏覽量

    42689
  • HarmonyOS
    +關(guān)注

    關(guān)注

    79

    文章

    1966

    瀏覽量

    29962
  • 鴻蒙OS
    +關(guān)注

    關(guān)注

    0

    文章

    188

    瀏覽量

    4359
收藏 人收藏

    評論

    相關(guān)推薦

    鴻蒙OS元服務(wù)開發(fā):【(Stage模型)設(shè)置懸浮窗】

    懸浮窗可以在已有的任務(wù)基礎(chǔ)上,創(chuàng)建一個始終在前臺顯示的窗口。即使創(chuàng)建懸浮窗的任務(wù)退至后臺,懸浮窗仍然可以在前臺顯示。通常懸浮窗位于所有應(yīng)用
    的頭像 發(fā)表于 04-03 15:32 ?1053次閱讀
    <b class='flag-5'>鴻蒙</b><b class='flag-5'>OS</b>元服務(wù)<b class='flag-5'>開發(fā)</b>:【(Stage模型)設(shè)置<b class='flag-5'>懸浮</b>窗】

    鴻蒙實戰(zhàn)基礎(chǔ)(ArkTS)-窗口管理

    (CommonConstants.HOME_PAGE_ACTION); }, CommonConstants.LOGIN_WAIT_TIME); } 本文主要是對基于窗口能力,實現(xiàn)驗證碼登錄的場景。有關(guān)鴻蒙的進階技能大家可以前往主頁查看更多
    發(fā)表于 01-12 17:51

    鴻蒙應(yīng)用/元服務(wù)開發(fā)-窗口(Stage模型)設(shè)置懸浮

    一、設(shè)置懸浮窗說明 懸浮窗可以在已有的任務(wù)基礎(chǔ)上,創(chuàng)建一個始終在前臺顯示的窗口。即使創(chuàng)建懸浮窗的任務(wù)退至后臺,懸浮窗仍然可以在前臺顯示。通常
    發(fā)表于 02-04 14:05

    鴻蒙實戰(zhàn)項目開發(fā):【短信服務(wù)】

    數(shù)據(jù)管理 電話服務(wù) 分布式應(yīng)用開發(fā) 通知與窗口管理 多媒體技術(shù) 安全技能 任務(wù)管理 WebGL 國際化開發(fā) 應(yīng)用測試 DFX面向未來設(shè)計 鴻蒙系統(tǒng)移植和裁剪定制 …… ? 《
    發(fā)表于 03-03 21:29

    鴻蒙OS崛起,鴻蒙應(yīng)用開發(fā)工程師成市場新寵

    應(yīng)用的形態(tài)也在發(fā)生著翻天覆地的變化。作為全球領(lǐng)先的移動操作系統(tǒng)和智能終端制造商,華為公司自主研發(fā)的鴻蒙OS應(yīng)運而生,致力于構(gòu)建一個統(tǒng)一的分布式操作系統(tǒng),為各行各業(yè)的應(yīng)用開發(fā)帶來全新的可能性。 一、
    發(fā)表于 04-29 17:32

    OpenHarmony實戰(zhàn)開發(fā)-如何實現(xiàn)窗口開發(fā)概述

    ],單位為vp。 系統(tǒng)窗口存在大小限制,寬度范圍:[0, 2560],高度范圍:[0, 2560],單位為vp。 最后 如果大家覺得這篇內(nèi)容對學(xué)習(xí)鴻蒙開發(fā)有幫助,我想邀請大家?guī)臀胰齻€小忙: 點贊,轉(zhuǎn)發(fā),有
    發(fā)表于 05-06 14:29

    鴻蒙Flutter實戰(zhàn):07混合開發(fā)

    # 鴻蒙Flutter實戰(zhàn):混合開發(fā) 鴻蒙Flutter混合開發(fā)主要有兩種形式。 ## 1.基于har 將flutter module
    發(fā)表于 10-23 16:00

    鴻蒙OS應(yīng)用程序開發(fā)

    這份學(xué)習(xí)文檔主要是帶領(lǐng)大家在鴻蒙OS上學(xué)習(xí)開發(fā)一個應(yīng)用程序,主要知識點如下:1、U-Boot引導(dǎo)文件燒寫方式;2、內(nèi)核鏡像燒寫方式;3、鏡像運行。
    發(fā)表于 09-11 14:39

    鴻蒙 OS 應(yīng)用開發(fā)初體驗

    的操作系統(tǒng)平臺和開發(fā)框架。HarmonyOS 的目標(biāo)是實現(xiàn)跨設(shè)備的無縫協(xié)同和高性能。 DevEco Studio 對標(biāo) Android Studio,開發(fā)鴻蒙 OS 應(yīng)用的 IDE。
    發(fā)表于 11-02 19:38

    鴻蒙OS 2.0手機開發(fā)者Beta版發(fā)布會在京舉辦

    三個月前,鴻蒙OS 2.0正式在華為開發(fā)者大會2020亮相。12月16日,鴻蒙OS 2.0手機開發(fā)
    的頭像 發(fā)表于 12-16 09:29 ?1.9w次閱讀

    華為發(fā)布鴻蒙OS Beta版

    昨天華為發(fā)布鴻蒙OS Beta版了?鴻蒙系統(tǒng)一直在按照既有步伐前進,現(xiàn)在華為發(fā)布鴻蒙OS Beta版,而且一些生態(tài)
    的頭像 發(fā)表于 12-17 08:41 ?2840次閱讀

    鴻蒙os怎么升級

    6月2日,華為正式發(fā)布了鴻蒙armonyOS 2系統(tǒng),那么鴻蒙os如何升級?現(xiàn)將鴻蒙os升級方式告知如下。
    的頭像 發(fā)表于 06-08 16:26 ?2694次閱讀

    華為開發(fā)者大會2021鴻蒙os在哪場

    華為開發(fā)者大會2021將在10月22日-24日舉辦,地點為東莞松山湖,鴻蒙os 3.0或?qū)⑴c我們見面,那么華為開發(fā)者大會2021鴻蒙
    的頭像 發(fā)表于 10-22 15:24 ?1861次閱讀

    RISC-V MCU開發(fā)實戰(zhàn) (三):移植鴻蒙OS項目

    移植鴻蒙OS項目
    的頭像 發(fā)表于 11-01 11:08 ?2877次閱讀
    RISC-V MCU<b class='flag-5'>開發(fā)</b><b class='flag-5'>實戰(zhàn)</b> (三):移植<b class='flag-5'>鴻蒙</b><b class='flag-5'>OS</b>項目

    鴻蒙實戰(zhàn)開發(fā):【實現(xiàn)應(yīng)用懸浮窗】

    如果你要做的是系統(tǒng)級別的懸浮窗,就需要判斷是否具備懸浮窗權(quán)限。然而這又不是一個標(biāo)準(zhǔn)的動態(tài)權(quán)限,你需要兼容各種奇葩機型的懸浮窗權(quán)限判斷。
    的頭像 發(fā)表于 04-03 22:18 ?379次閱讀
    <b class='flag-5'>鴻蒙</b><b class='flag-5'>實戰(zhàn)</b><b class='flag-5'>開發(fā)</b>:【實現(xiàn)應(yīng)用<b class='flag-5'>懸浮</b>窗】