鴻蒙HarmonyOS實戰-視窗管理

蜀道山QAQ發表於2024-06-11

🚀前言

視窗管理是指計算機作業系統中管理和控制視窗的一種機制。視窗管理器負責處理視窗的建立、關閉、移動、調整大小等操作,並且決定視窗的位置、層級、是否可見、是否接收使用者輸入等屬性。視窗管理器還負責繪製視窗的外觀和邊框,並提供使用者與視窗互動的方式,如滑鼠點選、鍵盤輸入等。視窗管理器可以透過圖形使用者介面(GUI)或命令列介面(CLI)來實現。不同作業系統和桌面環境提供不同的視窗管理器,如Windows系統下的Windows視窗管理器、macOS系統下的Aqua視窗管理器、Linux系統下的X視窗系統等。視窗管理的目的是提供使用者友好的介面,方便使用者進行多工操作和視窗間的切換和管理。

🚀一、視窗管理

🔎1.視窗開發概述

🦋1.1 視窗模組的定義

視窗模組是在同一塊物理螢幕上提供多個應用介面顯示和互動的機制。對應用開發者而言,視窗模組是一個重要的工具,它允許他們建立具有介面的應用程式,並提供了介面顯示和互動的能力。透過視窗模組,開發者可以方便地管理視窗的建立、展示和關閉,並可以對視窗進行調整和佈局,以滿足使用者的需求。

而對終端使用者而言,視窗模組提供了一種控制應用介面的方式。使用者可以透過視窗模組來切換和控制不同應用程式的視窗,使得他們能夠同時在螢幕上顯示多個應用介面,並能夠在這些介面之間進行互動和操作。視窗模組還提供了一些常用的視窗管理功能,如最小化、最大化、拖拽等,以方便使用者進行介面控制。

從整個作業系統的角度來看,視窗模組提供了對不同應用介面的組織和管理邏輯。它負責分配和管理螢幕上的空間,確保應用程式的視窗能夠正確顯示,並且能夠適應不同螢幕大小和解析度的裝置。視窗模組還負責處理視窗之間的互動和通訊,以保證應用程式的正常執行。

視窗模組在軟體開發中扮演著重要角色,它不僅為應用開發者提供了介面顯示和互動能力,也為終端使用者提供了控制應用介面的方式,同時為整個作業系統提供了對應用介面的組織和管理邏輯。

🦋1.2 視窗模組的用途

image

透過視窗模組的上述職責,HarmonyOS能夠提供強大的視窗管理功能,實現應用介面的顯示、互動和動效。開發者可以使用視窗物件載入UI介面,定義視窗的顯示關係和位置屬性,以及控制視窗的觸控和焦點狀態。這使得應用程式在同一塊物理螢幕上能夠提供多個應用介面的顯示和互動機制,為終端使用者提供了更豐富的控制應用介面的方式。整個作業系統也能夠透過視窗模組進行不同應用介面的組織和管理邏輯,實現多工的同時進行。

🦋1.3 基本概念

在 HarmonyOS 中,視窗模組將視窗介面分為系統視窗和應用視窗兩種基本型別。系統視窗是完成系統特定功能的視窗,例如音量條、桌布、通知欄、狀態列、導航欄等。這些視窗的設計旨在提供系統級別的功能和資訊,以便使用者可以方便地訪問和管理。

與系統視窗相對應的是應用視窗,這些視窗與應用的顯示相關。根據視窗內容的不同,應用視窗可以進一步分為應用主視窗和應用子視窗兩種型別。

  • 應用主視窗主要用於顯示應用的介面,它是應用的核心視窗。當使用者透過任務管理介面切換到應用時,應用主視窗將被顯示出來,讓使用者可以直接與應用進行互動。

  • 應用子視窗則是用於顯示應用的彈窗、懸浮窗等輔助視窗。與應用主視窗不同的是,應用子視窗不會在任務管理介面顯示。它們的生命週期與應用主視窗相同,即當應用主視窗銷燬時,相關的應用子視窗也會被銷燬。

透過這種視窗模組的劃分和設計,HarmonyOS 可以更好地管理和控制視窗的顯示和互動,為使用者提供更加流暢和便捷的操作體驗。無論是系統視窗還是應用視窗,它們都具有不同的功能和用途,相互配合,共同構建了豐富多樣的視窗介面。

🦋1.4 實現原理

HarmonyOS視窗管理的實現原理主要依賴於以下幾點:

  1. 基於物件導向的視窗管理:HarmonyOS使用物件導向的方式來管理視窗。每個視窗都是一個物件,具有自己的屬性和方法。視窗管理器負責建立、銷燬、排程和管理這些視窗物件。

  2. 視窗棧管理:HarmonyOS採用了視窗棧的方式來管理視窗。視窗棧是一個儲存視窗物件的有序集合,棧的頂部是當前正在顯示的視窗。當使用者開啟一個新的視窗時,它會被新增到棧的頂部,而當視窗被關閉時,它會從棧中移除。透過管理視窗棧,視窗管理器可以實現視窗的切換和管理。

  3. 視窗排程演算法:視窗管理器使用排程演算法來確定當前應該顯示哪個視窗。常見的排程演算法有先進先出(FIFO)、最近最少使用(LRU)等。排程演算法可以根據視窗的優先順序、使用者行為和系統資源等因素來決定視窗的排程順序。

  4. 視窗布局管理:HarmonyOS的視窗管理還包括視窗的佈局管理。視窗布局管理器負責確定視窗的大小、位置和佈局方式,以確保視窗在螢幕上正確顯示。佈局管理器可以根據視窗的屬性、螢幕大小和使用者設定等因素來動態調整視窗的佈局。

🦋1.5 約束與限制

應用主視窗與子視窗存在大小限制,寬度範圍:[320, 2560],高度範圍:[240, 2560],單位為vp。

🔎2.管理應用視窗

🦋2.1 基本概念

☀️2.1.1 視窗沉浸式能力

視窗沉浸式能力是指一個視窗應用程式能夠將使用者的注意力完全吸引到其介面上,並使使用者對其他應用程式和作業系統的介面感知降到最低的能力。視窗沉浸式能力通常透過以下幾個方面來實現:

  1. 使用全屏模式:視窗應用程式可以將自己的介面展示在整個螢幕上,從而將其他應用程式和作業系統的介面隱藏起來,使使用者更加專注於當前應用程式的內容。

  2. 最小化視覺干擾:視窗應用程式可以透過設計簡潔、優雅的介面,避免不必要的視覺元素和干擾,從而提供一個更加清晰和集中注意力的介面。

  3. 提供沉浸式內容:視窗應用程式可以提供各種吸引人的內容,如高質量的影像、音訊和影片,以吸引使用者的注意力並使其完全投入到應用程式的體驗中。

  4. 遮蔽外部干擾:視窗應用程式可以遮蔽或減少來自其他應用程式和作業系統的通知和提示等外部干擾,從而讓使用者在使用應用程式時不受打擾。

視窗沉浸式能力的目的是為了提供一種更加沉浸和集中注意力的使用者體驗,使使用者能夠更好地享受和使用視窗應用程式的功能和內容。

☀️2.1.2 懸浮窗

懸浮窗是一種浮動在螢幕上方的小視窗,可以在其他應用程式之上展示資訊或功能。它可以提供快速訪問或檢視特定內容,而無需離開當前應用程式。懸浮窗通常可以被拖動、調整大小或關閉。在移動裝置上,懸浮窗常用於顯示通知、快捷操作或其他實用工具。

🦋2.3 場景介紹

☀️2.3.1 設定應用主視窗

主視窗的"是否可觸"屬性是指能否透過觸控式螢幕與使用者進行互動。如果主視窗的"是否可觸"屬性為True,則使用者可以透過觸控式螢幕對主視窗進行操作,例如點選按鈕、滑動介面等。如果主視窗的"是否可觸"屬性為False,則使用者無法透過觸控式螢幕進行互動,只能使用其他輸入裝置(如滑鼠)進行操作。通常情況下,移動裝置(如手機、平板電腦)上的主視窗會設定為可觸控,而桌面電腦上的主視窗可能會設定為不可觸控。

import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from '@ohos.hilog';
import window from '@ohos.window';

export default class EntryAbility extends UIAbility {
  onCreate(want, launchParam) {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
  }

  onDestroy() {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
  }

  onWindowStageCreate(windowStage: window.WindowStage) {
    // Main window is created, set main page for this ability
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
    // 1.獲取應用主視窗。
    let windowClass = null;
    windowStage.getMainWindow((err, data) => {
      if (err.code) {
        console.error('Failed to obtain the main window. Cause: ' + JSON.stringify(err));
        return;
      }
      windowClass = data;
      console.info('Succeeded in obtaining the main window. Data: ' + JSON.stringify(data));
      // 2.設定主視窗屬性。以設定"是否可觸"屬性為例。
      let isTouchable = true;
      windowClass.setWindowTouchable(isTouchable, (err) => {
        if (err.code) {
          console.error('Failed to set the window to be touchable. Cause:' + JSON.stringify(err));
          return;
        }
        console.info('Succeeded in setting the window to be touchable.');
      })
    })
    // 3.為主視窗載入對應的目標頁面。
    windowStage.loadContent('pages/Index', (err, data) => {
      if (err.code) {
        hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
        return;
      }
      hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
    });
  }

  onWindowStageDestroy() {
    // Main window is destroyed, release UI related resources
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
  }

  onForeground() {
    // Ability has brought to foreground
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
  }

  onBackground() {
    // Ability has back to background
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
  }
}
☀️2.3.2 設定應用子視窗

應用子視窗是指在一個主視窗或父視窗之內建立一個較小的視窗,用於顯示其他相關的內容或功能。子視窗通常是獨立於主視窗的,可以拖動、最小化、最大化和關閉。應用子視窗可以提供更好的使用者體驗,將相關的功能和資訊集中在一起,並且可以在主視窗內方便地切換和操作。

應用子視窗常見的應用場景包括:

  1. 彈出式對話方塊:用於顯示警告、提示、確認等資訊,請求使用者的輸入或進行操作確認。

  2. 工具視窗:用於顯示一些輔助工具,例如調色盤、圖層皮膚等,在使用者需要時進行操作。

  3. 標籤頁:用於在主視窗內切換和顯示不同的內容,例如瀏覽器的多個標籤頁。

  4. 拆分視窗:將主視窗分割成多個子視窗,用於同時顯示多個相關的內容,例如文字編輯器的分屏編輯功能。

應用子視窗可以提高使用者的操作效率和便利性,使介面更加靈活和功能更加豐富。但在設計和使用時需要注意合理使用,避免過多的視窗導致使用者的混亂和困惑。

import UIAbility from '@ohos.app.ability.UIAbility';

let windowStage_ = null;
let sub_windowClass = null;
export default class EntryAbility extends UIAbility {
    showSubWindow() {
        // 1.建立應用子視窗。
        windowStage_.createSubWindow("mySubWindow", (err, data) => {
            if (err.code) {
                console.error('Failed to create the subwindow. Cause: ' + JSON.stringify(err));
                return;
            }
            sub_windowClass = data;
            console.info('Succeeded in creating the subwindow. Data: ' + JSON.stringify(data));
            // 2.子視窗建立成功後,設定子視窗的位置、大小及相關屬性等。
            sub_windowClass.moveWindowTo(300, 300, (err) => {
                if (err.code) {
                    console.error('Failed to move the window. Cause:' + JSON.stringify(err));
                    return;
                }
                console.info('Succeeded in moving the window.');
            });
            sub_windowClass.resize(500, 500, (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.為子視窗載入對應的目標頁面。
            sub_windowClass.setUIContent("pages/page3", (err) => {
                if (err.code) {
                    console.error('Failed to load the content. Cause:' + JSON.stringify(err));
                    return;
                }
                console.info('Succeeded in loading the content.');
                // 3.顯示子視窗。
                sub_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.');
                });
            });
        })
    }

    destroySubWindow() {
        // 4.銷燬子視窗。當不再需要子視窗時,可根據具體實現邏輯,使用destroy對其進行銷燬。
        sub_windowClass.destroyWindow((err) => {
            if (err.code) {
                console.error('Failed to destroy the window. Cause: ' + JSON.stringify(err));
                return;
            }
            console.info('Succeeded in destroying the window.');
        });
    }

    onWindowStageCreate(windowStage) {
        windowStage_ = windowStage;
        // 開發者可以在適當的時機,如主視窗上按鈕點選事件等,建立子視窗。並不一定需要在onWindowStageCreate呼叫,這裡僅作展示
        this.showSubWindow();
    }

    onWindowStageDestroy() {
        // 開發者可以在適當的時機,如子視窗上點選關閉按鈕等,銷燬子視窗。並不一定需要在onWindowStageDestroy呼叫,這裡僅作展示
        this.destroySubWindow();
    }
};
☀️2.3.3 體驗視窗沉浸式能力

概念上面有介紹,就不多說了。

import UIAbility from '@ohos.app.ability.UIAbility';

export default class EntryAbility extends UIAbility {
    onWindowStageCreate(windowStage) {
        // 1.獲取應用主視窗。
        let windowClass = null;
        windowStage.getMainWindow((err, data) => {
            if (err.code) {
                console.error('Failed to obtain the main window. Cause: ' + JSON.stringify(err));
                return;
            }
            windowClass = data;
            console.info('Succeeded in obtaining the main window. Data: ' + JSON.stringify(data));

            // 2.實現沉浸式效果:設定導航欄、狀態列不顯示。
            let names = [];
            windowClass.setWindowSystemBarEnable(names, (err) => {
                if (err.code) {
                    console.error('Failed to set the system bar to be visible. Cause:' + JSON.stringify(err));
                    return;
                }
                console.info('Succeeded in setting the system bar to be visible.');
            });
        })
        // 3.為沉浸式視窗載入對應的目標頁面。
        windowStage.loadContent("pages/page2", (err) => {
            if (err.code) {
                console.error('Failed to load the content. Cause:' + JSON.stringify(err));
                return;
            }
            console.info('Succeeded in loading the content.');
        });
    }
};

image

☀️2.3.4 設定懸浮窗

概念上面有介紹,就不多說了。

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

export default class EntryAbility extends UIAbility {
    onWindowStageCreate(windowStage) {
        // 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(300, 300, (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(500, 500, (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("pages/page4", (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.');
                });
            });
            // 4.銷燬懸浮窗。當不再需要懸浮窗時,可根據具體實現邏輯,使用destroy對其進行銷燬。
            windowClass.destroyWindow((err) => {
                if (err.code) {
                    console.error('Failed to destroy the window. Cause: ' + JSON.stringify(err));
                    return;
                }
                console.info('Succeeded in destroying the window.');
            });
        });
    }
};

🚀寫在最後

  • 如果你覺得這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:
  • 點贊,轉發,有你們的 『點贊和評論』,才是我創造的動力。
  • 關注小編,同時可以期待後續文章ing🚀,不定期分享原創知識。
  • 更多鴻蒙最新技術知識點,請關注作者部落格:https://t.doruo.cn/14DjR1rEY

image

相關文章