<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                [HarmonyOS應用開發者基礎認證](https://developer.huawei.com/consumer/cn/training/dev-cert-detail/101666948302721398) [HarmonyOS學習鏈接](https://www.html5iq.com/HarmonyOS/index) [可以下載使用谷歌瀏覽器插件:Smart TOC 生成目錄](https://www.chajianxw.com/product-tool/11058.html) ## **一、HarmonyOS第一課:運行Hello World** ## **闖關習題** * DevEco Studio是開發HarmonyOS應用的一站式集成開發環境:正確 * main_pages.json存放頁面page路徑配置信息。正確 * 在stage模型中,下列配置文件屬于AppScope文件夾的是?app.json5 * 如何在DevEco Studio中創建新項目? * 如果已打開項目,從DevEco Studio菜單選擇'file>new>Create Project' * 如果第一次打開DevEco Studio,在歡迎頁點擊“Create new Project” * module.json5配置文件中,包含了以下哪些信息?ability的相關配置信息、模塊名、模塊類型 ## **1.1 工程級目錄** 工程的目錄結構如下。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114004.05887895835754685037375708831627:50001231000000:2800:37FDA3724BF0FFCEAF66529859557B101D5734568D59B537A7F1C7F68B980649.png?needInitFileName=true?needInitFileName=true) 其中詳細如下: * AppScope中存放應用全局所需要的資源文件。 * entry是應用的主模塊,存放HarmonyOS應用的代碼、資源等。 * oh_modules是工程的依賴包,存放工程依賴的源文件。 * build-profile.json5是工程級配置信息,包括簽名、產品配置等。 * hvigorfile.ts是工程級編譯構建任務腳本,hvigor是基于任務管理機制實現的一款全新的自動化構建工具,主要提供任務注冊編排,工程模型管理、配置管理等核心能力。 * oh-package.json5是工程級依賴配置文件,用于記錄引入包的配置信息。 在AppScope,其中有resources文件夾和配置文件app.json5。AppScope>resources>base中包含element和media兩個文件夾 * 其中element文件夾主要存放公共的字符串、布局文件等資源。 * media存放全局公共的多媒體資源文件。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114004.66991068605762837436772598690613:50001231000000:2800:B0912A0768CC652258DE63C2AA95B71553464E99DC045E5CADD6255D0127FA2E.png?needInitFileName=true?needInitFileName=true) ## **1.2 模塊級目錄** ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114005.30439888302738843459091078997510:50001231000000:2800:1846C2BAF2EC319E7C9926ACF088719DEEB05329FB3F8D23B42341AE1424D873.png?needInitFileName=true?needInitFileName=true) entry>src目錄中主要包含總的main文件夾,單元測試目錄ohosTest,以及模塊級的配置文件。 * main文件夾中,ets文件夾用于存放ets代碼,resources文件存放模塊內的多媒體及布局文件等,module.json5文件為模塊的配置文件。 * ohosTest是單元測試目錄。 * build-profile.json5是模塊級配置信息,包括編譯構建配置項。 * hvigorfile.ts文件是模塊級構建腳本。 * oh-package.json5是模塊級依賴配置信息文件。 進入src>main>ets目錄中,其分為entryability、pages兩個文件夾。 * entryability存放ability文件,用于當前ability應用邏輯和生命周期管理。 * pages存放UI界面相關代碼文件,初始會生成一個Index頁面。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114005.55395222861315605930038112201035:50001231000000:2800:5EDE456E4383B183FA750A8B6EB3FDA550DBF837A8595259CFDC227CBF4AE68A.png?needInitFileName=true?needInitFileName=true) resources目錄下存放模塊公共的多媒體、字符串及布局文件等資源,分別存放在element、media文件夾中。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114005.62166781200500169814163464827141:50001231000000:2800:BA63DD9E3D1AFB0FE819E117D730E583828B005C5A37E855E0794C2338A8D28E.png?needInitFileName=true?needInitFileName=true) ## **1.3 app.json5** AppScope>app.json5是應用的全局的配置文件,用于存放應用公共的配置信息。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114005.97532016223915463239903576377060:50001231000000:2800:FBC56685FD6E983F0C565834D3ADFDFE103B0051CB10C75791A30BAE47D8A9E7.png?needInitFileName=true?needInitFileName=true) 其中配置信息如下: * bundleName是包名。 * vendor是應用程序供應商。 * versionCode是用于區分應用版本。 * versionName是版本號。 * icon對應于應用的顯示圖標。 * label是應用名。 ## **1.4 module.json5** module.json5配置文件中,包含了以下哪些信息? entry>src>main>module.json5是模塊的配置文件,包含當前模塊的配置信息。 :-: **表1**module.json5默認配置屬性及描述 | 屬性| 描述 | | :-- | :-- | | name| 該標簽標識當前module的名字,module打包成hap后,表示hap的名稱,標簽值采用字符串表示(最大長度31個字節),該名稱在整個應用要唯一。 | | type | 表示模塊的類型,類型有三種,分別是entry、feature和har。| | srcEntry | 當前模塊的入口文件路徑。| | description | 當前模塊的描述信息。 | | mainElement | 該標簽標識hap的入口ability名稱或者extension名稱。只有配置為mainElement的ability或者extension才允許在服務中心露出。 | | deviceTypes | 該標簽標識hap可以運行在哪類設備上,標簽值采用字符串數組的表示 | | deliveryWithInstall | 標識當前Module是否在用戶主動安裝的時候安裝,表示該Module對應的HAP是否跟隨應用一起安裝。- true:主動安裝時安裝。- false:主動安裝時不安裝。 | | installationFree | 標識當前Module是否支持免安裝特性。\- true:表示支持免安裝特性,且符合免安裝約束。\- false:表示不支持免安裝特性。 | |pages| 對應的是main\_pages.json文件,用于配置ability中用到的page信息。| | abilities | 是一個數組,存放當前模塊中所有的ability元能力的配置信息,其中可以有多個ability。| ## **1.5 main_pages.json** src/main/resources/base/profile/main\_pages.json文件保存的是頁面page的路徑配置信息,所有需要進行路由跳轉的page頁面都要在這里進行配置。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114005.19949345694859208383881946076343:50001231000000:2800:647DF640864F5E7A8E14EAF4473367588A023B3440743294F48DCC765BBB9E78.png?needInitFileName=true?needInitFileName=true "點擊放大") ## **二、HarmonyOS第一課:ArkTS開發語言介紹** ## **闖關習題** * 循環渲染ForEach可以從數據源中迭代獲取數據,并為每個數組項創建相應的組件。正確 * @Link變量不能在組件內部進行初始化。正確 * 用哪一種裝飾器修飾的struct表示該結構體具有組件化能力?@Component * 用哪一種裝飾器修飾的自定義組件可作為頁面入口組件?@Entry * 下面哪些函數是自定義組件的生命周期函數?aboutToAppear、aboutToDisappear、onPageShow、onPageHide、onBackPress * 下面哪些裝飾器可以用于管理自定義組件中變量的狀態?@State、@Link ## **2.1 TypeScript快速入門** ## **2.1.1 編程語言介紹** ArkTS是HarmonyOS優選的主力應用開發語言。它在TypeScript(簡稱TS)的基礎上,匹配ArkUI框架,擴展了聲明式UI、狀態管理等相應的能力,讓開發者以更簡潔、更自然的方式開發跨端應用。要了解什么是ArkTS,我們首先要了解下ArkTS、TypeScript和JavaScript之間的關系: * JavaScript是一種屬于網絡的高級腳本語言,已經被廣泛用于Web應用開發,常用來為網頁添加各式各樣的動態功能,為用戶提供更流暢美觀的瀏覽效果。 * TypeScript 是 JavaScript 的一個超集,它擴展了 JavaScript 的語法,通過在JavaScript的基礎上添加靜態類型定義構建而成,是一個開源的編程語言。 * ArkTS兼容TypeScript語言,拓展了聲明式UI、狀態管理、并發任務等能力。 由此可知,TypeScript是JavaScript的超集,ArkTS則是TypeScript的超集,他們的關系如下圖所示: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114006.09789960580347920997827335333048:50001231000000:2800:E2A927D873B16EC199132033001B4EE99724EE28864E0E4F83502508B6FD4A23.png?needInitFileName=true?needInitFileName=true "點擊放大") 在學習ArkTS聲明式的相關語法之前,我們首先學習下TypeScript的基礎語法。 ## **2.1.2 基礎類型** TypeScript支持一些基礎的數據類型,如布爾型、數組、字符串等,下文舉例幾個較為常用的數據類型,我們來了解下他們的基本使用。 `布爾值` TypeScript中可以使用boolean來表示這個變量是布爾值,可以賦值為true或者false。 ~~~ let isDone: boolean = false; ~~~ `數字` TypeScript里的所有數字都是浮點數,這些浮點數的類型是 number。除了支持十進制,還支持二進制、八進制、十六進制。 ~~~ let decLiteral: number = 2023;let binaryLiteral: number = 0b11111100111;let octalLiteral: number = 0o3747;let hexLiteral: number = 0x7e7; ~~~ `字符串` TypeScript里使用 string表示文本數據類型, 可以使用雙引號( ")或單引號(')表示字符串。 ~~~ let name: string = "Jacky";name = "Tom";name = 'Mick'; ~~~ `數組` TypeScrip有兩種方式可以定義數組。 第一種,可以在元素類型后面接上 \[\],表示由此類型元素組成的一個數組。 ~~~ let list: number[] = [1, 2, 3]; ~~~ 第二種方式是使用數組泛型,Array。 ~~~ let list: Array<number> = [1, 2, 3]; ~~~ `元組` 元組類型允許表示一個已知元素數量和類型的數組,各元素的類型不必相同。 比如,你可以定義一對值分別為 string和number類型的元組。 ~~~ let x: [string, number];x = ['hello', 10]; // OKx = [10, 'hello']; // Error ~~~ `枚舉` enum類型是對JavaScript標準數據類型的一個補充,使用枚舉類型可以為一組數值賦予友好的名字。 ~~~ enum Color {Red, Green, Blue};let c: Color = Color.Green; ~~~ `Unknown` 有時候,我們會想要為那些在編程階段還不清楚類型的變量指定一個類型。這種情況下,我們不希望類型檢查器對這些值進行檢查而是直接讓它們通過編譯階段的檢查。那么我們可以使用unknown類型來標記這些變量。 ~~~ let notSure: unknown = 4;notSure = 'maybe a string instead';notSure = false; ~~~ `Void` 當一個函數沒有返回值時,你通常會見到其返回值類型是 void。 ~~~ function test(): void { console.log('This is function is void');} ~~~ `Null 和 Undefined` TypeScript里,undefined和null兩者各自有自己的類型分別叫做undefined和null。 ~~~ let u: undefined = undefined;let n: null = null; ~~~ `聯合類型` 聯合類型(Union Types)表示取值可以為多種類型中的一種。 ~~~ let myFavoriteNumber: string | number;myFavoriteNumber = 'seven';myFavoriteNumber = 7; ~~~ ## **2.1.3 條件語句** 條件語句用于基于不同的條件來執行不同的動作。TypeScript 條件語句是通過一條或多條語句的執行結果(True 或 False)來決定執行的代碼塊。 `if 語句` TypeScript if 語句由一個布爾表達式后跟一個或多個語句組成。 ~~~ var num:number = 5if (num > 0) { console.log('數字是正數') } ~~~ `if...else 語句` 一個 if 語句后可跟一個可選的 else 語句,else 語句在布爾表達式為 false 時執行。 ~~~ var num:number = 12; if (num % 2==0) { console.log('偶數'); } else { console.log('奇數'); } ~~~ `if...else if....else 語句` if...else if....else 語句在執行多個判斷條件的時候很有用。 ~~~ var num:number = 2 if(num > 0) { console.log(num+' 是正數') } else if(num < 0) { console.log(num+' 是負數') } else { console.log(num+' 為0') } ~~~ `switch…case 語句` 一個 switch 語句允許測試一個變量等于多個值時的情況。每個值稱為一個 case,且被測試的變量會對每個 switch case 進行檢查。 ~~~ var grade:string = 'A'; switch(grade) { case 'A': { console.log('優'); break; } case 'B': { console.log('良'); break; } case 'C': { console.log('及格'); break; } case 'D': { console.log('不及格'); break; } default: { console.log('非法輸入'); break; } } ~~~ ## **2.1.4 函數** 函數是一組一起執行一個任務的語句,函數聲明要告訴編譯器函數的名稱、返回類型和參數。TypeScript可以創建有名字的函數和匿名函數,其創建方法如下: ~~~ // 有名函數function add(x, y) { return x + y;} // 匿名函數let myAdd = function (x, y) { return x + y;}; ~~~ `為函數定義類型` 為了確保輸入輸出的準確性,我們可以為上面那個函數添加類型: ~~~ // 有名函數:給變量設置為number類型function add(x: number, y: number): number { return x + y;} // 匿名函數:給變量設置為number類型let myAdd = function (x: number, y: number): number { return x + y;}; ~~~ `可選參數` 在TypeScript里我們可以在參數名旁使用 ?實現可選參數的功能。 比如,我們想讓lastName是可選的: ~~~ function buildName(firstName: string, lastName?: string) { if (lastName) return firstName + ' ' + lastName; else return firstName;} let result1 = buildName('Bob');let result2 = buildName('Bob', 'Adams'); ~~~ `剩余參數` 剩余參數會被當做個數不限的可選參數。 可以一個都沒有,同樣也可以有任意個。 可以使用省略號( ...)進行定義: ~~~ function getEmployeeName(firstName: string, ...restOfName: string[]) { return firstName + ' ' + restOfName.join(' ');} let employeeName = getEmployeeName('Joseph', 'Samuel', 'Lucas', 'MacKinzie'); ~~~ ``箭頭函數`` ES6版本的TypeScript提供了一個箭頭函數,它是定義匿名函數的簡寫語法,用于函數表達式,它省略了function關鍵字。箭頭函數的定義如下,其函數是一個語句塊: ~~~ ( [param1, parma2,…param n] )=> { // 代碼塊} ~~~ 其中,括號內是函數的入參,可以有0到多個參數,箭頭后是函數的代碼塊。我們可以將這個箭頭函數賦值給一個變量,如下所示: ~~~ let arrowFun = ( [param1, parma2,…param n] )=> { // 代碼塊} ~~~ 如何要主動調用這個箭頭函數,可以按如下方法去調用: ~~~ arrowFun(param1, parma2,…param n) ~~~ 接下來我們看看如何將我們熟悉的函數定義方式轉換為箭頭函數。我們可以定義一個判斷正負數的函數,如下: ~~~ function testNumber(num: number) { if (num > 0) { console.log(num + ' 是正數'); } else if (num < 0) { console.log(num + ' 是負數'); } else { console.log(num + ' 為0'); }} ~~~ 其調用方法如下: ~~~ testNumber(1) //輸出日志:1 是正數 ~~~ 如果將這個函數定義為箭頭函數,定義如下所示: ~~~ let testArrowFun = (num: number) => { if (num > 0) { console.log(num + ' 是正數'); } else if (num < 0) { console.log(num + ' 是負數'); } else { console.log(num + ' 為0'); }} ~~~ 其調用方法如下: ~~~ testArrowFun(-1) //輸出日志:-1 是負數 ~~~ 后面,我們在學習HarmonyOS應用開發時會經常用到箭頭函數。例如,給一個按鈕添加點擊事件,其中onClick事件中的函數就是箭頭函數。 ~~~ Button("Click Now") .onClick(() => { console.info("Button is click") }) ~~~ ## **2.1.5 類** TypeScript支持基于類的面向對象的編程方式,定義類的關鍵字為 class,后面緊跟類名。類描述了所創建的對象共同的屬性和方法。 `類的定義` 例如,我們可以聲明一個Person類,這個類有3個成員:一個是屬性(包含name和age),一個是構造函數,一個是getPersonInfo方法,其定義如下所示。 ~~~ class Person { private name: string private age: number constructor(name: string, age: number) { this.name = name; this.age = age; } public getPersonInfo(): string { return `My name is ${this.name} and age is ${this.age}`; }} ~~~ 通過上面的Person類,我們可以定義一個人物Jacky并獲取他的基本信息,其定義如下: ~~~ let person1 = new Person('Jacky', 18);person1.getPersonInfo(); ~~~ `繼承` 繼承就是子類繼承父類的特征和行為,使得子類具有父類相同的行為。TypeScript中允許使用繼承來擴展現有的類,對應的關鍵字為extends。 ~~~ class Employee extends Person { private department: string constructor(name: string, age: number, department: string) { super(name, age); this.department = department; } public getEmployeeInfo(): string { return this.getPersonInfo() + ` and work in ${this.department}`; }} ~~~ 通過上面的Employee類,我們可以定義一個人物Tom,這里可以獲取他的基本信息,也可以獲取他的雇主信息,其定義如下: ~~~ let person2 = new Employee('Tom', 28, 'HuaWei');person2.getPersonInfo();person2.getEmployeeInfo(); ~~~ 在TypeScript中,有public、private、protected修飾符,其功能和具體使用場景大家可以參考TypeScript的相關學習資料,進行拓展學習。 ## **2.1.6 模塊** 隨著應用越來越大,通常要將代碼拆分成多個文件,即所謂的模塊(module)。模塊可以相互加載,并可以使用特殊的指令 export 和 import 來交換功能,從另一個模塊調用一個模塊的函數。 兩個模塊之間的關系是通過在文件級別上使用 import 和 export 建立的。模塊里面的變量、函數和類等在模塊外部是不可見的,除非明確地使用 export 導出它們。類似地,我們必須通過 import 導入其他模塊導出的變量、函數、類等。 `導出` 任何聲明(比如變量,函數,類,類型別名或接口)都能夠通過添加export關鍵字來導出,例如我們要把NewsData這個類導出,代碼示意如下: ~~~ export class NewsData { title: string; content: string; imagesUrl: Array<NewsFile>; source: string; constructor(title: string, content: string, imagesUrl: Array<NewsFile>, source: string) { this.title = title; this.content = content; this.imagesUrl = imagesUrl; this.source = source; }} ~~~ `導入` 模塊的導入操作與導出一樣簡單。 可以使用以下 import形式之一來導入其它模塊中的導出內容。 ~~~ import { NewsData } from '../common/bean/NewsData'; ~~~ ## **2.1.7 迭代器** 當一個對象實現了Symbol.iterator屬性時,我們認為它是可迭代的。一些內置的類型如Array,Map,Set,String,Int32Array,Uint32Array等都具有可迭代性。 `for..of 語句` for..of會遍歷可迭代的對象,調用對象上的Symbol.iterator方法。 下面是在數組上使用for..of的簡單例子: ~~~ let someArray = [1, "string", false]; for (let entry of someArray) { console.log(entry); // 1, "string", false} ~~~ `for..of vs. for..in 語句` for..of和for..in均可迭代一個列表,但是用于迭代的值卻不同:for..in迭代的是對象的鍵,而for..of則迭代的是對象的值。 ~~~ let list = [4, 5, 6]; for (let i in list) { console.log(i); // "0", "1", "2",} for (let i of list) { console.log(i); // "4", "5", "6"} ~~~ ## **2.2 淺析ArkTS的起源和演進** ## **2.2.1 引言** Mozilla創造了JS,Microsoft創建了TS,Huawei進一步推出了ArkTS。 從最初的基礎的邏輯交互能力,到具備類型系統的高效工程開發能力,再到融合聲明式UI、多維狀態管理等豐富的應用開發能力,共同組成了相關的演進脈絡。 ArkTS是HarmonyOS優選的主力應用開發語言。它在TypeScript(簡稱TS)的基礎上,擴展了聲明式UI、狀態管理等相應的能力,讓開發者可以以更簡潔、更自然的方式開發高性能應用。TS是JavaScript(簡稱JS)的超集,ArkTS則是TS的超集。ArkTS會結合應用開發和運行的需求持續演進,包括但不限于引入分布式開發范式、并行和并發能力增強、類型系統增強等方面的語言特性。本期我們結合JS和TS以及相關的開發框架的發展,為大家介紹ArkTS的起源和演進思路。 ## **2.2.2 JS** JS語言由Mozilla創造,最初主要是為了解決頁面中的邏輯交互問題,它和HTML(負責頁面內容)、CSS(負責頁面布局和樣式)共同組成了Web頁面/應用開發的基礎。隨著Web和瀏覽器的普及,以及Node.js進一步將JS擴展到了瀏覽器以外的環境,JS語言得到了飛速的發展。在2015年相關的標準組織ECMA發布了一個主要的版本ECMAScript 6(簡稱ES6),這個版本具備了較為完整的語言能力,包括類(Class)、模塊(Module)、相關的語言基礎API增強(Map/Set等)、箭頭函數(Arrow Function)等。從2015年開始,ECMA每年都會發布一個標準版本,比如ES2016/ES2017/ES2018等,JS語言越來越成熟。 為了提升應用的開發效率,相應的JS前端框架也不斷地涌現出來。其中比較典型的有Facebook發起的React.js,以及個人開發者尤雨溪發起的Vue.js。React和Vue的主要出發點都是將響應式編程的能力引入到應用開發中,實現數據和界面內容的自動關聯處理。具體的實現方式上,React對JS做了一些擴展,引入了JSX(JavaScript XML)語法,可以將HTML的內容統一表示成JS來處理;Vue則是通過擴展的模板語法(Template)的方式來處理。 下面通過兩個示例,為大家簡要介紹React和Vue。(**示例來源于w3schools網站:**[https://www.w3schools.com/whatis/](https://www.w3schools.com/whatis/)) `1. React示例` **圖1**?React示例 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102104340.94108790062912697966750549293034:50001231000000:2800:DBC3B5AE63B6879EE21AF9E59D564ADAFE30E9D7CDFF6E6EA226D4128B2982C5.png) 以上代碼描述了React如何在指定的頁面元素(id為id01的div元素)中改變相應的字符串內容(從"Hello World!"到"Hello John Doe!")。其中第5行的ReactDOM.render()是React JS庫提供的一個方法,它可以將相應的內容刷新到指定的HTML元素中。第6行是符合JSX語義的一段代碼,它包含了一個類似HTML結構的字符串(...),以及一個表達數據綁定語義的字段({name}),會關聯到第4行定義的name變量。通過這種方式,JSX把HTML的語義以及數據綁定機制和JS語言結合起來,可以方便地在JS語言中使用。 `2. Vue示例` **圖2**?Vue示例 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102104352.79912171437667408945886710827184:50001231000000:2800:112F46F30295D467BA9DFF79E4FF0970B0F988D51013AF8C2460142BE3BA0DC9.png) 以上Vue示例代碼也描述了類似的功能。其中第1~3行是類似HTML的語法,描述一個id為app的div頁面元素,其中的{{message}}是數據綁定的語義,在Vue中表示為Template。第6~9行是JS代碼,描述了一個Vue對象,對應了上述的app頁面元素以及所需的數據變量message的內容信息。第11~13行則是JS函數,它改變message變量的值為"John Doe"。執行這個函數時Vue會自動實現相應的UI界面刷新。 如上所示,React和Vue所表達的能力是類似的,不過側重點稍微有所不同。React主要是基于JSX的語法,將類HTML的語法融合到JS語言中;Vue則是基于Template機制,在HTML的基礎上擴展相應的語義。當然,上面這兩個例子只是簡要地描述了React和Vue的基礎信息,更詳細的語法以及CSS相關的使用等都沒涉及。 從運行時的維度來看,基于React以及Vue的應用都可運行在Web引擎上。為了進一步提升相應的性能體驗,2015年Facebook在React基礎上推出了React Native, 在渲染架構上沒有采用傳統的Web引擎渲染路徑,而是橋接到相應OS平臺的原生UI組件上。2019年Facebook引入全新實現的JS引擎Hermes,并推出一系列架構改進來進一步提升React Native的性能體驗。2016年阿里巴巴開源的Weex則是基于Vue做了一些類似的改進,也是采用了橋接到原生UI組件的渲染路徑。 ## **2.2.3 TS** 隨著JS生態的發展,如何更有效地支撐大型的應用工程開發變成了一個重要的課題。大型的應用工程一般會涉及較復雜的代碼以及較多的團隊協作,對語言的規范性,模塊的復用性、擴展性以及相關的開發工具都提出了更高的要求。為此,Microsoft在JS的基礎上,創建了TS語言,并在2014年正式發布了1.0版本。TS主要從以下幾個方面做了進一步的增強: * 引入了類型系統,并提供了類型檢查以及類型自動推導能力,可以進行編譯時錯誤檢查,有效的提升了代碼的規范性以及錯誤檢測范圍和效率。 * 在類型系統基礎上,引入了聲明文件(Declaration Files)來管理接口或其他自定義類型。聲明文件一般是以d.ts的形式來定義模塊中的接口,這些接口和具體的實現做了相應的分離,有助于各模塊之間的分工協作。另外,TS通過接口,泛型(Generics)等相關特性的支持,進一步增強了設計復雜的框架所需的擴展以及復用能力。 在工具層面,TS也有相應的編輯器、編譯器、IDE(Integrated Development Environment)插件等相關的工具,來進一步提升開發效率。 TS在兼容JS生態方面也做了較好的平衡,TS應用通過相應編譯器可以編譯出純JS應用,可以在標準的JS引擎上運行。同時,TS定位為JS的超集,即JS應用也是合法的TS應用。此外,在標準層面上,TS兼容ECMA的相應標準,并維護那些還未成為ECMA標準的新特性。 ## **2.2.4 ArkTS** 如上所述,基于JS的前端框架以及TS的引入,進一步提升了應用開發效率,但依然存在一些不足。 `從開發者維度來看:` 寫一個應用需要了解三種語言(JS/TS、HTML和CSS)。這對Web開發者相對友好,但對非Web開發者來說,負擔較重。 `從運行時維度來看:` * 在語言運行時方面,盡管TS有了類型的加持,但也只是用于編譯時檢查,然后通過TS Compiler轉成JS,運行時引擎還是無法利用到基于類型系統的優化。 * 在渲染方面,主流Web引擎由于本身復雜度以及歷史原因,性能、資源占用方面與常見OS原生框架都有一定的差距,尤其在移動平臺上。React Native通過渲染架構的改進一定程度上提升了性能體驗,但在平臺渲染效果和能力的一致性,以及JS語言性能等方面還是存在一定的不足。 Google在2018年底推出的Flutter則走了另外一條路,結合新的語言Dart,引入新的聲明式開發范式,基于Skia的自繪制引擎構建可跨平臺的獨立的渲染能力。這是一種較為創新的方案,不過也有幾點不足: * Dart語言生態。盡管Dart語言2011年就已推出,傳聞其目標是取代JS,但整個生態還是非常弱小,Dart語言發布7年后隨著Flutter的推出才有所改善。整體而言,Dart和主流語言生態相比還是有非常大的差距。 * 開發范式。Flutter暴露了很多細粒度的Widget接口,整體開發的簡潔度,開發門檻,尤其是和Apple推出的SwiftUI相比,存在一定的差距。 有意思的是,Google在2021年又推出了新的開發框架Jetpack Compose,結合了Kotlin的語言生態,設計了新的聲明式UI開發范式。 2019年,我們在思考如何構建新的應用開發框架的時候,從以下幾個維度進行了重點考慮: * 語言生態 * 開發效率 * 性能體驗 * 跨設備/跨平臺能力 由于JS/TS有比較完善的開發者生態,語言也比較中立友好,有相應的標準組織可以逐步演進,JS/TS語言成了比較自然的選擇。以JS/TS為基礎,在開發框架的維度,我們做了如下的架構演進設計: * 通過基于JS擴展的類Web開發范式,來支持主流的前端開發方式。同步的,在運行時方面,通過渲染引擎的增強(平臺無關的自繪制機制、聲明式UI后端設計、動態布局/多態UI組件等),語言編譯器和運行時的優化增強(代碼預編譯、高效FFI-Foreign Function Interface、引擎極小化等),進一步提升相關的性能體驗,并可部署到不同設備上(包括百KB級內存的輕量設備)。另外,通過平臺適配層的設計,構建了跨OS平臺的基礎設施。 * 通過基于TS擴展的聲明式UI開發范式,提供了更簡潔更自然的開發體驗。在運行時方面,在上述的基礎上,結合語言運行時的類型優化,以及渲染運行時的扁平化流水線技術等,進一步提升性能體驗。 **圖3**?ArkUI開發框架 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102104405.81053425986211736818236644416294:50001231000000:2800:C4BDB880E1D7503824BBA83A0D6B298C45167CE3A25BE6CDEEB552C343EF8AE5.png) 圖3描述了ArkUI開發框架的整體架構,其中,基于TS擴展的聲明式UI范式中所用的語言就是ArkTS。下面結合一個具體示例,從應用開發視角簡單介紹下基于ArkTS的全新聲明式開發范式。 如圖4所示的代碼示例,UI界面會顯示兩段文本和一個按鈕,當開發者點擊按鈕時,文本內容會從'Hello World'變為‘Hello ArkUI’。 **圖4**?ArkTS聲明式開發范式 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102104418.97167608875006783664909326988909:50001231000000:2800:B0A2A3474E494DE620CBF58C0DBB2F835EBDAC07C87E2376865D3CA5ED979896.png) `這個示例中所包含的ArkTS聲明式開發范式的基本組成說明如下:` * 裝飾器 用來裝飾類、結構體、方法以及變量,賦予其特殊的含義,如上述示例中 @Entry 、 @Component 、 @State 都是裝飾器。具體而言, @Component 表示這是個自定義組件;?@Entry 則表示這是個入口組件;?@State 表示組件中的狀態變量,此狀態變化會引起 UI 變更。 * 自定義組件 可復用的 UI 單元,可組合其它組件,如上述被 @Component 裝飾的 struct Hello。 * UI 描述 聲明式的方式來描述 UI 的結構,如上述 build() 方法內部的代碼塊。 * 內置組件 框架中默認內置的基礎和布局組件,可直接被開發者調用,比如示例中的 Column、Text、Divider、Button。 * 事件方法 用于添加組件對事件的響應邏輯,統一通過事件方法進行設置,如跟隨在Button后面的onClick()。 * 屬性方法 用于組件屬性的配置,統一通過屬性方法進行設置,如fontSize()、width()、height()、color() 等,可通過鏈式調用的方式設置多項屬性。 從UI框架的需求角度,ArkTS在TS的類型系統的基礎上,做了進一步的擴展:**定義了各種裝飾器、自定義組件和UI描述機制**,再配合UI開發框架中的UI內置組件、事件方法、屬性方法等共同構成了應用開發的主體。在應用開發中,除了UI的結構化描述之外,還有一個重要的方面:**狀態管理**。如上述示例中,用 @State 裝飾過的變量 myText ,包含了一個基礎的狀態管理機制,即 myText 的值的變化會自動觸發相應的 UI 變更 (Text組件)。**ArkUI 中進一步提供了多維度的狀態管理機制**。和 UI 相關聯的數據,不僅可以在組件內使用,還可以在不同組件層級間傳遞,比如父子組件之間,爺孫組件之間,也可以是全局范圍內的傳遞,還可以是跨設備傳遞。另外,從數據的傳遞形式來看,可分為**只讀的單向傳遞和可變更的雙向傳遞**。開發者可以靈活的利用這些能力來實現數據和 UI 的聯動。 總體而言,ArkUI開發框架通過擴展成熟語言、結合語法糖或者語言原生的元編程能力、以及UI組件、狀態管理等方面設計了統一的UI開發范式,結合原生語言能力共同完成應用開發。這些構成了當前ArkTS基于TS的主要擴展。 `ArkUI完整的開發范式可參考這里:` [https://developer.harmonyos.com/cn/docs/documentation/doc-guides/arkui-overview-0000001281480754](https://developer.harmonyos.com/cn/docs/documentation/doc-guides/arkui-overview-0000001281480754) ## **2.3 ArkTS開發實踐** ## **2.3.1 聲明式UI基本概念** 應用界面是由一個個頁面組成,ArkTS是由ArkUI框架提供,用于以聲明式開發范式開發界面的語言。 聲明式UI構建頁面的過程,其實是組合組件的過程,聲明式UI的思想,主要體現在兩個方面: * 描述UI的呈現結果,而不關心過程 * 狀態驅動視圖更新 類似蘋果的SwiftUI中通過組合視圖View,安卓Jetpack Compose中通過組合@Composable函數,ArkUI作為HarmonyOS應用開發的UI開發框架,其使用ArkTS語言構建自定義組件,通過組合自定義組件完成頁面的構建。 ## **2.3.2 自定義組件的組成** ArkTS通過struct聲明組件名,并通過@Component和@Entry裝飾器,來構成一個自定義組件。 使用@Entry和@Component裝飾的自定義組件作為頁面的入口,會在頁面加載時首先進行渲染。 ~~~ @Entry@Componentstruct ToDoList {...} ~~~ 例如ToDoList組件對應如下整個代辦頁面。 **圖1**ToDoList待辦列表 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114005.08216763926514493509595362163178:50001231000000:2800:98A6F4B1B294C6C0CF94A70581AAA34BB495CBA86381BBA75488D1C71BA862FE.png?needInitFileName=true?needInitFileName=true) 使用@Component裝飾的自定義組件,如ToDoItem這個自定義組件則對應如下內容,作為頁面的組成部分。 ~~~ @Componentstruct ToDoItem {...} ~~~ **圖2**ToDoItem ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114005.75932547756921928359662551668593:50001231000000:2800:22C9276700FF1ABEBB38DB4666197E4358D8F0C1D92BF8DF29A5C3D089D96762.png?needInitFileName=true?needInitFileName=true "點擊放大") 在自定義組件內需要使用build方法來進行UI描述。 ~~~ @Entry@Component struct ToDoList ... build() { ... } } ~~~ build方法內可以容納內置組件和其他自定義組件,如Column和Text都是內置組件,由ArkUI框架提供,ToDoItem為自定義組件,需要開發者使用ArkTS自行聲明。 ~~~ @Entry@Componentstruct ToDoList { ... build() { Column(...) { Text(...) ... ForEach(...{ TodoItem(...) },...) } ... }} ~~~ ## **2.3.3 配置屬性與布局** 自定義組件的組成使用基礎組件和容器組件等內置組件進行組合。但有時內置組件的樣式并不能滿足我們的需求,ArkTS提供了屬性方法用于描述界面的樣式。屬性方法支持以下使用方式: * 常量傳遞 例如使用fontSize(50)來配置字體大小。 ~~~ Text('Hello World') .fontSize(50) ~~~ * 變量傳遞 在組件內定義了相應的變量后,例如組件內部成員變量size,就可以使用this.size方式使用該變量。 ~~~ Text('Hello World') .fontSize(this.size) ~~~ * 鏈式調用 在配置多個屬性時,ArkTS提供了鏈式調用的方式,通過'.'方式連續配置。 ~~~ Text('Hello World') .fontSize(this.size) .width(100) .height(100) ~~~ * 表達式傳遞 屬性中還可以傳入普通表達式以及三目運算表達式。 ~~~ Text('Hello World') .fontSize(this.size) .width(this.count + 100) .height(this.count % 2 === 0 ? 100 : 200) ~~~ * 內置枚舉類型 除此之外,ArkTS中還提供了內置枚舉類型,如Color,FontWeight等,例如設置fontColor改變字體顏色為紅色,并私有fontWeight為加粗。 ~~~ Text('Hello World') .fontSize(this.size) .width(this.count + 100) .height(this.count % 2 === 0 ? 100 : 200) .fontColor(Color.Red) .fontWeight(FontWeight.Bold) ~~~ 對于有多種組件需要進行組合時,容器組件則是描述了這些組件應該如何排列的結果。 ArkUI中的布局容器有很多種,在不同的適用場合選擇不同的布局容器實現,ArkTS使用容器組件采用花括號語法,內部放置UI描述。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114005.58356571013514207478782675626695:50001231000000:2800:1A728FE591D657B25137F57CF295D1589E6AB7DF739009B3FB31B2408E333324.png?needInitFileName=true?needInitFileName=true) 這里我們將介紹最基礎的兩個布局——列布局和行布局。 對于如下每一項的布局,兩個元素為橫向排列,選擇Row布局 **圖3**Row布局 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114005.80107629230495433313301244207697:50001231000000:2800:901203F2739F52DCA8641B6355F1A84C9A1F5A85BC9EAEA33057B79974F87017.png?needInitFileName=true?needInitFileName=true "點擊放大") ~~~ Row() { Image($r('app.media.ic_default')) ... Text(this.content) ...}... ~~~ 類似下圖所示的布局,整體都是從上往下縱向排列,適用的布局方式是Column列布局。 **圖4**Column布局 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114005.83007411797840114697967429764752:50001231000000:2800:13F2BAA1FD7C5B4B34495E02FE1B63BAD400AD1D9B3D5ED510B961ED6B21C397.png?needInitFileName=true?needInitFileName=true "點擊放大") ~~~ Column() { Text($r('app.string.page_title')) ... ForEach(this.totalTasks,(item) => { TodoItem({content:item}) },...) } ~~~ ## **2.3.4 改變組件狀態** 實際開發中由于交互,頁面的內容可能需要產生變化,以每一個ToDoItem為例,其在完成時的狀態與未完成時的展示效果是不一樣的。 **圖5**不同狀態的視圖 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114005.78299383157035977569357248263864:50001231000000:2800:CCD45AB03A26600002E3FEE6B5EECB7E24E50040365FA0F4450EE8D9DA208728.png?needInitFileName=true?needInitFileName=true "點擊放大") 聲明式UI的特點就是UI是隨數據更改而自動刷新的,我們這里定義了一個類型為boolean的變量isComplete,其被@State裝飾后,框架內建立了數據和視圖之間的綁定,其值的改變影響UI的顯示。 ~~~ @State isComplete : boolean = false; ~~~ **圖6**@State裝飾器的作用 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114005.17768528278725865389151857006615:50001231000000:2800:D2EC4581F8620BF0AA2D417853D59E099FB0BC651785E5A227C033E78422C2CA.png?needInitFileName=true?needInitFileName=true "點擊放大") 用圓圈和對勾這樣兩個圖片,分別來表示該項是否完成,這部分涉及到內容的切換,需要使用條件渲染if / else語法來進行組件的顯示與消失,當判斷條件為真時,組件為已完成的狀態,反之則為未完成。 ~~~ if (this.isComplete) { Image($r('app.media.ic_ok')) .objectFit(ImageFit.Contain) .width($r('app.float.checkbox_width')) .height($r('app.float.checkbox_width')) .margin($r('app.float.checkbox_margin'))} else { Image($r('app.media.ic_default')) .objectFit(ImageFit.Contain) .width($r('app.float.checkbox_width')) .height($r('app.float.checkbox_width')) .margin($r('app.float.checkbox_margin'))} ~~~ 由于兩個Image的實現具有大量重復代碼,ArkTS提供了@Builder裝飾器,來修飾一個函數,快速生成布局內容,從而可以避免重復的UI描述內容。這里使用@Bulider聲明了一個labelIcon的函數,參數為url,對應要傳給Image的圖片路徑。 ~~~ @Builder labelIcon(url) { Image(url) .objectFit(ImageFit.Contain) .width($r('app.float.checkbox_width')) .height($r('app.float.checkbox_width')) .margin($r('app.float.checkbox_margin'))} ~~~ 使用時只需要使用this關鍵字訪問@Builder裝飾的函數名,即可快速創建布局。 ~~~ if (this.isComplete) { this.labelIcon($r('app.media.ic_ok'))} else { this.labelIcon($r('app.media.ic_default'))} ~~~ 為了讓待辦項帶給用戶的體驗更符合已完成的效果,給內容的字體也增加了相應的樣式變化,這里使用了三目運算符來根據狀態變化修改其透明度和文字樣式,如opacity是控制透明度,decoration是文字是否有劃線。通過isComplete的值來控制其變化。 ~~~ Text(this.content) ... .opacity(this.isComplete ? CommonConstants.OPACITY_COMPLETED : CommonConstants.OPACITY_DEFAULT) .decoration({ type: this.isComplete ? TextDecorationType.LineThrough : TextDecorationType.None }) ~~~ 最后,為了實現與用戶交互的效果,在組件上添加了onClick點擊事件,當用戶點擊該待辦項時,數據isComplete的更改就能夠觸發UI的更新。 ~~~ @Componentstruct ToDoItem { @State isComplete : boolean = false; @Builder labelIcon(icon) {...} ... build() { Row() { if (this.isComplete) { this.labelIcon($r('app.media.ic_ok')) } else { this.labelIcon($r('app.media.ic_default')) } ... } ... .onClick(() => { this.isComplete= !this.isComplete; }) }} ~~~ ## **2.3.5 循環渲染列表數據** 剛剛只是完成了一個ToDoItem組件的開發,當我們有多條待辦數據需要顯示在頁面時,就需要使用到ForEach循環渲染語法。 例如這里我們有五條待辦數據需要展示在頁面上。 ~~~ total_Tasks:Array<string> = [ '早起晨練', '準備早餐', '閱讀名著', '學習ArkTS', '看劇放松'] ~~~ ForEach基本使用中,只需要了解要渲染的數據以及要生成的UI內容兩個部分,例如這里要渲染的數組為以上的五條待辦事項,要渲染的內容是ToDoItem這個自定義組件,也可以是其他內置組件。 **圖7**ForEach基本使用 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114005.53506681941979099871827168247996:50001231000000:2800:1A3961DAFF82E047C4F91BDBF1FFA1A7CDE73CFE01E4CA61045D71A489F899B1.png?needInitFileName=true?needInitFileName=true "點擊放大") ToDoItem這個自定義組件中,每一個ToDoItem要顯示的文本參數content需要外部傳入,參數傳遞使用花括號的形式,用content接受數組內的內容項item。 最終完成的代碼及其效果如下。 ~~~ @Entry@Componentstruct ToDoList { ... build() { Row() { Column() { Text(...) ... ForEach(this.totalTasks,(item) => { TodoItem({content:item}) },...) } .width('100%') } .height('100%') } } ~~~ **圖8**ToDoList頁面 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114005.12138313563148947387408726256839:50001231000000:2800:C207F4B1FC5A190DF56160071004365C18A9B4210AD75447A27F7AECB5B2E0B4.png?needInitFileName=true?needInitFileName=true) ## **三、HarmonyOS第一課:應用程序框架** ## **闖關習題** * 一個應用只能有一個UIAbility。錯誤 * 創建的Empty Ability模板工程,初始會生成一個UIAbility文件。:正確 * 每調用一次router.pushUrl()方法,頁面路由棧數量均會加1。:錯誤 * API9及以上,router.pushUrl()方法,默認的跳轉頁面使用的模式是哪一種?Standard(多實例模式) * UIAbility啟動模式需要在module.json5文件中配置哪個字段?launchType * API9及以上,router.pushUrl()方法的mode參數可以配置為以下哪幾種跳轉頁面使用的模式?Single單實例模式和Standard多實例模式。 * UIAbility的生命周期有哪幾個狀態?包括Create、Foreground、Background、Destroy四個狀態 * UIAbility有哪幾種的啟動模式?支持singleton(單實例模式)、multiton(多實例模式)和 specified(指定實例模式)3種啟動模式。 ## **3.1 UIAbility概述** UIAbility是一種包含用戶界面的應用組件,主要用于和用戶進行交互。UIAbility也是系統調度的單元,為應用提供窗口在其中繪制界面。 每一個UIAbility實例,都對應于一個最近任務列表中的任務。 **一個應用可以有一個UIAbility,也可以有多個UIAbility**,創建的Empty Ability模板工程,初始會生成一個UIAbility文件。 如下圖所示。例如瀏覽器應用可以通過一個UIAbility結合多頁面的形式讓用戶進行的搜索和瀏覽內容;而聊天應用增加一個“外賣功能”的場景,則可以將聊天應用中“外賣功能”的內容獨立為一個UIAbility,當用戶打開聊天應用的“外賣功能”,查看外賣訂單詳情,此時有新的聊天消息,即可以通過最近任務列表切換回到聊天窗口繼續進行聊天對話。 一個UIAbility可以對應于多個頁面,建議將一個獨立的功能模塊放到一個UIAbility中,以多頁面的形式呈現。例如新聞應用在瀏覽內容的時候,可以進行多頁面的跳轉使用。 **圖1**單UIAbility應用和多UIAbility應用 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114011.74239043062763697270334112578297:50001231000000:2800:AD40C7AD7AC5BCF7929E1032AC309D9EA2CE0388FAFD02F3520EB4303FC7B608.png?needInitFileName=true?needInitFileName=true "點擊放大") ## **3.2 UIAbility內頁面的跳轉和數據傳遞** UIAbility的數據傳遞包括有UIAbility內頁面的跳轉和數據傳遞、UIAbility間的數據跳轉和數據傳遞,本章節主要講解UIAbility內頁面的跳轉和數據傳遞。 在一個應用包含一個UIAbility的場景下,可以通過新建多個頁面來實現和豐富應用的內容。這會涉及到UIAbility內頁面的新建以及UIAbility內頁面的跳轉和數據傳遞。 打開DevEco Studio,選擇一個Empty Ability工程模板,創建一個工程,例如命名為MyApplication。 * 在src/main/ets/entryability目錄下,初始會生成一個UIAbility文件EntryAbility.ts。可以在EntryAbility.ts文件中根據業務需要實現UIAbility的生命周期回調內容。 * 在src/main/ets/pages目錄下,會生成一個Index頁面。這也是基于UIAbility實現的應用的入口頁面。可以在Index頁面中根據業務需要實現入口頁面的功能。 * 在src/main/ets/pages目錄下,右鍵New->Page,新建一個Second頁面,用于實現頁面間的跳轉和數據傳遞。 為了實現頁面的跳轉和數據傳遞,需要新建一個頁面。在原有Index頁面的基礎上,新建一個頁面,例如命名為Second.ets。 頁面間的導航可以通過頁面路由router模塊來實現。頁面路由模塊根據頁面url找到目標頁面,從而實現跳轉。通過頁面路由模塊,可以使用不同的url訪問不同的頁面,包括跳轉到UIAbility內的指定頁面、用UIAbility內的某個頁面替換當前頁面、返回上一頁面或指定的頁面等。具體使用方法請參見[ohos.router (頁面路由)](https://developer.harmonyos.com/cn/docs/documentation/doc-references-V3/js-apis-router-0000001478061893-V3?catalogVersion=V3)。 ## **3.2.1 頁面跳轉和參數接收** 在使用頁面路由之前,需要先導入router模塊,如下代碼所示。 ~~~ import router from '@ohos.router'; ~~~ 頁面跳轉的幾種方式,根據需要選擇一種方式跳轉即可。 * 方式一:API9及以上,router.pushUrl()方法新增了mode參數,可以將mode參數配置為router.RouterMode.Single單實例模式和router.RouterMode.Standard多實例模式。 在單實例模式下:如果目標頁面的url在頁面棧中已經存在同url頁面,離棧頂最近同url頁面會被移動到棧頂,移動后的頁面為新建頁,原來的頁面仍然存在棧中,頁面棧的元素數量不變;如果目標頁面的url在頁面棧中不存在同url頁面,按多實例模式跳轉,頁面棧的元素數量會加1。 DOC.NOTE 當頁面棧的元素數量較大或者超過32時,可以通過調用router.clear()方法清除頁面棧中的所有歷史頁面,僅保留當前頁面作為棧頂頁面。 ~~~ router.pushUrl({ url: 'pages/Second', params: { src: 'Index頁面傳來的數據', }}, router.RouterMode.Single) ~~~ * 方式二:API9及以上,router.replaceUrl()方法新增了mode參數,可以將mode參數配置為router.RouterMode.Single單實例模式和router.RouterMode.Standard多實例模式。 在單實例模式下:如果目標頁面的url在頁面棧中已經存在同url頁面,離棧頂最近同url頁面會被移動到棧頂,替換當前頁面,并銷毀被替換的當前頁面,移動后的頁面為新建頁,頁面棧的元素數量會減1;如果目標頁面的url在頁面棧中不存在同url頁面,按多實例模式跳轉,頁面棧的元素數量不變。 ~~~ router.replaceUrl({ url: 'pages/Second', params: { src: 'Index頁面傳來的數據', }}, router.RouterMode.Single) ~~~ 已經實現了頁面的跳轉,接下來,在Second頁面中如何進行自定義參數的接收呢? 通過調用router.getParams()方法獲取Index頁面傳遞過來的自定義參數。 ~~~ import router from '@ohos.router'; @Entry@Componentstruct Second { @State src: string = (router.getParams() as Record<string, string>)['src']; // 頁面刷新展示 // ...} ~~~ 效果示意如下圖所示。在Index頁面中,點擊“Next”后,即可從Index頁面跳轉到Second頁面,并在Second頁面中接收參數和進行頁面刷新展示。 **圖2**Index頁面跳轉到Second頁面 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114011.91665771657297428088312711708057:50001231000000:2800:A616F5D416A99E30E4A1E9EEC0CBEC0C19CBD384C9A39A05D3B1E7A7788F6DEA.png?needInitFileName=true?needInitFileName=true "點擊放大") ## **3.2.2 頁面返回和參數接收** 經常還會遇到一個場景,在Second頁面中,完成了一些功能操作之后,希望能返回到Index頁面,那我們要如何實現呢? 在Second頁面中,可以通過調用router.back()方法實現返回到上一個頁面,或者在調用router.back()方法時增加可選的options參數(增加url參數)返回到指定頁面。 DOC.NOTE * 調用router.back()返回的目標頁面需要在頁面棧中存在才能正常跳轉。 * 例如調用router.pushUrl()方法跳轉到Second頁面,在Second頁面可以通過調用router.back()方法返回到上一個頁面。 * 例如調用router.clear()方法清空了頁面棧中所有歷史頁面,僅保留當前頁面,此時則無法通過調用router.back()方法返回到上一個頁面。 * 返回上一個頁面。 ~~~ router.back(); ~~~ * 返回到指定頁面。 ~~~ router.back({ url: 'pages/Index' }); ~~~ 效果示意如下圖所示。在Second頁面中,點擊“Back”后,即可從Second頁面返回到Index頁面。 **圖3**Second頁面返回到Index頁面 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114011.14970941967153485432157339428475:50001231000000:2800:1984CB83B4E86684AB9A0CA2ADDB03A5D033142BFD47ADE34E895D4F41647B90.png?needInitFileName=true?needInitFileName=true "點擊放大") 頁面返回可以根據業務需要增加一個詢問對話框。 即在調用router.back()方法之前,可以先調用router.enableBackPageAlert()方法開啟頁面返回詢問對話框功能。 DOC.NOTE * router.enableBackPageAlert()方法開啟頁面返回詢問對話框功能,只針對當前頁面生效。例如在調用router.pushUrl()或者router.replaceUrl()方法,跳轉后的頁面均為新建頁面,因此在頁面返回之前均需要先調用router.enableBackPageAlert()方法之后,頁面返回詢問對話框功能才會生效。 * 如需關閉頁面返回詢問對話框功能,可以通過調用router.disableAlertBeforeBackPage()方法關閉該功能即可。 ~~~ router.enableBackPageAlert({ message: 'Message Info'}); router.back(); ~~~ 在Second頁面中,調用router.back()方法返回上一個頁面或者返回指定頁面時,根據需要繼續增加自定義參數,例如在返回時增加一個自定義參數src。 ~~~ router.back({ url: 'pages/Index', params: { src: 'Second頁面傳來的數據', }}) ~~~ 從Second頁面返回到Index頁面。在Index頁面通過調用router.getParams()方法,獲取Second頁面傳遞過來的自定義參數。 DOC.NOTE 調用router.back()方法,不會新建頁面,返回的是原來的頁面,在原來頁面中@State聲明的變量不會重復聲明,以及也不會觸發頁面的aboutToAppear()生命周期回調,因此無法直接在變量聲明以及頁面的aboutToAppear()生命周期回調中接收和解析router.back()傳遞過來的自定義參數。 可以放在業務需要的位置進行參數解析。示例代碼在Index頁面中的onPageShow()生命周期回調中進行參數的解析。 ~~~ import router from '@ohos.router';class routerParams { src:string constructor(str:string) { this.src = str }}@Entry@Componentstruct Index { @State src: string = ''; onPageShow() { this.src = (router.getParams() as routerParams).src } // 頁面刷新展示 // ...} ~~~ 效果示意圖如下圖所示。在Second頁面中,點擊“Back”后,即可從Second頁面返回到Index頁面,并在Index頁面中接收參數和進行頁面刷新展示。 **圖4**Second頁面帶參數返回Index頁面 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114011.12623918692265902034557168065752:50001231000000:2800:D483457916CAA37618C00EAE6094FF948C45608A1D8A1A8F6B2121A51165DC23.png?needInitFileName=true?needInitFileName=true "點擊放大") ## **3.3 UIAbility的生命周期** 當用戶瀏覽、切換和返回到對應應用的時候,應用中的UIAbility實例會在其生命周期的不同狀態之間轉換。 UIAbility類提供了很多回調,通過這些回調可以知曉當前UIAbility的某個狀態已經發生改變:例如UIAbility的創建和銷毀,或者UIAbility發生了前后臺的狀態切換。 例如從桌面點擊圖庫應用圖標,到啟動圖庫應用,應用的狀態經過了從創建到前臺展示的狀態變化。如下圖所示。 **圖5**從桌面點擊圖庫應用圖標啟動應用 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114011.24788759508988041583401965384025:50001231000000:2800:3C65B520FA6FADF05090E8E5B429BB4C3C26749CDD17D0DA6586D731FA2B9651.png?needInitFileName=true?needInitFileName=true "點擊放大") 回到桌面,從最近任務列表,切換回到圖庫應用,應用的狀態經過了從后臺到前臺展示的狀態變化。如下圖所示。 **圖6**從最近任務列表切換回到圖庫應用 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114011.25455291479663923862214520328938:50001231000000:2800:D4E8F6454E5DA104334F70E8B3393056647F8CF308E351EBE6B3F7719F975587.png?needInitFileName=true?needInitFileName=true "點擊放大") 在UIAbility的使用過程中,會有多種生命周期狀態。掌握UIAbility的生命周期,對于應用的開發非常重要。 為了實現多設備形態上的裁剪和多窗口的可擴展性,系統對組件管理和窗口管理進行了解耦。UIAbility的生命周期包括Create、Foreground、Background、Destroy四個狀態,WindowStageCreate和WindowStageDestroy為窗口管理器(WindowStage)在UIAbility中管理UI界面功能的兩個生命周期回調,從而實現UIAbility與窗口之間的弱耦合。如下圖所示。 **圖7**UIAbility生命周期狀態 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114011.85030804480791293473872381051767:50001231000000:2800:B4ADE6E5CD82D6A8D3D179B6610C6DD1ABB5549A115DAD982BCF7B672788A4FB.png?needInitFileName=true?needInitFileName=true) * Create狀態,在UIAbility實例創建時觸發,系統會調用onCreate回調。可以在onCreate回調中進行相關初始化操作。 ~~~ import UIAbility from '@ohos.app.ability.UIAbility';import window from '@ohos.window'; export default class EntryAbility extends UIAbility { onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) { // 應用初始化 // ... } // ...} ~~~ 例如用戶打開電池管理應用,在應用加載過程中,在UI頁面可見之前,可以在onCreate回調中讀取當前系統的電量情況,用于后續的UI頁面展示。 * UIAbility實例創建完成之后,在進入Foreground之前,系統會創建一個WindowStage。每一個UIAbility實例都對應持有一個WindowStage實例。 WindowStage為本地窗口管理器,用于管理窗口相關的內容,例如與界面相關的獲焦/失焦、可見/不可見。 可以在onWindowStageCreate回調中,設置UI頁面加載、設置WindowStage的事件訂閱。 在onWindowStageCreate(windowStage)中通過loadContent接口設置應用要加載的頁面,Window接口的使用詳見[窗口開發指導](https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/application-window-stage-0000001427584712-V3?catalogVersion=V3)。 ~~~ import UIAbility from '@ohos.app.ability.UIAbility';import window from '@ohos.window'; export default class EntryAbility extends UIAbility { // ... onWindowStageCreate(windowStage: window.WindowStage) { // 設置UI頁面加載 // 設置WindowStage的事件訂閱(獲焦/失焦、可見/不可見) // ... windowStage.loadContent('pages/Index', (err, data) => { // ... }); } // ...} ~~~ 例如用戶打開游戲應用,正在打游戲的時候,有一個消息通知,打開消息,消息會以彈窗的形式彈出在游戲應用的上方,此時,游戲應用就從獲焦切換到了失焦狀態,消息應用切換到了獲焦狀態。對于消息應用,在onWindowStageCreate回調中,會觸發獲焦的事件回調,可以進行設置消息應用的背景顏色、高亮等操作。 * Foreground和Background狀態,分別在UIAbility切換至前臺或者切換至后臺時觸發。 分別對應于onForeground回調和onBackground回調。 onForeground回調,在UIAbility的UI頁面可見之前,即UIAbility切換至前臺時觸發。可以在onForeground回調中申請系統需要的資源,或者重新申請在onBackground中釋放的資源。 onBackground回調,在UIAbility的UI頁面完全不可見之后,即UIAbility切換至后臺時候觸發。可以在onBackground回調中釋放UI頁面不可見時無用的資源,或者在此回調中執行較為耗時的操作,例如狀態保存等。 ~~~ import UIAbility from '@ohos.app.ability.UIAbility';import window from '@ohos.window'; export default class EntryAbility extends UIAbility { // ... onForeground() { // 申請系統需要的資源,或者重新申請在onBackground中釋放的資源 // ... } onBackground() { // 釋放UI頁面不可見時無用的資源,或者在此回調中執行較為耗時的操作 // 例如狀態保存等 // ... }} ~~~ 例如用戶打開地圖應用查看當前地理位置的時候,假設地圖應用已獲得用戶的定位權限授權。在UI頁面顯示之前,可以在onForeground回調中打開定位功能,從而獲取到當前的位置信息。 當地圖應用切換到后臺狀態,可以在onBackground回調中停止定位功能,以節省系統的資源消耗。 * 前面我們了解了UIAbility實例創建時的onWindowStageCreate回調的相關作用。 對應于onWindowStageCreate回調。在UIAbility實例銷毀之前,則會先進入onWindowStageDestroy回調,我們可以在該回調中釋放UI頁面資源。 ~~~ import UIAbility from '@ohos.app.ability.UIAbility';import window from '@ohos.window'; export default class EntryAbility extends UIAbility { // ... onWindowStageDestroy() { // 釋放UI頁面資源 // ... }} ~~~ 例如在onWindowStageCreate中設置的獲焦/失焦等WindowStage訂閱事件。 * Destroy狀態,在UIAbility銷毀時觸發。可以在onDestroy回調中進行系統資源的釋放、數據的保存等操作。 ~~~ import UIAbility from '@ohos.app.ability.UIAbility';import window from '@ohos.window'; export default class EntryAbility extends UIAbility { // ... onDestroy() { // 系統資源的釋放、數據的保存等 // ... }} ~~~ 例如用戶使用應用的程序退出功能,會調用UIAbilityContext的terminalSelf()方法,從而完成UIAbility銷毀。或者用戶使用最近任務列表關閉該UIAbility實例時,也會完成UIAbility的銷毀。 ## **3.4 UIAbility的啟動模式** 對于瀏覽器或者新聞等應用,用戶在打開該應用,并瀏覽訪問相關內容后,回到桌面,再次打開該應用,顯示的仍然是用戶當前訪問的界面。 對于應用的分屏操作,用戶希望使用兩個不同應用(例如備忘錄應用和圖庫應用)之間進行分屏,也希望能使用同一個應用(例如備忘錄應用自身)進行分屏。 對于文檔應用,用戶從文檔應用中打開一個文檔內容,回到文檔應用,繼續打開同一個文檔,希望打開的還是同一個文檔內容。 基于以上場景的考慮,UIAbility當前支持singleton(單實例模式)、multiton(多實例模式)和specified(指定實例模式)3種啟動模式。 對啟動模式的詳細說明如下: * singleton(單實例模式) singleton啟動模式為單實例模式,也是默認情況下的啟動模式。 每次調用startAbility()方法時,如果應用進程中該類型的UIAbility實例已經存在,則復用系統中的UIAbility實例。系統中只存在唯一一個該UIAbility實例,即在最近任務列表中只存在一個該類型的UIAbility實例。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114011.07840675822392264038881193804928:50001231000000:2800:B423B1AC64CC5CA43BC33B9D9969FB7EC82B4480063BCB7FA95D0BAA631B3891.gif?needInitFileName=true?needInitFileName=true) DOC.NOTE 應用的UIAbility實例已創建,該UIAbility配置為單實例模式,再次調用startAbility()方法啟動該UIAbility實例。由于啟動的還是原來的UIAbility實例,并未重新創建一個新的UIAbility實例,此時只會進入該UIAbility的onNewWant()回調,不會進入其onCreate()和onWindowStageCreate()生命周期回調。 singleton啟動模式的開發使用,在module.json5文件中的“launchType”字段配置為“singleton”即可。 ~~~ { "module": { // ... "abilities": [ { "launchType": "singleton", // ... } ] }} ~~~ * multiton(多實例模式) multiton啟動模式為多實例模式,每次調用startAbility()方法時,都會在應用進程中創建一個新的該類型UIAbility實例。即在最近任務列表中可以看到有多個該類型的UIAbility實例。這種情況下可以將UIAbility配置為multiton(多實例模式)。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114012.86234775954609048451037616082182:50001231000000:2800:5362370291ECB54CBAEA427175180C22700ED0F5D2B6BB488C25DD21C11D93B4.gif?needInitFileName=true?needInitFileName=true) multiton啟動模式的開發使用,在module.json5配置文件中的launchType字段配置為multiton即可。 ~~~ { "module": { // ... "abilities": [ { "launchType": "multiton", // ... } ] }} ~~~ * specified(指定實例模式) specified啟動模式為指定實例模式,針對一些特殊場景使用(例如文檔應用中每次新建文檔希望都能新建一個文檔實例,重復打開一個已保存的文檔希望打開的都是同一個文檔實例)。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114012.88674820101476086219905247075726:50001231000000:2800:D471454BB1F0F17541D84A1B9C678FAA398C72DE5106DDD072027D9A40FE9D1A.gif?needInitFileName=true?needInitFileName=true) 例如有兩個UIAbility:EntryAbility和SpecifiedAbility,SpecifiedAbility配置為指定實例模式啟動,需要從EntryAbility的頁面中啟動SpecifiedAbility。 1. 在SpecifiedAbility中,將module.json5配置文件的launchType字段配置為specified。 ~~~ { "module": { // ... "abilities": [ { "launchType": "specified", // ... } ] }} ~~~ 2. 在創建UIAbility實例之前,開發者可以為該實例指定一個唯一的字符串Key,這樣在調用startAbility()方法時,應用就可以根據指定的Key來識別響應請求的UIAbility實例。在EntryAbility中,調用startAbility()方法時,可以在want參數中增加一個自定義參數,例如instanceKey,以此來區分不同的UIAbility實例。 ~~~ // 在啟動指定實例模式的UIAbility時,給每一個UIAbility實例配置一個獨立的Key標識// 例如在文檔使用場景中,可以用文檔路徑作為Key標識import common from '@ohos.app.ability.common';import Want from '@ohos.app.ability.Want';import { BusinessError } from '@ohos.base'; function getInstance() { return 'key';} let context:common.UIAbilityContext = ...; // context為調用方UIAbility的UIAbilityContextlet want: Want = { deviceId: '', // deviceId為空表示本設備 bundleName: 'com.example.myapplication', abilityName: 'SpecifiedAbility', moduleName: 'specified', // moduleName非必選 parameters: { // 自定義信息 instanceKey: getInstance(), },} context.startAbility(want).then(() => { console.info('Succeeded in starting ability.');}).catch((err: BusinessError) => { console.error(`Failed to start ability. Code is ${err.code}, message is ${err.message}`);}) ~~~ 3. 由于SpecifiedAbility的啟動模式被配置為指定實例啟動模式,因此在SpecifiedAbility啟動之前,會先進入對應的AbilityStage的onAcceptWant()生命周期回調中,以獲取該UIAbility實例的Key值。然后系統會自動匹配,如果存在與該UIAbility實例匹配的Key,則會啟動與之綁定的UIAbility實例,并進入該UIAbility實例的onNewWant()回調函數;否則會創建一個新的UIAbility實例,并進入該UIAbility實例的onCreate()回調函數和onWindowStageCreate()回調函數。 示例代碼中,通過實現onAcceptWant()生命周期回調函數,解析傳入的want參數,獲取自定義參數instanceKey。業務邏輯會根據這個參數返回一個字符串Key,用于標識當前UIAbility實例。如果返回的Key已經對應一個已啟動的UIAbility實例,系統會將該UIAbility實例拉回前臺并獲焦,而不會創建新的實例。如果返回的Key沒有對應已啟動的UIAbility實例,則系統會創建新的UIAbility實例并啟動。 ~~~ import AbilityStage from '@ohos.app.ability.AbilityStage';import Want from '@ohos.app.ability.Want'; export default class MyAbilityStage extends AbilityStage { onAcceptWant(want: Want): string { // 在被調用方的AbilityStage中,針對啟動模式為specified的UIAbility返回一個UIAbility實例對應的一個Key值 // 當前示例指的是module1 Module的SpecifiedAbility if (want.abilityName === 'SpecifiedAbility') { // 返回的字符串Key標識為自定義拼接的字符串內容 if (want.parameters) { return `SpecifiedAbilityInstance_${want.parameters.instanceKey}`; } } return ''; }} ~~~ DOC.NOTE 1. 當應用的UIAbility實例已經被創建,并且配置為指定實例模式時,如果再次調用startAbility()方法啟動該UIAbility實例,且AbilityStage的onAcceptWant()回調匹配到一個已創建的UIAbility實例,則系統會啟動原來的UIAbility實例,并且不會重新創建一個新的UIAbility實例。此時,該UIAbility實例的onNewWant()回調會被觸發,而不會觸發onCreate()和onWindowStageCreate()生命周期回調。 2. DevEco Studio默認工程中未自動生成AbilityStage,AbilityStage文件的創建請參見AbilityStage組件容器。 例如在文檔應用中,可以為不同的文檔實例內容綁定不同的Key值。每次新建文檔時,可以傳入一個新的Key值(例如可以將文件的路徑作為一個Key標識),此時AbilityStage中啟動UIAbility時都會創建一個新的UIAbility實例;當新建的文檔保存之后,回到桌面,或者新打開一個已保存的文檔,回到桌面,此時再次打開該已保存的文檔,此時AbilityStage中再次啟動該UIAbility時,打開的仍然是之前原來已保存的文檔界面。 以如下步驟所示進行舉例說明。 1. 打開文件A,對應啟動一個新的UIAbility實例,例如啟動UIAbility實例1。 2. 在最近任務列表中關閉文件A的任務進程,此時UIAbility實例1被銷毀,回到桌面,再次打開文件A,此時對應啟動一個新的UIAbility實例,例如啟動UIAbility實例2。 3. 回到桌面,打開文件B,此時對應啟動一個新的UIAbility實例,例如啟動UIAbility實例3。 4. 回到桌面,再次打開文件A,此時仍然啟動之前的UIAbility實例2,因為系統會自動匹配UIAbility實例的Key值,如果存在與之匹配的Key,則會啟動與之綁定的UIAbility實例。在此例中,之前啟動的UIAbility實例2與文件A綁定的Key是相同的,因此系統會拉回UIAbility實例2并讓其獲焦,而不會創建新的實例。 ## **四、HarmonyOS第一課:從簡單的頁面開始** ## **闖關習題** * 在Column容器中的子組件默認是按照從上到下的垂直方向布局的,其主軸的方向是垂直方向,在Row容器中的組件默認是按照從左到右的水平方向布局的,其主軸的方向是水平方向。 正確 * List容器可以沿水平方向排列,也可以沿垂直方向排列。 正確 * 當Tabs組件的參數barPosition為BarPosition.End時,頁簽位于頁面底部。 錯誤 * Resource是資源引用類型,用于設置組件屬性的值,可以定義組件的顏色、文本大小、組件大小等屬性。 正確 * 使用TextInput完成一個密碼輸入框,推薦設置type屬性為下面哪個值?InputType.Password * 使用Image加載網絡圖片,需要以下那種權限?ohos.permission.INTERNET * 下面哪個組件層次結構是錯誤的?Grid>Row>GridItem * Row容器的主軸是水平方向,交叉軸是垂直方向,其參數類型為VerticalAlign (垂直對齊),VerticalAlign 定義了以下幾種類型?Top、Bottom、Center * 下面哪些組件是容器組件?Column、Row * 關于Tabs組件頁簽的位置設置,下面描述正確的是?ABCD * A. 當barPosition為Start(默認值),vertical屬性為false時(默認值),頁簽位于容器頂部。 * B. 當barPosition為Start(默認值) ,vertical屬性為true時,頁簽位于容器左側。 * C. 當barPosition為End ,vertical屬性為false(默認值)時,頁簽位于容器底部。 * D. 當barPosition為End ,vertical屬性為true時,頁簽位于容器右側。 ## **4.1 常用基礎組件** ## **4.1.1 組件介紹** 組件(Component)是界面搭建與顯示的最小單位,HarmonyOS ArkUI聲明式開發范式為開發者提供了豐富多樣的UI組件,我們可以使用這些組件輕松的編寫出更加豐富、漂亮的界面。 組件根據功能可以分為以下五大類:基礎組件、容器組件、媒體組件、繪制組件、畫布組件。其中基礎組件是視圖層的基本組成單元,包括Text、Image、TextInput、Button、LoadingProgress等,例如下面這個常用的登錄界面就是由這些基礎組件組合而成。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102114256.28802753566728898498304998943977:50001231000000:2800:FE20D3B6F279BC1450B3B4D172874836DEA69202B2A75554D752FA3EB9D671BC.png) 下面我們將分別介紹這些常用基礎組件的使用。 ## **4.1.2 Text** Text組件用于在界面上展示一段文本信息,可以包含子組件Span。 `文本樣式` 針對包含文本元素的組件,例如Text、Span、Button、TextInput等,可使用fontColor、fontSize、fontStyle、 fontWeight、fontFamily這些文本樣式,分別設置文本的顏色、大小、樣式、粗細以及字體,文本樣式的屬性如下表: |名稱|參數類型|描述| | --- | --- | --- | |fontColor|ResourceColor|設置文本顏色。| |fontSize|Length | Resource設置文本尺寸,Length為number類型時,使用fp單位。| fontStyle|FontStyle設置文本的字體樣式。默認值:FontStyle.Normal。 |fontWeight|number 、FontWeight 、string|設置文本的字體粗細,number類型取值[100, 900],取值間隔為100,默認為400,取值越大,字體越粗。string類型僅支持number類型取值的字符串形式,例如“400”,以及“bold”、“bolder”、“lighter”、“regular”、“medium”,分別對應FontWeight中相應的枚舉值。默認值:FontWeight.Normal。 |fontFamily|string | Resource設置文本的字體列表。使用多個字體,使用“,”進行分割,優先級按順序生效。例如:“Arial,sans-serif”。| 下面示例代碼中包含兩個Text組件,第一個使用的是默認樣式,第二個給文本設置了一些文本樣式。 ~~~ @Entry @Component struct TextDemo { build() { Row() { Column() { Text('HarmonyOS') Text('HarmonyOS') .fontColor(Color.Blue) .fontSize(20) .fontStyle(FontStyle.Italic) .fontWeight(FontWeight.Bold) .fontFamily('Arial') } .width('100%') } .backgroundColor(0xF1F3F5) .height('100%') } } ~~~ 效果圖如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102114323.57981255142134670738558204839276:50001231000000:2800:F3EE4576F8914D849A63F9C5ECBB4975EE604C6F1946761DEA0A651962DB4A54.png) 除了通用屬性和文本樣式設置,下面列舉了一些Text組件的常用屬性的使用。 `設置文本對齊方式` 使用textAlign屬性可以設置文本的對齊方式,示例代碼如下: ~~~ Text('HarmonyOS') .width(200) .textAlign(TextAlign.Start) .backgroundColor(0xE6F2FD) ~~~ textAlign參數類型為TextAlign,定義了以下幾種類型: * Start(默認值):水平對齊首部。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102114348.49288669266616261684997326269610:50001231000000:2800:C83DAC3856051E7F08D9380E4E9B9C549791FC558BFAD923E1C2940103109326.png) * Center:水平居中對齊。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102114354.42991757464168023713723675951861:50001231000000:2800:752E6D300496CE068E18468EAAE7634E86193E63A4E550D0A3DC5CC3A19E7467.png) * End:水平對齊尾部。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102114402.58501849207326897212851960388072:50001231000000:2800:313C09B1F0D805695F59F5495D121C5018FCAE91DC4DC613FB8E40F91F25089D.png) `設置文本超長顯示` 當文本內容較多超出了Text組件范圍的時候,您可以使用textOverflow設置文本截取方式,需配合maxLines使用,單獨設置不生效,maxLines用于設置文本顯示最大行數。下面的示例代碼將textOverflow設置為Ellipsis ,它將顯示不下的文本用 “...” 表示: ~~~ Text('This is the text content of Text Component This is the text content of Text Component') .fontSize(16) .maxLines(1) .textOverflow({overflow:TextOverflow.Ellipsis}) .backgroundColor(0xE6F2FD) ~~~ 效果圖如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102114644.68621516400405905798472500772263:50001231000000:2800:A59BEB8CFE497FC541786416270A6AFBE858A57D1260B775BF6FF3B2E907264B.png) `設置文本裝飾線` 使用decoration設置文本裝飾線樣式及其顏色,大家在瀏覽網頁的時候經常可以看到裝飾線,例如帶有下劃線超鏈接文本。decoration包含type和color兩個參數,其中type用于設置裝飾線樣式,參數類型為TextDecorationType,color為可選參數。 下面的示例代碼給文本設置了下劃線,下劃線顏色為黑色: ~~~ Text('HarmonyOS') .fontSize(20) .decoration({ type: TextDecorationType.Underline, color: Color.Black }) .backgroundColor(0xE6F2FD) ~~~ 效果圖如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102114652.69929054773100409790044510283833:50001231000000:2800:A94A866A6D98B51775AE12C3032607C04C117EAA8718BF4250850536BE1EF13E.png) TextDecorationTyp包含以下幾種類型: * None:不使用文本裝飾線。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102114659.61640987050459682152517426675687:50001231000000:2800:A8FD2B5031982209FE5EAD7FB83154041709083A37592BE677A3CFBF8E787579.png) * Overline:文字上劃線修飾。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102114706.69305764948918217718838177409802:50001231000000:2800:0B6C095B820036BC7F50A6D475A92E0D00A7E74D2BDDB3D4351CAE0EFCA42141.png) * LineThrough:穿過文本的修飾線。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102114713.98751594289299554991883153173320:50001231000000:2800:B9264CD5F1ED6C83B00DF3FC8E801AEF11C480FA03C25595749274718306613B.png) * Underline:文字下劃線修飾。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102114720.48060694093389960570703646806593:50001231000000:2800:0536F799D1C27DCC66B5E9F085C366E1EA8F4468B648B27A3CCE637EFFF70934.png) ## **4.1.3 Image** Image組件用來渲染展示圖片,它可以讓界面變得更加豐富多彩。只需要給Image組件設置圖片地址、寬和高,圖片就能加載出來,示例如下: ~~~ Image($r("app.media.icon")) .width(100) .height(100) ~~~ 效果圖如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102114731.39553189055211199745202206018464:50001231000000:2800:E0097635C3FE290FC9F65597FC978BCA530F5095D149BFB0A350FF57AEC2FA1A.png) `設置縮放類型` 為了使圖片在頁面中有更好的顯示效果,有時候需要對圖片進行縮放處理。您可以使用objectFit屬性設置圖片的縮放類型,objectFit的參數類型為ImageFit。 現有原始圖片如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102114739.77512714800298501080868850482759:50001231000000:2800:8D788340321FFC1F46F5A06C88E506AD5E769F26D95717A5881C3FC1C09BBF76.png) 將圖片加載到Image組件,設置寬高各100,設置objectFit為Cover(默認值),設置圖片背景色為灰色0xCCCCCC。示例代碼如下: ~~~ Image($r("app.media.image2")) .objectFit(ImageFit.Cover) .backgroundColor(0xCCCCCC) .width(100) .height(100) ~~~ 效果圖如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102114748.77856821710199152384867059247638:50001231000000:2800:397491140D28842CCE2BC0E60F5999E608782F715E5C294D5172CA7DF129558D.png) ImageFit包含以下幾種類型: * Contain:保持寬高比進行縮小或者放大,使得圖片完全顯示在顯示邊界內。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102114756.85370236529918272452949887734853:50001231000000:2800:F979D2A17F79AB04523A87103269E8E3BB4C63A9BA65E6B1BFB298CED56610AA.png) * Cover(默認值):保持寬高比進行縮小或者放大,使得圖片兩邊都大于或等于顯示邊界。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102114803.14650992439248534530091313002089:50001231000000:2800:E532173B3B6DD9E92259F72675697BCEAE09FE72485AEA9EFD5FE9794FF14521.png) * Auto:自適應顯示。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102114822.02052799258986122197086618380763:50001231000000:2800:190ECE96A361A13874FAAEA025A05042FC3D8E342712FC156BA48E7CFEFD40D1.png) * Fill:不保持寬高比進行放大縮小,使得圖片充滿顯示邊界。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102114940.81869569958898798079916961462275:50001231000000:2800:5B61B3C66E719CA10CA11E6E7AE81BDC98D1A3F472A07ADE97C446AA04DAAE8D.png) * ScaleDown:保持寬高比顯示,圖片縮小或者保持不變。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102114951.52445007947325457633854339454055:50001231000000:2800:FA807656E11E50469F5271B238470C6851F0D4ED5498914110CA07EFE52A80DE.png) * None:保持原有尺寸顯示。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102115000.02884099972463949183812198219733:50001231000000:2800:076F73E497FD71E0338C04471F3B5770E5CCB46D3EE80A6A7565E6C503550E5D.png) `加載網絡圖片` 比如瀏覽新聞的時候,圖片一般從網絡加載而來,Image組件支持加載網絡圖片,將圖片地址換成網絡圖片地址進行加載。 ~~~ Image('https://www.example.com/xxx.png') ~~~ 為了成功加載網絡圖片,您需要在module.json5文件中申明網絡訪問權限。 ~~~ { "module" : { "requestPermissions":[ { "name": "ohos.permission.INTERNET" } ] } } ~~~ ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102115035.57901904356964270856139117822179:50001231000000:2800:38B8FF4B793C0D18A93C462CCD38A2644D9C09D65674F94484ECDE7556979346.png) 應用訪問網絡需要申請ohos.permission.INTERNET權限,因為HarmonyOS提供了一種訪問控制機制即應用權限,用來保證這些數據或功能不會被不當或惡意使用。關于應用權限的的詳細信息開發者可以參考:[訪問控制](https://developer.harmonyos.com/cn/docs/documentation/doc-guides/accesstoken-overview-0000001333641125)。 ## **4.1.4?TextInput** TextInput組件用于輸入單行文本,響應輸入事件。TextInput的使用也非常廣泛,例如應用登錄賬號密碼、發送消息等。和Text組件一樣,TextInput組件也支持文本樣式設置,下面的示例代碼實現了一個簡單的輸入框: ~~~ TextInput() .fontColor(Color.Blue) .fontSize(20) .fontStyle(FontStyle.Italic) .fontWeight(FontWeight.Bold) .fontFamily('Arial') ~~~ 效果圖如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102115050.52007374582615623658877407943036:50001231000000:2800:0E13911716C4593FFFE505D017280722EF6A8B33E0CEC9DFB56264D63094E2E3.png) `設置輸入提示文本` 當我們平時使用輸入框的時候,往往會有一些提示文字。例如登錄賬號的時候會有“請輸入賬號”這樣的文本提示,當用戶輸入內容之后,提示文本就會消失,這種提示功能使用placeholder屬性就可以輕松的實現。您還可以使用placeholderColor和placeholderFont分別設置提示文本的顏色和樣式,示例代碼如下: ~~~ TextInput({ placeholder: '請輸入帳號' }) .placeholderColor(0x999999) .placeholderFont({ size: 20, weight: FontWeight.Medium, family: 'cursive', style: FontStyle.Italic }) ~~~ 效果圖如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102115100.71218216501593991658353961758535:50001231000000:2800:AA026BFE5F8E049506B4D2EEF7BF7DABE7284E5D4226C75AA32B57E124399DAA.gif) `設置輸入類型` 可以使用type屬性來設置輸入框類型。例如密碼輸入框,一般輸入密碼的時候,為了用戶密碼安全,內容會顯示為“......”,針對這種場景,將type屬性設置為InputType.Password就可以實現。示例代碼如下: ~~~ TextInput({ placeholder: '請輸入密碼' }) .type(InputType.Password) ~~~ 效果圖如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102115110.98778894209644360311002826080034:50001231000000:2800:8BE918EFF85332BB9140B61DFA66D7145E578618FA793045987D49CF092433EC.gif) type的參數類型為InputType,包含以下幾種輸入類型: * Normal:基本輸入模式。支持輸入數字、字母、下劃線、空格、特殊字符。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102115236.92283096302002995875166741811262:50001231000000:2800:C8BC6EA9E6A997BEA2866CE6E0C2D310333C33A43E0076B191E91D366CF1ADA5.png) * Password:密碼輸入模式。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102115245.61142009918082527144769110156060:50001231000000:2800:7C742106E203659C40CB97B3A8EE5AF7AA76B74C81DA0AD15BFD3110C19E28C1.png) * Email:e-mail地址輸入模式。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102115252.23834448972955999986156295718579:50001231000000:2800:FD5F0326431F311238418A1EF5C1C182CB7A017C49F06946CB376C1E9EA788A4.png) * Number:純數字輸入模式。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102115300.02746846170472180689146322192383:50001231000000:2800:EE622B2BECDD549786E554861A899563031A770C888553F3F274EB5DBF6C5D04.png) `設置光標位置` 可以使用TextInputController動態設置光位置,下面的示例代碼使用TextInputController的caretPosition方法,將光標移動到了第二個字符后。 ~~~ @Entry @Component struct TextInputDemo { controller: TextInputController = new TextInputController() build() { Column() { TextInput({ controller: this.controller }) Button('設置光標位置') .onClick(() => { this.controller.caretPosition(2) }) } .height('100%') .backgroundColor(0xE6F2FD) } } ~~~ 效果圖如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102115317.35282590037382074976290780085823:50001231000000:2800:CC4870F4F8F97CC04C6F11016FD88D50323C29F810F9D24B1C43E38E82A2BDEB.gif) `獲取輸入文本` 我們可以給TextInput設置onChange事件,輸入文本發生變化時觸發回調,下面示例代碼中的value為實時獲取用戶輸入的文本信息。 ~~~ @Entry @Component struct TextInputDemo { @State text: string = '' build() { Column() { TextInput({ placeholder: '請輸入賬號' }) .caretColor(Color.Blue) .onChange((value: string) => { this.text = value }) Text(this.text) } .alignItems(HorizontalAlign.Center) .padding(12) .backgroundColor(0xE6F2FD) } } ~~~ 效果圖如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102115338.69547694354764992883763360730107:50001231000000:2800:BA25C8408214B902ECBD215DABB7916AED4E7FB78B8F280799A83B607AA7C909.gif) ## **4.1.5?Button** Button組件主要用來響應點擊操作,可以包含子組件。下面的示例代碼實現了一個“登錄按鈕”: ~~~ Button('登錄', { type: ButtonType.Capsule, stateEffect: true }) .width('90%') .height(40) .fontSize(16) .fontWeight(FontWeight.Medium) .backgroundColor('#007DFF') ~~~ 效果圖如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102115354.26733108824112602628872699815029:50001231000000:2800:3DB0F3BDDC39FA9DA9B6E2CBA0E80BE64853C6EB2527A6EE2B245EE38E99F134.png) `設置按鈕樣式` type用于定義按鈕樣式,示例代碼中ButtonType.Capsule表示膠囊形按鈕;stateEffect用于設置按鈕按下時是否開啟切換效果,當狀態置為false時,點擊效果關閉,默認值為true。 我們可以設置多種樣式的Button,除了Capsule可以以設置Normal和Circle: * Capsule:膠囊型按鈕(圓角默認為高度的一半)。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102115404.84177998820036585609688095430669:50001231000000:2800:24CD642B18947F8B32434F65CD7E2C91A4DFF48A1E4017520A161AEDA3ED30F0.png) * Circle:圓形按鈕。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102115412.39174965009162190745462310963210:50001231000000:2800:60EFB66EA62ED61D60B28BCFB7C3F290964FDB1893DD428AF61531F600F69D3D.png) * Normal:普通按鈕(默認不帶圓角)。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102115421.19102972822842807234366477791218:50001231000000:2800:A1E76374955D7EDDC93058ED603C1232459C8CD2AFBF7B4EC96D650A6D80AF3F.png) `設置按鈕點擊事件` 可以給Button綁定onClick事件,每當用戶點擊Button的時候,就會回調執行onClick方法,調用里面的邏輯代碼。 ~~~ Button('登錄', { type: ButtonType.Capsule, stateEffect: true }) ... .onClick(() => { // 處理點擊事件邏輯 }) ~~~ `包含子組件` Button組件可以包含子組件,讓您可以開發出更豐富多樣的Button,下面的示例代碼中Button組件包含了一個Image組件: ~~~ Button({ type: ButtonType.Circle, stateEffect: true }) { Image($r('app.media.icon_delete')) .width(30) .height(30) } .width(55) .height(55) .backgroundColor(0x317aff) ~~~ 效果圖如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102115439.25988537294891479063435944038115:50001231000000:2800:7FB346C30BFCECCBDB87006E52813894BCCD0DCF5041B524F5B5C59F851D0DAA.png) ## **4.1.6?LoadingProgress** LoadingProgress組件用于顯示加載進展,比如應用的登錄界面,當我們點擊登錄的時候,顯示的“正在登錄”的進度條狀態。LoadingProgress的使用非常簡單,只需要設置顏色和寬高就可以了。 ~~~ LoadingProgress() .color(Color.Blue) .height(60) .width(60) ~~~ 效果圖如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102115448.33909926886752830021497148543403:50001231000000:2800:98D471A991979097448D44923AF569FB44492907377352F8752E055DDECD23C2.gif) ## **4.1.7?使用資源引用類型** Resource是資源引用類型,用于設置組件屬性的值。推薦大家優先使用Resource類型,將資源文件(字符串、圖片、音頻等)統一存放于resources目錄下,便于開發者統一維護。同時系統可以根據當前配置加載合適的資源,例如,開發者可以根據屏幕尺寸呈現不同的布局效果,或根據語言設置提供不同的字符串。 例如下面的這段代碼,直接在代碼中寫入了字符串和數字這樣的硬編碼。 ~~~ Button('登錄', { type: ButtonType.Capsule, stateEffect: true }) .width(300) .height(40) .fontSize(16) .fontWeight(FontWeight.Medium) .backgroundColor('#007DFF') ~~~ 我們可以將這些硬編碼寫到entry/src/main/resources下的資源文件中。 在string.json中定義Button顯示的文本。 ~~~ { "string": [ { "name": "login_text", "value": "登錄" } ] } ~~~ 在float.json中定義Button的寬高和字體大小。 ~~~ { "float": [ { "name": "button_width", "value": "300vp" }, { "name": "button_height", "value": "40vp" }, { "name": "login_fontSize", "value": "18fp" } ] } ~~~ 在color.json中定義Button的背景顏色。 ~~~ { "color": [ { "name": "button_color", "value": "#1890ff" } ] } ~~~ 然后在Button組件通過“$r('app.type.name')”的形式引用應用資源。app代表應用內resources目錄中定義的資源;type代表資源類型(或資源的存放位置),可以取“color”、“float”、“string”、“plural”、“media”;name代表資源命名,由開發者定義資源時確定。 ~~~ Button($r('app.string.login_text'), { type: ButtonType.Capsule }) .width($r('app.float.button_width')) .height($r('app.float.button_height')) .fontSize($r('app.float.login_fontSize')) .backgroundColor($r('app.color.button_color')) ~~~ ## **4.1.8?參考資料** 常用基礎的組件的更多使用方法可以參考: * [Text](https://developer.harmonyos.com/cn/docs/documentation/doc-references/ts-basic-components-text-0000001333720953) * [Image](https://developer.harmonyos.com/cn/docs/documentation/doc-references/ts-basic-components-image-0000001281001226) * [TextInput](https://developer.harmonyos.com/cn/docs/documentation/doc-references/ts-basic-components-textinput-0000001333321201) * [Button](https://developer.harmonyos.com/cn/docs/documentation/doc-references/ts-basic-components-button-0000001281480682) * [LoadingProgress](https://developer.harmonyos.com/cn/docs/documentation/doc-references/ts-basic-components-loadingprogress-0000001281361106) 引用資源類型的使用可以參考: * [資源訪問](https://developer.harmonyos.com/cn/docs/documentation/doc-guides/resource-categories-and-access-0000001435940589) ## **4.2 Column&Row組件的使用** ## **4.2.1?概述** 一個豐富的頁面需要很多組件組成,那么,我們如何才能讓這些組件有條不紊地在頁面上布局呢?這就需要借助容器組件來實現。 容器組件是一種比較特殊的組件,它可以包含其他的組件,而且按照一定的規律布局,幫助開發者生成精美的頁面。容器組件除了放置基礎組件外,也可以放置容器組件,通過多層布局的嵌套,可以布局出更豐富的頁面。 ArkTS為我們提供了豐富的容器組件來布局頁面,本文將以構建登錄頁面為例,介紹Column和Row組件的屬性與使用。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102115843.56869299768090628795048614168558:50001231000000:2800:FF53A5245D2813CE1EB2CBCE12EC3C1B5311CE160DCB25180A25008353C9FE49.png) ## **4.2.2?組件介紹** `布局容器概念` 線性布局容器表示按照垂直方向或者水平方向排列子組件的容器,ArkTS提供了Column和Row容器來實現線性布局。 * Column表示沿垂直方向布局的容器。 * Row表示沿水平方向布局的容器。 `主軸和交叉軸概念` 在布局容器中,默認存在兩根軸,分別是主軸和交叉軸,這兩個軸始終是相互垂直的。不同的容器中主軸的方向不一樣的。 * **主軸**:在Column容器中的子組件是按照從上到下的垂直方向布局的,其主軸的方向是垂直方向;在Row容器中的組件是按照從左到右的水平方向布局的,其主軸的方向是水平方向。 圖2-1?Column容器&Row容器主軸 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102115909.22873051359993638219253132361587:50001231000000:2800:DD239797D663EF0C454A0344974110E18232305E2A48C465CB8104AB7AF3B76D.png) * **交叉軸**:與主軸垂直相交的軸線,如果主軸是垂直方向,則交叉軸就是水平方向;如果主軸是水平方向,則交叉軸是垂直方向。 圖2-2?Column容器&Row容器交叉軸 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102115920.30331924582664597826254415316555:50001231000000:2800:3531A948E66A8AEFBC7ACD4C60C8412D31B655FF90C52DCA0F06B4E8C6B1DE16.png) `屬性介紹` 了解布局容器的主軸和交叉軸,主要是為了讓大家更好地理解子組件在主軸和交叉軸的排列方式。 接下來,我們將詳細講解Column和Row容器的兩個屬性justifyContent和alignItems。 | 屬性名稱| 描述 | | --- | --- | | justifyContent | 設置子組件在主軸方向上的對齊格式。 | | alignItems | 設置子組件在交叉軸方向上的對齊格式。| 1. 主軸方向的對齊(justifyContent) 子組件在主軸方向上的對齊使用justifyContent屬性來設置,其參數類型是[FlexAlign](https://developer.harmonyos.com/cn/docs/documentation/doc-references/ts-appendix-enums-0000001281201130#ZH-CN_TOPIC_0000001281201130__flexalign)。FlexAlign定義了以下幾種類型: * Start:元素在主軸方向首端對齊,第一個元素與行首對齊,同時后續的元素與前一個對齊。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102120034.55002488649666187375452743115722:50001231000000:2800:976A64244DB1E53DBC7A14E86E8F112B367438881EA17072BCD99E5055D7AFC7.png) * Center:元素在主軸方向中心對齊,第一個元素與行首的距離以及最后一個元素與行尾距離相同。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102120046.20470396574913051754264941328891:50001231000000:2800:4649A1983ADC5F536F5BFB50A8BD04A252B37F9AB8E77022FAFF8BAB5AAD0452.png) * End:元素在主軸方向尾部對齊,最后一個元素與行尾對齊,其他元素與后一個對齊。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102120054.44350106670787534871004252237249:50001231000000:2800:597FEE07999060038975E0A285F22F073C2136A3C0E4CA01233DFF0B42A7C4E7.png) * SpaceBetween:元素在主軸方向均勻分配彈性元素,相鄰元素之間距離相同。 第一個元素與行首對齊,最后一個元素與行尾對齊。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102120104.61306639232139542053660772535451:50001231000000:2800:FACFD1B00005797C0D61BF2251FF367148A168AC095028E9D9E75BE7E1B3B265.png) * SpaceAround:元素在主軸方向均勻分配彈性元素,相鄰元素之間距離相同。 第一個元素到行首的距離和最后一個元素到行尾的距離是相鄰元素之間距離的一半。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102120116.52750004019093798708346178362676:50001231000000:2800:DA370AD7DEA8F643106B77791FA2F7615794BEB0131FB0592BC29E1CFB26CDE8.png) * SpaceEvenly:元素在主軸方向等間距布局,無論是相鄰元素還是邊界元素到容器的間距都一樣。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102120152.44741357072809756514190316665580:50001231000000:2800:8E31F05211B1741DCA78E90C063CDEB3CA69288D8F7382A2FA3BC771E446FE9E.png) 2. 交叉軸方向的對齊(alignItems) 子組件在交叉軸方向上的對齊方式使用alignItems屬性來設置。 Column容器的主軸是垂直方向,交叉軸是水平方向,其參數類型為HorizontalAlign(水平對齊),HorizontalAlign定義了以下幾種類型: * Start:設置子組件在水平方向上按照起始端對齊。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102120211.31994376220784208696781894804465:50001231000000:2800:A8A75840C5F94CCB04EB7AC2DB6B149D2F5D5D1ED3235EFE280BC5626540BDE3.png) * Center(默認值):設置子組件在水平方向上居中對齊。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102120220.73077653691423267775452046266338:50001231000000:2800:83604C691A921D5058905270A365B0680FF1439AE1F633AD43B6E771FCFFCB13.png) * End:設置子組件在水平方向上按照末端對齊。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102120231.65240250845776209940244577183907:50001231000000:2800:94F40FCB4DE8E3960D9AF78074611ACB80ABD642D779C0EC4F8932A662CF5853.png) Row容器的主軸是水平方向,交叉軸是垂直方向,其參數類型為VerticalAlign(垂直對齊),VerticalAlign定義了以下幾種類型: * Top:設置子組件在垂直方向上居頂部對齊。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102120244.63498934647274355019879968073116:50001231000000:2800:31FACC5011AC5DFE44996316747E28DFEBDCC20262ABAC9B6ADAFB8ACD78165D.png) * Center(默認值):設置子組件在豎直方向上居中對齊。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102120303.35457043976848012752874761084865:50001231000000:2800:08FB1D45111107EFDC181C535AD817C051FE8D668AB08B096554009E57B96E5D.png) * Bottom:設置子組件在豎直方向上居底部對齊。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102120311.16840084438584689920641238749604:50001231000000:2800:F0091640561F333A2BB462FCF6420DCB08670B2D1EFE4B0722BFCE88CA4C0859.png) `接口介紹` 接下來,我們介紹Column和Row容器的接口。 | 容器組件| 接口| | --- | --- | | Column| Column(value?:{space?: string | number}) | | Row | Row(value?:{space?: string | number})| Column和Row容器的接口都有一個可選參數space,表示子組件在主軸方向上的間距。 效果如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102120341.29998493080685429299217527032153:50001231000000:2800:F722E0E366A541021F8FF33486CD9177134A9F1C588090C7669DEE216D0E92FE.png) ## **4.2.3?組件使用** 我們來具體講解如何高效的使用Column和Row容器組件來構建這個登錄頁面。 當我們從設計同學那拿到一個頁面設計圖時,我們需要對頁面進行拆解,先確定頁面的布局,再分析頁面上的內容分別使用哪些組件來實現。 我們仔細分析這個登錄頁面。在靜態布局中,組件整體是從上到下布局的,因此構建該頁面可以使用Column來構建。在此基礎上,我們可以看到有部分內容在水平方向上由幾個基礎組件構成,例如頁面中間的短信驗證碼登錄與忘記密碼以及頁面最下方的其他方式登錄,那么構建這些內容的時候,可以在Column組件中嵌套Row組件,繼而在Row組件中實現水平方向的布局。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102120349.33899634270625091234160890072156:50001231000000:2800:4959D75213902E59FC842C767ADF8643A9B8B50D2ECBF4E84A0DB82D4766B133.png) 根據上述頁面拆解,在Column容器里,依次是Image、Text、TextInput、Button等基礎組件,還有兩組組件是使用Row容器組件來實現的,主要代碼如下: ~~~ @Entry @Component export struct LoginPage { build() { Column() { Image($r('app.media.logo')) ... Text($r('app.string.login_page')) ... Text($r('app.string.login_more')) ... TextInput({ placeholder: $r('app.string.account') }) ... TextInput({ placeholder: $r('app.string.password') }) ... Row() { Text($r(…)) Text($r(…)) } Button($r('app.string.login'), { type: ButtonType.Capsule, stateEffect: true }) ... Row() { this.imageButton($r(…)) this.imageButton($r(…)) this.imageButton($r(…)) } ... } ... } } ~~~ 我們詳細看一下使用Row容器的兩組組件。 兩個文本組件展示的內容是按水平方向布局的,使用兩端對齊的方式。這里我們使用Row容器組件,并且需要配置主軸上(水平方向)的對齊格式justifyContent為FlexAlign.SpaceBetween(兩端對齊)。 ~~~ Row() { Text($r(…)) Text($r(…)) } .justifyContent(FlexAlign.SpaceBetween) .width('100%') ~~~ 其他登錄方式的三個按鈕也是按水平方向布局的,同樣使用Row容器組件。這里按鈕的間距是一致的,我們可以通過配置可選參數space來設置按鈕間距,使子組件間距一致。 ~~~ Row({ space: CommonConstants.LOGIN_METHODS_SPACE }) { this.imageButton($r(…)) this.imageButton($r(…)) this.imageButton($r(…)) } ~~~ 至此,你已經完成這個登錄頁面的簡單布局實現了。你可以參考[《常用組件與布局》](https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/tutorials_ArkTSComponents)這個Codelab驗證一下頁面效果。 另外,你也可以通過[《常用布局容器對齊方式》](https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/tutorials_LayoutAlignETS)這個Codelab來深入學習Column、Row、Flex、Stack容器組件的對齊方式,掌握更多布局容器的使用方法。 ## **4.2.4 參考鏈接** * Column組件的相關API參考:[Column組件](https://developer.harmonyos.com/cn/docs/documentation/doc-references/ts-container-column-0000001333641085)。 * Row組件的相關API參考:[Row組件](https://developer.harmonyos.com/cn/docs/documentation/doc-references/ts-container-row-0000001281480714)。 ## **4.3 List組件和Grid組件的使用** ## **4.3.1 簡介** 在我們常用的手機應用中,經常會見到一些數據列表,如設置頁面、通訊錄、商品列表等。下圖中兩個頁面都包含列表,“首頁”頁面中包含兩個網格布局,“商城”頁面中包含一個商品列表。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114021.76298852609843510545142880169923:50001231000000:2800:2137B63F852C293D664B758EE1E1D4C3EE9D38B33213FD815E191D6425308960.png?needInitFileName=true?needInitFileName=true "點擊放大") 上圖中的列表中都包含一系列相同寬度的列表項,連續、多行呈現同類數據,例如圖片和文本。常見的列表有線性列表(List列表)和網格布局(Grid列表): ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114021.24614355659251483508034402327799:50001231000000:2800:BD15FDDE47541155D6AD9C5BF53249AB1373264F661177558DE65AE348B57E8D.png?needInitFileName=true?needInitFileName=true "點擊放大") 為了幫助開發者構建包含列表的應用,ArkUI提供了List組件和Grid組件,開發者使用List和Grid組件能夠很輕松的完成一些列表頁面。 ## **4.3.2 List組件的使用** `List組件簡介` List是很常用的滾動類容器組件,一般和子組件ListItem一起使用,List列表中的每一個列表項對應一個ListItem組件。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114021.96205742196885500306668913604627:50001231000000:2800:5C79A273B85F365326E589CCC436F8D7513B041EEA45D01C2BBA18B47CB3F747.png?needInitFileName=true?needInitFileName=true "點擊放大") `使用ForEeach渲染列表` 列表往往由多個列表項組成,所以我們需要在List組件中使用多個ListItem組件來構建列表,這就會導致代碼的冗余。使用循環渲染(ForEach)遍歷數組的方式構建列表,可以減少重復代碼,示例代碼如下: ~~~ @Entry@Componentstruct ListDemo { private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] build() { Column() { List({ space: 10 }) { ForEach(this.arr, (item: number) => { ListItem() { Text(`${item}`) .width('100%') .height(100) .fontSize(20) .fontColor(Color.White) .textAlign(TextAlign.Center) .borderRadius(10) .backgroundColor(0x007DFF) } }, item => item) } } .padding(12) .height('100%') .backgroundColor(0xF1F3F5) }} ~~~ 效果圖如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114021.99873170533581670005602323955528:50001231000000:2800:6AC5121B60676A6DA174E1131282C66DCC9A5F0929A1ED40E622EB4707A69FC4.png?needInitFileName=true?needInitFileName=true "點擊放大") `設置列表分割線` List組件子組件ListItem之間默認是沒有分割線的,部分場景子組件ListItem間需要設置分割線,這時候您可以使用List組件的divider屬性。divider屬性包含四個參數: * strokeWidth: 分割線的線寬。 * color: 分割線的顏色。 * startMargin:分割線距離列表側邊起始端的距離。 * endMargin: 分割線距離列表側邊結束端的距離。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114021.70899408000972728202421604905907:50001231000000:2800:E1FF3F55C2015193E71725692CDA33590276D4079935932CF9307BBA153B23FD.png?needInitFileName=true?needInitFileName=true "點擊放大") `List列表滾動事件監聽` List組件提供了一系列事件方法用來監聽列表的滾動,您可以根據需要,監聽這些事件來做一些操作: * onScroll:列表滑動時觸發,返回值scrollOffset為滑動偏移量,scrollState為當前滑動狀態。 * onScrollIndex:列表滑動時觸發,返回值分別為滑動起始位置索引值與滑動結束位置索引值。 * onReachStart:列表到達起始位置時觸發。 * onReachEnd:列表到底末尾位置時觸發。 * onScrollStop:列表滑動停止時觸發。 使用示例代碼如下: ~~~ List({ space: 10 }) { ForEach(this.arr, (item) => { ListItem() { Text(`${item}`) ... } }, item => item)}.onScrollIndex((firstIndex: number, lastIndex: number) => { console.info('first' + firstIndex) console.info('last' + lastIndex)}).onScroll((scrollOffset: number, scrollState: ScrollState) => { console.info('scrollOffset' + scrollOffset) console.info('scrollState' + scrollState)}).onReachStart(() => { console.info('onReachStart')}).onReachEnd(() => { console.info('onReachEnd')}).onScrollStop(() => { console.info('onScrollStop')}) ~~~ `設置List排列方向` List組件里面的列表項默認是按垂直方向排列的,如果您想讓列表沿水平方向排列,您可以將List組件的listDirection屬性設置為Axis.Horizontal。 listDirection參數類型是[Axis](https://developer.harmonyos.com/cn/docs/documentation/doc-references-V3/ts-appendix-enums-0000001478061741-V3?catalogVersion=V3#ZH-CN_TOPIC_0000001478061741__axis),定義了以下兩種類型: * Vertical(默認值):子組件ListItem在List容器組件中呈縱向排列。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114021.26794363778619638674061157585930:50001231000000:2800:291F9F7FFFEC929BE74D8CFDCEF41C8B78C19AC0412BB1A08BB8E091125985EE.png?needInitFileName=true?needInitFileName=true) * Horizontal:子組件ListItem在List容器組件中呈橫向排列。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114022.50652940074991976805062285871306:50001231000000:2800:6824367FCCB4EED18E566F9D7B67C364B93767894D38E518A0B535607C73AC5D.png?needInitFileName=true?needInitFileName=true "點擊放大") ## **4.3.3 Grid組件的使用** `Grid組件簡介` Grid組件為網格容器,是一種網格列表,由“行”和“列”分割的單元格所組成,通過指定“項目”所在的單元格做出各種各樣的布局。Grid組件一般和子組件GridItem一起使用,Grid列表中的每一個條目對應一個GridItem組件。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114022.77512977599829805485453715488693:50001231000000:2800:CE05196127051B0013E7E0C4C548E240A220B6D159E6CC1882430C512A39091A.png?needInitFileName=true?needInitFileName=true) `使用ForEach渲染網格布局` 和List組件一樣,Grid組件也可以使用ForEach來渲染多個列表項GridItem,我們通過下面的這段示例代碼來介紹Grid組件的使用。 ~~~ @Entry@Componentstruct GridExample { // 定義一個長度為16的數組 private arr: string[] = new Array(16).fill('').map((_, index) => `item ${index}`); build() { Column() { Grid() { ForEach(this.arr, (item: string) => { GridItem() { Text(item) .fontSize(16) .fontColor(Color.White) .backgroundColor(0x007DFF) .width('100%') .height('100%') .textAlign(TextAlign.Center) } }, item => item) } .columnsTemplate('1fr 1fr 1fr 1fr') .rowsTemplate('1fr 1fr 1fr 1fr') .columnsGap(10) .rowsGap(10) .height(300) } .width('100%') .padding(12) .backgroundColor(0xF1F3F5) }} ~~~ 示例代碼中創建了16個GridItem列表項。同時設置columnsTemplate的值為'1fr 1fr 1fr 1fr',表示這個網格為4列,將Grid允許的寬分為4等分,每列占1份;rowsTemplate的值為'1fr 1fr 1fr 1fr',表示這個網格為4行,將Grid允許的高分為4等分,每行占1份。這樣就構成了一個4行4列的網格列表,然后使用columnsGap設置列間距為10vp,使用rowsGap設置行間距也為10vp。示例代碼效果圖如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114022.17665645714348674469081257729838:50001231000000:2800:879489F0B0BA8E7D0CD604D58C09FAD81A4E8A71660A50A01DEE3E8BE2C08577.png?needInitFileName=true?needInitFileName=true) 上面構建的網格布局使用了固定的行數和列數,所以構建出的網格是不可滾動的。然而有時候因為內容較多,我們通過滾動的方式來顯示更多的內容,就需要一個可以滾動的網格布局。我們只需要設置rowsTemplate和columnsTemplate中的一個即可。 將示例代碼中GridItem的高度設置為固定值,例如100;僅設置columnsTemplate屬性,不設置rowsTemplate屬性,就可以實現Grid列表的滾動: ~~~ Grid() { ForEach(this.arr, (item: string) => { GridItem() { Text(item) .height(100) ... } }, item => item)}.columnsTemplate('1fr 1fr 1fr 1fr').columnsGap(10).rowsGap(10).height(300) ~~~ 此外,Grid像List一樣也可以使用onScrollIndex來監聽列表的滾動。 ## **4.3.4 列表性能優化** 開發者在使用長列表時,如果直接采用循環渲染方式,會一次性加載所有的列表元素,從而導致頁面啟動時間過長,影響用戶體驗,推薦通過以下方式來進行列表性能優化: [使用數據懶加載](https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/ui-ts-performance-improvement-recommendation-0000001477981001-V3#ZH-CN_TOPIC_0000001477981001__%E6%8E%A8%E8%8D%90%E4%BD%BF%E7%94%A8%E6%95%B0%E6%8D%AE%E6%87%92%E5%8A%A0%E8%BD%BD) [設置list組件的寬高](https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/ui-ts-performance-improvement-recommendation-0000001477981001-V3#ZH-CN_TOPIC_0000001477981001__%E8%AE%BE%E7%BD%AElist%E7%BB%84%E4%BB%B6%E7%9A%84%E5%AE%BD%E9%AB%98) ## **4.3.5 參考鏈接** 1. List組件的相關API參考:[List組件](https://developer.harmonyos.com/cn/docs/documentation/doc-references-V3/ts-container-list-0000001477981213-V3?catalogVersion=V3)。 2. Grid組件的相關API參考:[Grid組件](https://developer.harmonyos.com/cn/docs/documentation/doc-references-V3/ts-container-grid-0000001478341161-V3?catalogVersion=V3)。 3. 循環渲染(ForEach):[循環渲染](https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/arkts-rendering-control-0000001427744548-V3?catalogVersion=V3#ZH-CN_TOPIC_0000001427744548__%E5%BE%AA%E7%8E%AF%E6%B8%B2%E6%9F%93)。 ## **4.4 Tabs組件的使用** ## **4.4.1 概述** 在我們常用的應用中,經常會有視圖內容切換的場景,來展示更加豐富的內容。比如下面這個頁面,點擊底部的頁簽的選項,可以實現“首頁”和“我的” 兩個內容視圖的切換。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114006.15810054681671435100170427657354:50001231000000:2800:E9BDCF9EAC3F9EBFCC7F002F5D2C732C85BAEB85269A48A40EF4E5DFF2A7CA58.png?needInitFileName=true?needInitFileName=true) ArkUI開發框架提供了一種頁簽容器組件Tabs,開發者通過Tabs組件可以很容易的實現內容視圖的切換。頁簽容器Tabs的形式多種多樣,不同的頁面設計頁簽不一樣,可以把頁簽設置在底部、頂部或者側邊。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114006.46447288653153728154552503552279:50001231000000:2800:1144177FA18D0F78CC134D77DA5068F7A78EB98D8FA083A9C3162E288D254510.png?needInitFileName=true?needInitFileName=true "點擊放大") 本文將詳細介紹Tabs組件的使用。 ## **4.4.2 Tabs組件的簡單使用** Tabs組件僅可包含子組件TabContent,每一個頁簽對應一個內容視圖即TabContent組件。下面的示例代碼構建了一個簡單的頁簽頁面: ~~~ @Entry@Componentstruct TabsExample { private controller: TabsController = new TabsController() build() { Column() { Tabs({ barPosition: BarPosition.Start, controller: this.controller }) { TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Green) } .tabBar('green') TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Blue) } .tabBar('blue') TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Yellow) } .tabBar('yellow') TabContent() { Column().width('100%').height('100%').backgroundColor(Color.Pink) } .tabBar('pink') } .barWidth('100%') // 設置TabBar寬度 .barHeight(60) // 設置TabBar高度 .width('100%') // 設置Tabs組件寬度 .height('100%') // 設置Tabs組件高度 .backgroundColor(0xF5F5F5) // 設置Tabs組件背景顏色 } .width('100%') .height('100%') }} ~~~ 效果圖如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114006.29388684682955911574804670483565:50001231000000:2800:3EFBA20D3549581C44141A3D234D80BC46D486E6CE793FBBC861ED176F07DF66.png?needInitFileName=true?needInitFileName=true) 上面示例代碼中,Tabs組件中包含4個子組件TabContent,通過TabContent的tabBar屬性設置TabBar的顯示內容。使用通用屬性width和height設置了Tabs組件的寬高,使用barWidth和barHeight設置了TabBar的寬度和高度。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114006.79264385194341570090819155451771:50001231000000:2800:A2CE99D2A7C909EC0C35C13B543FD391AA55A4004E975C4679AA45CE5BFC2CA9.png?needInitFileName=true?needInitFileName=true "點擊放大") 說明 * TabContent組件不支持設置通用寬度屬性,其寬度默認撐滿Tabs父組件。 * TabContent組件不支持設置通用高度屬性,其高度由Tabs父組件高度與TabBar組件高度決定。 ## **4.4.3 設置TabBar布局模式** 因為Tabs的布局模式默認是Fixed的,所以Tabs的頁簽是不可滑動的。當頁簽比較多的時候,可能會導致頁簽顯示不全,將布局模式設置為Scrollable的話,可以實現頁簽的滾動。 Tabs的布局模式有Fixed(默認)和Scrollable兩種: * BarMode.Fixed:所有TabBar平均分配barWidth寬度(縱向時平均分配barHeight高度),頁簽不可滾動,效果圖如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114006.00460165163455564647201027087937:50001231000000:2800:94F3210B44CB210AE136DA49EC478CA31CF08F979C5431AECEC3A39D7E001C64.png?needInitFileName=true?needInitFileName=true "點擊放大") * BarMode.Scrollable:每一個TabBar均使用實際布局寬度,超過總長度(橫向Tabs的barWidth,縱向Tabs的barHeight)后可滑動。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114006.14498940122308999777083020551597:50001231000000:2800:55787BB5B63B27802B5972AB686754E17AAF5F4C8E5D276C8B9D6C225B7DE855.png?needInitFileName=true?needInitFileName=true "點擊放大") * 當頁簽比較多的時候,可以滑動頁簽,下面的示例代碼將barMode設置為BarMode.Scrollable,實現了可滾動的頁簽: ~~~ @Entry @Component struct TabsExample { private controller: TabsController = new TabsController() build() { Column() { Tabs({ barPosition: BarPosition.Start, controller: this.controller }) { TabContent() { Column() .width('100%') .height('100%') .backgroundColor(Color.Green) } .tabBar('green') TabContent() { Column() .width('100%') .height('100%') .backgroundColor(Color.Blue) } .tabBar('blue') ... } .barMode(BarMode.Scrollable) .barWidth('100%') .barHeight(60) .width('100%') .height('100%') } } } ~~~ ## **4.4.4 設置TabBar位置和排列方向** Tabs組件頁簽默認顯示在頂部,某些場景下您可能希望Tabs頁簽出現在底部或者側邊,您可以使用Tabs組件接口中的參數barPosition設置頁簽位置。此外頁簽顯示位置還與vertical屬性相關聯,vertical屬性用于設置頁簽的排列方向,當vertical的屬性值為false(默認值)時頁簽橫向排列,為true時頁簽縱向排列。 barPosition的值可以設置為BarPosition.Start(默認值)和BarPosition.End: * BarPosition.Start vertical屬性方法設置為false(默認值)時,頁簽位于容器頂部。 ~~~ Tabs({ barPosition: BarPosition.Start }) { ...}.vertical(false) .barWidth('100%') .barHeight(60) ~~~ 效果圖如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114006.62395530800278543062807409174448:50001231000000:2800:4203E893971E6BBECF8248462719E91115086765AA25E0BB89EACF09374FE6AC.png?needInitFileName=true?needInitFileName=true) vertical屬性方法設置為true時,頁簽位于容器左側。 ~~~ Tabs({ barPosition: BarPosition.Start }) { ...}.vertical(true) .barWidth(100) .barHeight(200) ~~~ 效果圖如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114006.83138558673583645639387597842606:50001231000000:2800:7E259ED98DEE865CFF39CEB5C8F65908876B532C3AE41E85B81E7F43DA5311E2.png?needInitFileName=true?needInitFileName=true) * BarPosition.End vertical屬性方法設置為false時,頁簽位于容器底部。 ~~~ Tabs({ barPosition: BarPosition.End }) { ...}.vertical(false) .barWidth('100%') .barHeight(60) ~~~ 效果圖如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114006.05108002382405768723664985207344:50001231000000:2800:0348FDFA2E0DBF13049E7CBEF16A39A787B3F28BD92CD31DA433F7C3A19A8FA2.png?needInitFileName=true?needInitFileName=true) vertical屬性方法設置為true時,頁簽位于容器右側。 ~~~ Tabs({ barPosition: BarPosition.End}) { ...}.vertical(true) .barWidth(100) .barHeight(200) ~~~ 效果圖如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114006.04492209501668721336598242374605:50001231000000:2800:98598526A504AFC8EFD7918CD72936A0BD29771D53DDB6F74EF50F975E61A768.png?needInitFileName=true?needInitFileName=true) ## **4.4.5 自定義TabBar樣式** TabBar的默認顯示效果如下所示: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114007.57593322016245242510238779899423:50001231000000:2800:FFC6D0D6E2F086D34F1604E5942C502354571C066F2532F28D2F25538B368B2F.png?needInitFileName=true?needInitFileName=true "點擊放大") 往往開發過程中,UX給我們的設計效果可能并不是這樣的,比如下面的這種底部頁簽效果: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114007.72898712019039007392373201818437:50001231000000:2800:F1B0F2CEE7AEBA54CD61BA50E076745B89930EF879881CAF646D7D6E559B3F54.png?needInitFileName=true?needInitFileName=true) TabContent的tabBar屬性除了支持string類型,還支持使用@Builder裝飾器修飾的函數。您可以使用@Builder裝飾器,構造一個生成自定義TabBar樣式的函數,實現上面的底部頁簽效果,示例代碼如下: ~~~ @Entry @Component struct TabsExample { @State currentIndex: number = 0; private tabsController: TabsController = new TabsController(); @Builder TabBuilder(title: string, targetIndex: number, selectedImg: Resource, normalImg: Resource) { Column() { Image(this.currentIndex === targetIndex ? selectedImg : normalImg) .size({ width: 25, height: 25 }) Text(title) .fontColor(this.currentIndex === targetIndex ? '#1698CE' : '#6B6B6B') } .width('100%') .height(50) .justifyContent(FlexAlign.Center) .onClick(() => { this.currentIndex = targetIndex; this.tabsController.changeIndex(this.currentIndex); }) } build() { Tabs({ barPosition: BarPosition.End, controller: this.tabsController }) { TabContent() { Column().width('100%').height('100%').backgroundColor('#00CB87') } .tabBar(this.TabBuilder('首頁', 0, $r('app.media.home_selected'), $r('app.media.home_normal'))) TabContent() { Column().width('100%').height('100%').backgroundColor('#007DFF') } .tabBar(this.TabBuilder('我的', 1, $r('app.media.mine_selected'), $r('app.media.mine_normal'))) } .barWidth('100%') .barHeight(50) .onChange((index: number) => { this.currentIndex = index; }) } } ~~~ 示例代碼中將barPosition的值設置為BarPosition.End,使頁簽顯示在底部。使用@Builder修飾TabBuilder函數,生成由Image和Text組成的頁簽。同時也給Tabs組件設置了TabsController控制器,當點擊某個頁簽時,調用changeIndex方法進行頁簽內容切換。 最后還需要給Tabs添加onChange事件,Tab頁簽切換后觸發該事件,這樣當我們左右滑動內容視圖的時候,頁簽樣式也會跟著改變。 ## **4.4.6 參考** * Tabs組件的更多屬性和參數的使用,可以參考API:[Tabs](https://developer.harmonyos.com/cn/docs/documentation/doc-references-V3/ts-container-tabs-0000001478181433-V3?catalogVersion=V3)。 * @Builder裝飾器的使用,可以參考:[@Builder](https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/arkts-dynamic-ui-elememt-building-0000001427584592-V3?catalogVersion=V3#ZH-CN_TOPIC_0000001427584592__builder)。 ## **五、HarmonyOS第一課:構建更加豐富的頁面** ## **闖關習題** @State修飾的屬性不允許在本地進行初始化。錯誤 @CustomDialog裝飾器用于裝飾自定義彈窗組件,使得彈窗可以自定義內容及樣式。正確 將Video組件的controls屬性設置為false時,不會顯示控制視頻播放的控制欄。正確 @Prop修飾的屬性值發生變化時,此狀態變化不會傳遞到其父組件。正確 使用Video組件播放網絡視頻時,需要以下哪種權限?ohos.permission.INTERNET 下列哪種組合方式可以實現子組件從父子組件單向狀態同步。@State和@Prop 下列哪些狀態裝飾器修飾的屬性必須在本地進行初始化。@State、@Provide ArkUI提供了下面哪些彈窗功能。 AlertDialog、TextPickerDialog、DatePickerDialog、@CustomDialog、TimePickerDialog ## **5.1 管理組件狀態** ## **5.1.1 概述** 在應用中,界面通常都是動態的。如圖1所示,在子目標列表中,當用戶點擊目標一,目標一會呈現展開狀態,再次點擊目標一,目標一呈現收起狀態。界面會根據不同的狀態展示不一樣的效果。 **圖1**展開/收起目標項 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114007.50352989855908807575143613709720:50001231000000:2800:60603B31427EA970E49A5131A2F8E26EF56B951C270F3B24D94F21CBE1DF31D1.gif?needInitFileName=true?needInitFileName=true) ArkUI作為一種聲明式UI,具有狀態驅動UI更新的特點。當用戶進行界面交互或有外部事件引起狀態改變時,狀態的變化會觸發組件自動更新。所以在ArkUI中,我們只需要通過一個變量來記錄狀態。當改變狀態的時候,ArkUI就會自動更新界面中受影響的部分。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114007.39160377706025210406744915044142:50001231000000:2800:B918D7FE99474ACEE4D33A18924AC4F844396C02399DCE7646A2626B5C75B02C.png?needInitFileName=true?needInitFileName=true "點擊放大") ArkUI框架提供了多種管理狀態的裝飾器來修飾變量,使用這些裝飾器修飾的變量即稱為狀態變量。 在組件范圍傳遞的狀態管理常見的場景如下: | **場景** | **裝飾器**| | :-- | :-- | | 組件內的狀態管理 | @State| | 從父組件單向同步狀態| @Prop | | 與父組件雙向同步狀態 | @Link| | 跨組件層級雙向同步狀態| @Provide和@Consume| 在組件內使用@State裝飾器來修飾變量,可以使組件根據不同的狀態來呈現不同的效果。若當前組件的狀態需要通過其父組件傳遞而來,此時需要使用@Prop裝飾器;若是父子組件狀態需要相互綁定進行雙向同步,則需要使用@Link裝飾器。使用@Provide和@Consume裝飾器可以實現跨組件層級雙向同步狀態。 在實際應用開發中,應用會根據需要封裝數據模型。如果需要觀察嵌套類對象屬性變化,需要使用@Observed和@ObjectLink裝飾器,因為上述表格中的裝飾器只能觀察到對象的第一層屬性變化。@Observed和@ObjectLink裝飾器的具體使用方法可參考[@Observed裝飾器和@ObjectLink裝飾器:嵌套類對象屬性變化](https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/arkts-observed-and-objectlink-0000001473697338-V3?catalogVersion=V3)。 另外,當狀態改變,需要對狀態變化進行監聽做一些相應的操作時,可以使用@Watch裝飾器來修飾狀態。 ## **5.1.2 組件內的狀態管理:@State** 實際開發中由于交互,組件的內容呈現可能產生變化。當需要在組件內使用狀態來控制UI的不同呈現方式時,可以使用@State裝飾器。以任務管理應用為例,當點擊子目標列表的其中一項,列表項會展開。當再次點擊同一項,列表項會收起。所以,對于某一個列表項來說,它的呈現方式會受列表項是否展開這個狀態影響。 **圖2**展開/收起目標項 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114007.52317835111317836092836111367073:50001231000000:2800:77E1EB146DBD2A34191F8F111ABE3848912C0710348AEEE23BFF9F178B646E05.gif?needInitFileName=true?needInitFileName=true) 將是否展開這個狀態定義為isExpanded變量,當其值為false表示目標項收起,值為true時表示目標項展開。 此時,需要使用@State裝飾器修飾isExpanded,使其成為目標項內部的狀態變量。通過@State裝飾后,框架內部會建立數據與視圖間的綁定, 當isExpanded狀態變化時,目標項會隨之展開或收起。 **圖3**定義是否展開狀態 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114007.45649197960477681904864887845876:50001231000000:2800:B054BCF110986B242961805CC08005E929E031C1D9B6978A10751182BBFEDAE6.png?needInitFileName=true?needInitFileName=true "點擊放大") 其具體實現只要用@State修飾isExpanded變量,定義是否展開狀態。然后通過條件渲染,實現是否顯示進度調整面板和列表項的高度變化。最后,監聽列表項的點擊事件,在onClick回調中改變isExpanded狀態。 這樣就實現了對相同列表項點擊時,列表項的展開和收起功能。當用戶反復點擊同一個列表項時,組件內的isExpanded狀態變化,列表項會自動更新。 ~~~ @Component export default struct TargetListItem { @State isExpanded: boolean = false; ... build() { ... Column() { ... if (this.isExpanded) { Blank() ProgressEditPanel(...) } } .height(this.isExpanded ? $r('app.float.expanded_item_height') : $r('app.float.list_item_height')) .onClick(() => { ... this.isExpanded = !this.isExpanded; ... }) ... } } ~~~ ## **5.1.3 從父組件單向同步狀態:@Prop** 當子組件中的狀態依賴從父組件傳遞而來時,需要使用@Prop裝飾器,@Prop修飾的變量可以和其父組件中的狀態建立單向同步關系。當父組件中狀態變化時,該狀態值也會更新至@Prop修飾的變量;對@Prop修飾的變量的修改不會影響其父組件中的狀態。 **圖4**列表的編輯模式 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114007.48656041274936249580000818927673:50001231000000:2800:545F77297CB59671CE844B2356DC04F677EECCB7260706EC948D5EA1D7B3E6D3.gif?needInitFileName=true?needInitFileName=true) 如圖4所示,在目標管理應用中,當用戶點擊子目標列表的“編輯”文本,列表進入編輯模式,點擊取消,列表退出編輯模式。 整個列表是自定義組件TargetList,頂部是文本顯示區域,主要是Text組件,底部是一個Button組件。中間區域則是用來顯示每個目標項,目標項是自定義組件TargetListItem。 從圖中可以看出,TargetListItem是TargetList的子組件。TargetList是TargetListItem父組件。 **圖5**TargetList和TargetListItem ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114007.45427026185552780164054661698684:50001231000000:2800:BAAF65FBDED4B6E3EEFEE7F6A2777BB86BB79E15F84C75F7B39D1038F3FFBC27.png?needInitFileName=true?needInitFileName=true) 對于父組件TargetList,其頂部顯示的文本和底部按鈕會隨編輯模式的變化而變化,因此父組件擁有編輯模式狀態。 對于子組件TargetListItem,其最右側是否預留位置和顯示勾選框也會隨編輯模式變化,因此子組件也擁有編輯模式狀態。 但是是否進入編輯模式,其觸發點是在用戶點擊列表的“編輯”或取消按鈕,狀態變化的源頭僅在于父組件TargetList。當父組件TargetList中的編輯模式變化時,子組件TargetListItem的編輯模式狀態需要隨之變化。 **圖6**從父組件單向同步isEditMode狀態 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114007.63145098253182931658469736073510:50001231000000:2800:6CFF8A17BCDC00E88591606139F6E152096A2C6C7682B2AACAFC418D0F586720.png?needInitFileName=true?needInitFileName=true) 在父組件TargetList中可以定義一個是否進入編輯模式的狀態,即用@State修飾isEditMode。@State修飾的變量不僅是組件內部的狀態,也可以作為子組件單向或雙向同步的數據源。ArkUI提供了@Prop裝飾器,@Prop修飾的變量可以和其父組件中的狀態建立單向同步關系,所以用@Prop修飾子組件TargetListItem中的isEditMode變量。 在父組件TargetList中,用@State修飾isEditMode,定義編輯模式狀態。然后利用條件渲染實現根據是否進入編輯模式,顯示不同的文本和按鈕。同時,在父組件中需要在用戶點擊時改變狀態,觸發界面更新。 當點擊“編輯”事件發生時,進入編輯模式,顯示取消、全選文本和勾選框,同時顯示刪除按鈕;當點擊“取消”事件發生時,退出編輯模式,顯示“編輯”文本和“添加子目標”按鈕。 ~~~ @Component export default struct TargetList { @State isEditMode: boolean = false; ... build() { Column() { Row() { ... if (this.isEditMode) { Text($r('app.string.cancel_button')) .onClick(() => { this.isEditMode = false; ... }) ... Text($r('app.string.select_all_button'))... Checkbox()... } else { Text($r('app.string.edit_button')) .onClick(() => { this.isEditMode = true; }) ... } ... } ... List({ space: CommonConstants.LIST_SPACE }) { ForEach(this.targetData, (item: TaskItemBean, index: number) => { ListItem() { TargetListItem({ isEditMode: this.isEditMode, ... }) } }, (item, index) => JSON.stringify(item) + index) } ... if (this.isEditMode) { Button($r('app.string.delete_button')) } else { Button($r('app.string.add_task')) } } ... } } ~~~ 在子組件TargetListItem中,使用@Prop修飾子組件的isEditMode變量,定義子組件的編輯模式狀態。然后同樣根據是否進入編輯模式,控制目標項最右側是否預留位置和顯示勾選框。 ~~~ @Component export default struct TargetListItem { @Prop isEditMode: boolean; ... Column() { ... } .padding({ ... right: this.isEditMode ? $r('app.float.list_edit_padding') : $r('app.float.list_padding') }) ... if (this.isEditMode) { Row() { Checkbox()... } } ... } ~~~ 最后,最關鍵的一步就是要在父組件中使用子組件時,將父組件的編輯模式狀態this.isEditMode傳遞給子組件的編輯模式狀態isEditMode。 ~~~ @Component export default struct TargetList { @State isEditMode: boolean = false; ... build() { Column() { ... List({ space: CommonConstants.LIST_SPACE }) { ForEach(this.targetData, (item: TaskItemBean, index: number) => { ListItem() { TargetListItem({ isEditMode: this.isEditMode, ... }) } }, (item, index) => JSON.stringify(item) + index) } ... } ... } } ~~~ ## **5.1.4 與父組件雙向同步狀態:@Link** 若是父子組件狀態需要相互綁定進行雙向同步時,可以使用@Link裝飾器。父組件中用于初始化子組件@Link變量的必須是在父組件中定義的狀態變量。 **圖7**切換目標項 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114007.08063644355014687331552094225475:50001231000000:2800:700D8227A3EE3E40CE49ABF3211D80EA553F8DFE60EC799FAB6301AFDC74E91B.gif?needInitFileName=true?needInitFileName=true) 在目標管理應用中,當用戶點擊同一個目標,目標項會展開或者收起。當用戶點擊不同的目標項時,除了被點擊的目標項展開,同時前一次被點擊的目標項會收起。 如圖7所示,當目標一展開時,點擊目標三,目標三會展開,同時目標一會收起。再點擊目標一時,目標一展開,同時目標三會收起。 從目標一切換到目標三的流程中,關鍵在于最后目標一的收起,當點擊目標三時,目標一需要知道點擊了目標三,目標一才會收起。 **圖8**子目標列表目標項位置索引 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114007.00725185809807510601169603208881:50001231000000:2800:9CDC1827744D8BD687EC1E24F477FEFF30D288149478EC31B6C17EB7D652DC66.png?needInitFileName=true?needInitFileName=true "點擊放大") 在子目標列表中,每個列表項都有其位置索引值index屬性,表示目標項在列表中的位置。index從0開始,即第一個目標項的索引值為0,第二個目標項的索引值為1,以此類推。此外,clickIndex用來記錄被點擊的目標項索引。當點擊目標一時,clickIndex為0,點擊目標三時,clickIndex為2。 在父組件子目標列表和每個子組件目標項中都擁有clickIndex狀態。當目標一展開時,clickIndex為0。此時點擊目標三,目標三的clickIndex變為2,只要其父組件子目標列表感知到clickIndex狀態變化,同時將此變化傳遞給目標一。目標一的clickIndex即可同步改變為2,即目標一感知到此時點擊了目標三。 **圖9**與父組件雙向同步clickIndex狀態 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114008.93339135166396342543737983202826:50001231000000:2800:A2FE86136BDF79F5E868578F5B13E6B5D869F9507B42FB81B1FB59203FC111EA.png?needInitFileName=true?needInitFileName=true "點擊放大") 將列表和目標項對應到列表組件TargetList和列表項TargetListItem。首先,需要在父組件TargetList中定義clickIndex狀態。 若此時子組件中的clickIndex用@Prop裝飾器修飾,當子組件中clickIndex變化時,父組件無法感知,因為@Prop裝飾器建立的是從父組件到子組件的單向同步關系。 ArkUI提供了@Link裝飾器,用于與父組件雙向同步狀態。當子組件TargetListItem中的clickIndex用@Link修飾,可與父組件TargetList中的clickIndex建立雙向同步關系。 ~~~ @Component export default struct TargetList { @State clickIndex: number = CommonConstants.DEFAULT_CLICK_INDEX; ... TargetListItem({ clickIndex: $clickIndex, ... }) ... } ~~~ 首先,在父組件TargetList中用@State裝飾器定義點擊的目標項索引狀態。然后,在子組件TargetListItem中用@Link裝飾器定義clickIndex,當點擊目標項時,clickIndex更新為當前目標索引值。 完成在父子組件中定義狀態后,最關鍵的就是要建立父子組件的雙向關聯關系。在父組件中使用子組件時,將父組件的clickIndex傳遞給子組件的clickIndex。其中父組件的clickIndex加上$表示傳遞的是引用。 ~~~ @Component export default struct TargetListItem { @Link @Watch('onClickIndexChanged') clickIndex: number; @State isExpanded: boolean = false ... onClickIndexChanged() { if (this.clickIndex != this.index) { this.isExpanded = false; } } build() { ... Column() { ... } .onClick(() => { ... this.clickIndex = this.index; ... }) ... } } ~~~ 當目標一感知到點擊了目標三時,還需要將目標一收起,切換列表項的功能才是完整的。此時,目標一感知到clickIndex變為2,需要判斷與目標一本身的位置索引值0不相等,從而將目標一收起。此時,就需要用到ArkUI中監聽狀態變化@Watch的能力。用@Watch修飾的狀態,當狀態發生變化時,會觸發聲明時定義的回調。 我們給TargetListItem的中的clickIndex狀態加上@Watch("onClickIndexChanged")。這表示需要監聽clickIndex狀態的變化。當clickIndex狀態變化時,將觸發onClickIndexChanged回調:如果點擊的列表項索引不等于當前列表項索引,則將isExpanded狀態置為false,從而收起該目標項。 ## **5.1.5 跨組件層級雙向同步狀態:@Provide和@Consume** ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114008.98879574764239285336519579985124:50001231000000:2800:7FAB420652A38B6314548097054ABC5B9743D9F41C67657F52C5005ED395565E.png?needInitFileName=true?needInitFileName=true) 跨組件層級雙向同步狀態是指@Provide修飾的狀態變量自動對提供者組件的所有后代組件可用,后代組件通過使用@Consume裝飾的變量來獲得對提供的狀態變量的訪問。@Provide作為數據的提供方,可以更新其子孫節點的數據,并觸發頁面渲染。@Consume在感知到@Provide數據的更新后,會觸發當前自定義組件的重新渲染。 使用@Provide的好處是開發者不需要多次將變量在組件間傳遞。@Provide和@Consume的具體使用方法請參見開發指南:[@Provide裝飾器和@Consume裝飾器:與后代組件雙向同步](https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/arkts-provide-and-consume-0000001473857338-V3?catalogVersion=V3)。 ## **5.1.6 參考** 更多狀態管理場景和相關知識請參考開發指南:[狀態管理](https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/arkts-state-management-overview-0000001524537145-V3?catalogVersion=V3)。 ## **5.2 Video組件的使用** ## **5.2.1 概述** 在手機、平板或是智慧屏這些終端設備上,媒體功能可以算作是我們最常用的場景之一。無論是實現音頻的播放、錄制、采集,還是視頻的播放、切換、循環,亦或是相機的預覽、拍照等功能,媒體組件都是必不可少的。以視頻功能為例,在應用開發過程中,我們需要通過ArkUI提供的Video組件為應用增加基礎的視頻播放功能。借助Video組件,我們可以實現視頻的播放功能并控制其播放狀態。常見的視頻播放場景包括觀看網絡上的較為流行的短視頻,也包括查看我們存儲在本地的視頻內容。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114008.58383607879892627431132066000424:50001231000000:2800:E14F8E0A7DB56B8FA1D052EDE49E4D7C3598670D401FD7DFED4686CA3FC308D6.png?needInitFileName=true?needInitFileName=true) 本文將結合《簡易視頻播放器(ArkTS)》這個Codelab,對Video組件的參數、屬性及事件進行介紹,然后通過組件的屬性調用和事件回調闡明Video組件的基本使用方法,最后結合Video組件使用過程中的常見問題講解自定義控制器的使用。 ## **5.2.2 Video組件用法介紹** `Video組件參數介紹` Video組件的接口表達形式為: ~~~ Video(value: {src?: string | Resource, currentProgressRate?: number | string |PlaybackSpeed, previewUri?: string |PixelMap | Resource, controller?: VideoController}) ~~~ 其中包含四個可選參數,src、currentProgressRate、previewUri和controller。 * src表示視頻播放源的路徑,可以支持本地視頻路徑和網絡路徑。使用網絡地址時,如https,需要注意的是需要在module.json5文件中申請網絡權限。在使用本地資源播放時,當使用本地視頻地址我們可以使用媒體庫管理模塊medialibrary來查詢公共媒體庫中的視頻文件,示例代碼如下: ~~~ import mediaLibrary from '@ohos.multimedia.mediaLibrary'; async queryMediaVideo() { let option = { // 根據媒體類型檢索 selections: mediaLibrary.FileKey.MEDIA_TYPE + '=?', // 媒體類型為視頻 selectionArgs: [mediaLibrary.MediaType.VIDEO.toString()] }; let media = mediaLibrary.getMediaLibrary(getContext(this)); // 獲取資源文件 const fetchFileResult = await media.getFileAssets(option); // 以獲取的第一個文件為例獲取視頻地址 let fileAsset = await fetchFileResult.getFirstObject(); this.source = fileAsset.uri } ~~~ 為了方便功能演示,示例中媒體資源需存放在resources下的rawfile文件夾里。 * currentProgressRate表示視頻播放倍速,其參數類型為number,取值支持0.75,1.0,1.25,1.75,2.0,默認值為1.0倍速; * previewUri表示視頻未播放時的預覽圖片路徑; * controller表示視頻控制器。 參數的具體描述如下表: | 參數名| 參數類型| 必填| | :-- | :-- | :-- | | src | string\Resource | 否| | currentProgressRate| number \ string \ PlaybackSpeed8+ | 否| | previewUri | string \PixelMap8+ \ Resource|否| | controller| VideoController| 否 | 說明 視頻支持的規格是:mp4、mkv、webm、TS。 下面我們通過具體的例子來說明參數的使用方法,我們選擇播放本地視頻,視頻未播放時的預覽圖片路徑也為本地,代碼實現如下: ~~~ @Component export struct VideoPlayer { private source: string | Resource; private controller: VideoController; private previewUris: Resource = $r('app.media.preview'); ... build() { Column() { Video({ src: this.source, previewUri: this.previewUris, controller: this.controller }) ... VideoSlider({ controller: this.controller }) } } } ~~~ 效果如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114008.96443894124163118085489349855543:50001231000000:2800:710F1AB15816BC79619840A28E687A50783FE4D62B22C0D0F763E111A4394F2B.png?needInitFileName=true?needInitFileName=true) `Video組件屬性介紹` 除了支持組件的尺寸設置、位置設置等通用屬性外,Video組件還支持是否靜音、是否自動播放、控制欄是否顯示、視頻顯示模式以及單個視頻是否循環播放五個私有屬性。 |名稱|參數類型| 描述| | :-- | :-- | :-- | | muted| boolean| 是否靜音。默認值:false| | autoPlay | boolean| 是否自動播放。默認值:false | | controls| boolean| 控制視頻播放的控制欄是否顯示。默認值:true| | objectFit| ImageFit| 設置視頻顯示模式。默認值:Cover| | loop | boolean | 是否單個視頻循環播放。默認值:false| 其中,objectFit 中視頻顯示模式包括Contain、Cover、Auto、Fill、ScaleDown、None 6種模式,默認情況下使用ImageFit.Cover(保持寬高比進行縮小或者放大,使得圖片兩邊都大于或等于顯示邊界),其他效果(如自適應顯示、保持原有尺寸顯示、不保持寬高比進行縮放等)可以根據具體使用場景/設備來進行選擇。 在Codelab示例中體現了controls、autoplay和loop屬性的配置,示例代碼如下: ``` @Component export struct VideoPlayer { private source: string | Resource; private controller: VideoController; ... build() { Column() { Video({ controller: this.controller }) .controls(false) //不顯示控制欄 .autoPlay(false) // 手動點擊播放 .loop(false) // 關閉循環播放 ... } } } ``` 效果如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114008.22034190109683083044144924925615:50001231000000:2800:F5DC490C05E427658F1904CC94137AF9A131585C401952FEC9370EF0219F32BB.png?needInitFileName=true?needInitFileName=true) ### Video組件回調事件介紹 Video組件能夠支持常規的點擊、觸摸等通用事件,同時也支持onStart、onPause、onFinish、onError等事件,具體事件的功能描述見下表: | 事件名稱 | 功能描述 | | :-- | :-- | | onStart(event:() => void | 播放時觸發該事件。 | | onPause(event:() => void)| 暫停時觸發該事件。 | | onFinish(event:() => void) | 播放結束時觸發該事件。| | onError(event:() => void) | 播放失敗時觸發該事件。| | onPrepared(callback:(event?: { duration: number }) => void)| 視頻準備完成時觸發該事件,通過duration可以獲取視頻時長,單位為s。 | | onSeeking(callback:(event?: { time: number }) => void)| 操作進度條過程時上報時間信息,單位為s。 | | onSeeked(callback:(event?: { time: number }) => void) | 操作進度條完成后,上報播放時間信息,單位為s。| | onUpdate(callback:(event?: { time: number }) => void) |播放進度變化時觸發該事件,單位為s,更新時間間隔為250ms。| | onFullscreenChange(callback:(event?: { fullscreen: boolean }) => void)| 在全屏播放與非全屏播放狀態之間切換時觸發該事件| 在Codelab中我們以更新事件、準備事件、失敗事件以及點擊事件為回調為例進行演示,代碼實現如下: ~~~ Video({ ... }) .onUpdate((event) => { this.currentTime = event.time; this.currentStringTime = changeSliderTime(this.currentTime); //更新事件 }) .onPrepared((event) => { prepared.call(this, event); //準備事件 }) .onError(() => { prompt.showToast({ duration: COMMON_NUM_DURATION, //播放失敗事件 message: MESSAGE }); ... }) ~~~ 其中,onUpdate更新事件在播放進度變化時觸發,從event中可以獲取當前播放進度,從而更新進度條顯示事件,比如視頻播放時間從24秒更新到30秒。onError事件在視頻播放失敗時觸發,在CommonConstants.ets中定義了常量類MESSAGE,所以在視頻播放失敗時會顯示“請檢查網絡”。 ~~~ const MESSAGE: string = '請檢查網絡' ~~~ ## **5.2.2 自定義控制器的組成與實現** `自定義控制器的組成` Video組件的原生控制器樣式相對固定,當我們對頁面的布局色調的一致性有所要求,或者在拖動進度條的同時需要顯示其百分比進度時,原生控制器就無法滿足需要了。如下圖右側的效果需要使用自定義控制器實現,接下來我們看一下自定義控制器的組成。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114008.34725310254114274042500806358448:50001231000000:2800:46143ADEC3EE2106CC5BF83D49527471872A8DB7F6D277CE8BD81ACCF65712EA.png?needInitFileName=true?needInitFileName=true "點擊放大") 為了實現自定義控制器的進度顯示等功能,我們需要通過Row容器實現控制器的整體布局,然后借由Text組件來顯示視頻的播放起始時間、進度時間以及視頻總時長,最后通過滑動進度條Slider組件來實現視頻進度條的效果,代碼如下: ~~~ @Componentexport struct VideoSlider { ... build() { Row(...) { Image(...) Text(...) Slider(...) Text(...) } ... }} ~~~ `自定義控制器的實現` 自定義控制器容器內嵌套了視頻播放時間Text組件、滑動器Slider組件以及視頻總時長Text組件 3個橫向排列的組件,其中Text組件在之前的基礎組件課程中已經有過詳細介紹,這里就不再進行贅述。需要強調的是兩個Text組件顯示的時長是由Slider組件的onChange(callback: (value: number, mode: SliderChangeMode) => void)回調事件來進行傳遞的,而Text組件的數值與視頻播放進度數值value則是通過@Provide與 @Consume裝飾器進行的數據聯動,實現效果可見圖片下方黑色控制欄部分,具體代碼步驟及代碼如下: `獲取/計算視頻時長` ~~~ export function prepared(event) { this.durationTime = event.duration; let second: number = event.duration % COMMON_NUM_MINUTE; let min: number = parseInt((event.duration / COMMON_NUM_MINUTE).toString()); let head = min < COMMON_NUM_DOUBLE ? `${ZERO_STR}${min}` : min; let end = second < COMMON_NUM_DOUBLE ? `${ZERO_STR}${second}` : second; this.durationStringTime = `${head}${SPLIT}${end}`; ... }; ~~~ `設置進度條參數及屬性` ~~~ Slider({ value: this.currentTime, min: 0, max: this.durationTime, step: 1, style: SliderStyle.OutSet }) .blockColor($r('app.color.white')) .width(STRING_PERCENT.SLIDER_WITH) .trackColor(Color.Gray) .selectedColor($r('app.color.white')) .showSteps(true) .showTips(true) .trackThickness(this.isOpacity ? SMALL_TRACK_THICK_NESS : BIG_TRACK_THICK_NESS) .onChange((value: number, mode: SliderChangeMode) => {...}) ~~~ `計算當前進度播放時間及添加onUpdate回調` 最后,在我們播放視頻時還需要更新顯示播放的時間進度,也就是左側的Text組件。在視頻開始播放前,播放時間默認為00:00,隨著視頻播放,時間需要不斷更新為當前進度時間。所以左側的Text組件我們不僅需要讀取時間,還需要為其添加數據聯動。這里,我們就是通過為Video組件添加onUpdate事件來實現的,在視頻播放過程中會不斷調用changeSliderTime方法獲取當前的播放時間并進行計算及單位轉化,從而不斷刷新進度條的值,也就是控制器左側的播放進度時間Text組件。 ~~~ Video({...}) ... .onUpdate((event) => { this.currentTime = event.time; this.currentStringTime = changeSliderTime(this.currentTime) }) ~~~ ~~~ export function changeSliderTime(value: number): string { let second: number = value % COMMON_NUM_MINUTE; let min: number = parseInt((value / COMMON_NUM_MINUTE).toString()); let head = min < COMMON_NUM_DOUBLE ? `${ZERO_STR}${min}` : min; let end = second < COMMON_NUM_DOUBLE ? `${ZERO_STR}${second}` : second; let nowTime = `${head}${SPLIT}${end}`; return nowTime; }; ~~~ `指定視頻播放進度及添加onChange事件回調` 如需手動進行進度條的拖動,則需要在Slider組件中指定播放進度,并為Slider組件添加onChange事件回調。Slider滑動時就會觸發該事件回調,從而實現將視頻定位到進度條當前刷新位置,完成時長組件渲染與視頻播放進度數據聯動。 ~~~ Slider({...}) .onChange((value: number, mode: SliderChangeMode) => { sliderOnchange.call(this, value, mode); }) ~~~ ~~~ export function sliderOnchange(value: number, mode: SliderChangeMode) { this.currentTime = parseInt(value.toString()); this.controller.setCurrentTime(parseInt(value.toString()), SeekMode.Accurate); ... }; ~~~ 到這里我們就實現了自定義控制器的構建,兩個Text組件顯示的時長是由Slider組件的onChange回調事件來進行傳遞的,而Text組件的數值與視頻播放進度數值value則通過是onUpdate與onChange事件并借由@Provide @Consume裝飾器進行的數據聯動。 ## **5.2.3 參考鏈接** * Video組件的更多屬性和參數的使用,可以參考API:[Video](https://developer.harmonyos.com/cn/docs/documentation/doc-references-V3/ts-media-components-video-0000001427902484-V3?catalogVersion=V3)。 * ## **5.3 給您的應用添加彈窗** ## **5.3.1 概述** 在我們日常使用應用的時候,可能會進行一些敏感的操作,比如刪除聯系人,這時候我們給應用添加彈窗來提示用戶是否需要執行該操作,如下圖所示: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114009.81266630475599466259176175375297:50001231000000:2800:F5A0952D592407AC3AFAB2CF7F41409D389C190B0F8EAD9E55B9429C1DE13213.png?needInitFileName=true?needInitFileName=true "點擊放大") 彈窗是一種模態窗口,通常用來展示用戶當前需要的或用戶必須關注的信息或操作。在彈出框消失之前,用戶無法操作其他界面內容。ArkUI為我們提供了豐富的彈窗功能,彈窗按照功能可以分為以下兩類: * 確認類:例如警告彈窗AlertDialog。 * 選擇類:包括文本選擇彈窗TextPickerDialog 、日期滑動選擇彈窗DatePickerDialog、時間滑動選擇彈窗TimePickerDialog等。 您可以根據業務場景,選擇不同類型的彈窗。部分彈窗效果圖如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114009.02006314208798124153612926629630:50001231000000:2800:ABE98C27C05EC327B72237D9B573DEC9A7C1510CFD86C77DF61153F1EBBF765D.png?needInitFileName=true?needInitFileName=true "點擊放大") 此外,如果上述彈窗還不能滿足您的需求,或者需要對彈窗的布局和樣式進行自定義,您還可以使用自定義彈窗CustomDialog。 下文將分別介紹AlertDialog 、TextPickerDialog 、DatePickerDialog以及CustomDialog的使用。 ## **5.3.2 警告彈窗** 警告彈窗AlertDialog由以下三部分區域構成,對應下面的示意圖: 1. 標題區:為可選的。 2. 內容區:顯示提示消息。 3. 操作按鈕區:用戶做”確認“或者”取消“等操作。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114009.08583160946403332888990227475910:50001231000000:2800:20C90F4598DE791028199ADD260C7B89388EC4F22911B6F75643884907D8AB82.png?needInitFileName=true?needInitFileName=true "點擊放大") 以下示例代碼,演示了如何使用AlertDialog 實現上圖所示的警告彈窗。AlertDialog可以設置兩個操作按鈕,示例代碼中分別使用primaryButton和secondaryButton實現了“取消”和“刪除”操作按鈕,操作按鈕可以通過action響應點擊事件。 ~~~ Button('點擊顯示彈窗') .onClick(() => { AlertDialog.show( { title: '刪除聯系人', // 標題 message: '是否需要刪除所選聯系人?', // 內容 autoCancel: false, // 點擊遮障層時,是否關閉彈窗。 alignment: DialogAlignment.Bottom, // 彈窗在豎直方向的對齊方式 offset: { dx: 0, dy: -20 }, // 彈窗相對alignment位置的偏移量 primaryButton: { value: '取消', action: () => { console.info('Callback when the first button is clicked'); } }, secondaryButton: { value: '刪除', fontColor: '#D94838', action: () => { console.info('Callback when the second button is clicked'); } }, cancel: () => { // 點擊遮障層關閉dialog時的回調 console.info('Closed callbacks'); } } ) }) ~~~ 此外,您還可以使用AlertDialog,構建只包含一個操作按鈕的確認彈窗,使用confirm響應操作按鈕回調。 ~~~ AlertDialog.show( { title: '提示', message: '提示信息', autoCancel: true, alignment: DialogAlignment.Bottom, offset: { dx: 0, dy: -20 }, confirm: { value: '確認', action: () => { console.info('Callback when confirm button is clicked'); } }, cancel: () => { console.info('Closed callbacks') } } ) ~~~ ## **5.3.3 選擇類彈窗** 選擇類彈窗用于方便用戶選擇相關數據,比如選擇喜歡吃的水果、出生日期等等。下面我們以TextPickerDialog和DatePickerDialog為例,來介紹選擇類彈窗的使用。 `文本選擇彈窗` TextPickerDialog為文本滑動選擇器彈窗,根據指定的選擇范圍創建文本選擇器,展示在彈窗上,例如下面這段示例代碼使用TextPickerDialog實現了一個水果選擇彈窗。示例代碼中使用selected指定了彈窗的初始選擇項索引為2,對應的數據為“香蕉”。當用戶點擊“確定”操作按鈕后,會觸發onAccept事件回調,在回調中將選中的值,傳遞給宿主中的select變量。 ~~~ @Entry @Component struct TextPickerDialogDemo { @State select: number = 2; private fruits: string[] = ['蘋果', '橘子', '香蕉', '獼猴桃', '西瓜']; build() { Column() { Button('TextPickerDialog') .margin(20) .onClick(() => { TextPickerDialog.show({ range: this.fruits, // 設置文本選擇器的選擇范圍 selected: this.select, // 設置初始選中項的索引值。 onAccept: (value: TextPickerResult) => { // 點擊彈窗中的“確定”按鈕時觸發該回調。 // 設置select為按下確定按鈕時候的選中項index,這樣當彈窗再次彈出時顯示選中的是上一次確定的選項 this.select = value.index; console.info("TextPickerDialog:onAccept()" + JSON.stringify(value)); }, onCancel: () => { // 點擊彈窗中的“取消”按鈕時觸發該回調。 console.info("TextPickerDialog:onCancel()"); }, onChange: (value: TextPickerResult) => { // 滑動彈窗中的選擇器使當前選中項改變時觸發該回調。 console.info('TextPickerDialog:onChange()' + JSON.stringify(value)); } }) }) } .width('100%') } } ~~~ 效果圖如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114009.06203127778312855358092695067169:50001231000000:2800:3CCD9ED5BE2B0AE318165A94B62BFD39360CD682734333DD41CAA028AB3A709A.png?needInitFileName=true?needInitFileName=true) `日期選擇彈窗` 下面我們介紹另一種常用的選擇類彈窗DatePickerDialog,它是日期滑動選擇器彈窗,根據指定的日期范圍創建日期滑動選擇器,展示在彈窗上。DatePickerDialog的使用非常廣泛,比如當我們需要輸入個人出生日期的時候,就可以使用DatePickerDialog。下面的示例代碼實現了一個日期選擇彈窗: ~~~ @Entry @Component struct DatePickerDialogDemo { selectedDate: Date = new Date('2010-1-1'); build() { Column() { Button("DatePickerDialog") .margin(20) .onClick(() => { DatePickerDialog.show({ start: new Date('1900-1-1'), // 設置選擇器的起始日期 end: new Date('2023-12-31'), // 設置選擇器的結束日期 selected: this.selectedDate, // 設置當前選中的日期 lunar: false, onAccept: (value: DatePickerResult) => { // 點擊彈窗中的“確定”按鈕時觸發該回調 // 通過Date的setFullYear方法設置按下確定按鈕時的日期,這樣當彈窗再次彈出時顯示選中的是上一次確定的日期 this.selectedDate.setFullYear(value.year, value.month, value.day) console.info('DatePickerDialog:onAccept()' + JSON.stringify(value)) }, onCancel: () => { // 點擊彈窗中的“取消”按鈕時觸發該回調 console.info('DatePickerDialog:onCancel()') }, onChange: (value: DatePickerResult) => { // 滑動彈窗中的滑動選擇器使當前選中項改變時觸發該回調 console.info('DatePickerDialog:onChange()' + JSON.stringify(value)) } }) }) } .width('100%') } } ~~~ 效果圖如下: ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114009.43114074270197359477544761452334:50001231000000:2800:262CAFA928F19AFA4C1E24FB0B2AEEDFA63FAC09A2B94A91C49CCF5A1B87524C.png?needInitFileName=true?needInitFileName=true) ## **5.3.4 自定義彈窗** 自定義彈窗的使用更加靈活,適用于更多的業務場景,在自定義彈窗中您可以自定義彈窗內容,構建更加豐富的彈窗界面。自定義彈窗的界面可以通過裝飾器@CustomDialog定義的組件來實現,然后結合CustomDialogController來控制自定義彈窗的顯示和隱藏。下面我們通過一個興趣愛好的選擇框來介紹自定義彈窗的使用。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20231219114009.14616045610526211938768708679974:50001231000000:2800:BF9222E4BBD64C060BEDECA9B48C58808806FC3B2FE10588E88BB1E1542F0C78.png?needInitFileName=true?needInitFileName=true) 從上面的效果圖可以看出,這個選擇框是一個多選的列表彈窗,我們可以使用裝飾器@CustomDialog,結合List組件來完成這個彈窗布局,實現步驟如下: 1. 初始化彈窗數據。 先準備好資源文件和數據實體類。其中資源文件stringarray.json創建在resources/base/element目錄下,文件根節點為strarray。 ~~~ { "strarray": [ { "name": "hobbies_data", "value": [ { "value": "Soccer" }, { "value": "Badminton" }, { "value": "Travelling" }, ... ] } ] } ~~~ 實體類HobbyBean用來封裝自定義彈窗中的"興趣愛好"數據。 ``` export default class HobbyBean { label: string; isChecked: boolean; } ``` 然后創建一個ArkTS文件CustomDialogWidget,用來封裝自定義彈窗,使用裝飾器@CustomDialog修飾CustomDialogWidget表示這是一個自定義彈窗。使用資源管理對象manager獲取數據,并將數據封裝到hobbyBeans。 ~~~ @CustomDialog export default struct CustomDialogWidget { @State hobbyBeans: HobbyBean[] = []; aboutToAppear() { let context: Context = getContext(this); let manager = context.resourceManager; manager.getStringArrayValue($r('app.strarray.hobbies_data'), (error, hobbyResult) => { ... hobbyResult.forEach((hobbyItem: string) => { let hobbyBean = new HobbyBean(); hobbyBean.label = hobbyItem; hobbyBean.isChecked = false; this.hobbyBeans.push(hobbyBean); }); }); } build() {...} } ~~~ 2. 創建彈窗組件。 controller對象用于控制彈窗的控制和隱藏,hobbies表示彈窗選中的數據結果。setHobbiesValue方法用于篩選出被選中的數據,賦值給hobbies。 ~~~ @CustomDialog export default struct CustomDialogWidget { @State hobbyBeans: HobbyBean[] = []; @Link hobbies: string; private controller?: CustomDialogController; aboutToAppear() {...} setHobbiesValue(hobbyBeans: HobbyBean[]) { let hobbiesText: string = ''; hobbiesText = hobbyBeans.filter((isCheckItem: HobbyBean) => isCheckItem?.isChecked) .map((checkedItem: HobbyBean) => { return checkedItem.label; }).join(','); this.hobbies = hobbiesText; } build() { Column() { Text($r('app.string.text_title_hobbies'))... List() { ForEach(this.hobbyBeans, (itemHobby: HobbyBean) => { ListItem() { Row() { Text(itemHobby.label)... Toggle({ type: ToggleType.Checkbox, isOn: false })... .onChange((isCheck) => { itemHobby.isChecked = isCheck; }) } } }, itemHobby => itemHobby.label) } Row() { Button($r('app.string.cancel_button'))... .onClick(() => { this.controller?.close(); }) Button($r('app.string.definite_button'))... .onClick(() => { this.setHobbiesValue(this.hobbyBeans); this.controller?.close(); }) } } } } ~~~ 3. 使用自定義彈窗。 在自定義彈窗的使用頁面HomePage中先定義一個變量hobbies,使用裝飾器@State修飾,和自定義彈窗中的@Link 裝飾器修飾的變量進行雙向綁定。然后我們使用alignment和offset設置彈窗的位置在屏幕底部,并且距離底部20vp。最后我們在自定義組件TextCommonWidget(具體實現可以參考《構建多種樣式彈窗》Codelab源碼)的點擊事件中,調用customDialogController的open方法,用于顯示彈窗。 ~~~ @Entry @Component struct HomePage { customDialogController: CustomDialogController = new CustomDialogController({ builder: CustomDialogWidget({ onConfirm: this.setHobbiesValue.bind(this), }), alignment: DialogAlignment.Bottom, customStyle: true, offset: { dx: 0,dy: -20 } }); setHobbiesValue(hobbyArray: HobbyBean[]) {...} build() { ... TextCommonWidget({ ... title: $r('app.string.title_hobbies'), content: $hobby, onItemClick: () => { this.customDialogController.open(); } }) ... } } ~~~ ## **5.3.5 參考** 關于更多彈窗,您可以參考: [警告彈窗](https://developer.harmonyos.com/cn/docs/documentation/doc-references-V3/ts-methods-alert-dialog-box-0000001478341185-V3?catalogVersion=V3) [列表選擇彈窗](https://developer.harmonyos.com/cn/docs/documentation/doc-references-V3/ts-methods-action-sheet-0000001478061737-V3?catalogVersion=V3) [自定義彈窗](https://developer.harmonyos.com/cn/docs/documentation/doc-references-V3/ts-methods-custom-dialog-box-0000001477981237-V3?catalogVersion=V3) [日期滑動選擇彈窗](https://developer.harmonyos.com/cn/docs/documentation/doc-references-V3/ts-methods-datepicker-dialog-0000001427902500-V3?catalogVersion=V3) [時間滑動選擇彈窗](https://developer.harmonyos.com/cn/docs/documentation/doc-references-V3/ts-methods-timepicker-dialog-0000001428061780-V3?catalogVersion=V3) [文本滑動選擇彈窗](https://developer.harmonyos.com/cn/docs/documentation/doc-references-V3/ts-methods-textpicker-dialog-0000001427584912-V3?catalogVersion=V3) ## **六、給應用添加動畫** ## **闖關習題** 屬性動畫中產生動畫的屬性可以在任意位置聲明。錯誤 屬性動畫中改變屬性時需觸發UI狀態更新。正確 屬性animation可以在哪些組件中使用?基礎組件和容器組件 屬性動畫中如何設置反向播放?PlayMode.Reverse 下面哪種情況不會回調onFinish函數?iterations設置為 -1 屬性動畫中關于animation參數說法錯誤的是?參數delay不能大于duration 屬性動畫支持哪些屬性?width、rotate、opacity、scale 屬性動畫中animation的參數有哪些?playMode、curve、delay、onFinish ## **6.1 屬性動畫的使用** ## **6.1.1 概述** 屬性動畫,是最為基礎的動畫,其功能強大、使用場景多,應用范圍較廣。常用于如下場景中: * 一、頁面布局發生變化。例如添加、刪除部分組件元素。 * 二、頁面元素的可見性和位置發生變化。例如顯示或者隱藏部分元素,或者將部分元素從一端移動到另外一端。 * 三、頁面中圖形圖片元素動起來。例如使頁面中的靜態圖片動起來。 簡單來說,屬性動畫是組件的通用屬性發生改變時而產生的屬性漸變效果。如下圖所示,其原理是,當組件的通用屬性發生改變時,組件狀態由初始狀態逐漸變為結束狀態的過程中,會創建多個連續的中間狀態,逐幀播放后,就會形成屬性漸變效果,從而形成動畫。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102140125.30776267042224696432056505610220:50001231000000:2800:C687D4A94C174B70FF789C69919B0E052A06867898F4D3EC21A1FD70AA858E4B.png) 屬性動畫的使用方式也非常簡單,只需要給組件(包括基礎組件和容器組件)添加animation屬性,并設置好參數,如下代碼所示: ~~~ Image($r('app.media.image1')) .animation({ duration: 1000, tempo: 1.0, delay: 0, curve: Curve.Linear, playMode: PlayMode.Normal, iterations: 1 }) ~~~ ## **6.1.2?創建屬性動畫頁面** 如下圖所示,在該下拉刷新動畫場景中,一共有6個屬性動畫。頭部中的五個圖標的移動放大動畫中,每個圖標都是單獨的一個動畫,其共同組合成一個刷新等待動畫。最后是下方組件上移的一個移動動畫。為方便理解,圖中下方的內容將以圖片來代替實際應用的功能頁面。 圖2-1?**:示例動畫** ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221103195705.09518356622801048800456761033244:50001231000000:2800:70089B858FC528F98930773EBB3F12369CC19C7ADBD5551FF3DDB4E6E342186B.gif) 該6個屬性動畫創建方式類似,以五個圖標放大移動動畫的為例來講解如何創建屬性動畫。 首先,創建一個頭部刷新組件RefreshAnimHeader,在其中自定義一個圖標組件AttrAnimIcons,用Image組件將資源圖標引入,并設置好樣式,如下所示: ~~~ @Component export default struct RefreshAnimHeader { ... @Builder AttrAnimIcons(iconItem) { Image(iconItem.imgRes) .width(this.iconWidth) .position({ x: iconItem.posX }) .objectFit(ImageFit.Contain) .animation({ duration: 2000, tempo: 3.0, delay: iconItem.delay, curve: Curve.Linear, playMode: PlayMode.Alternate, iterations: -1 }) } ... } ~~~ 然后在build方法中使用Row容器組件,將自定義的圖標組件引入,并設置好樣式,同時定義組件狀態iconWidth,添加onApper事件,修改iconWidth的值,使其從30變為100,觸發UI狀態更新。 ~~~ @Component export default struct RefreshAnimHeader { ... @State iconWidth: number = 30; private onStateCheck() { if (this.state === RefreshState.REFRESHING) { this.iconWidth = 100; } else { this.iconWidth = 30; } } build() { Row() { ForEach(CommonConstants.REFRESH_HEADER_FEATURE, (iconItem) => { this.AttrAnimIcons(iconItem) }, item => item.toString()) } .width("100%") .height("100%") .onAppear(() => { this.onStateCheck(); }) } } ~~~ 運行代碼,即可看到五個圖標的移動放大動畫效果。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102140215.95436691968376446727824039360503:50001231000000:2800:9FAA7180774BC8A094CAA362FE8B05632C7B132E77DAD1B04C5BCE60C447EE8C.png) 1、animation屬性作用域。animation自身也是組件的一個屬性,其作用域為animation之前。即產生屬性動畫的屬性須在animation之前聲明,其后聲明的將不會產生屬性動畫。以示例中的五個圖標動畫為例,我們期望產生動畫的屬性為Image組件的width屬性,故該屬性width需在animation屬性之前聲明。如果將該屬性width在animation之后聲明,則不會產生動畫效果。 2、產生屬性動畫的屬性變化時需觸發UI狀態更新。在本示例中,產生動畫的屬性width,其值是通過變量iconWidth從30變為100,故該變量iconWidth的改變需觸發UI狀態更新。 3、產生屬性動畫的屬性本身需滿足一定的要求,并非任何屬性都可以產生屬性動畫。目前支持的屬性包括width、height、position、opacity、backgroundColor、scale、rotate、translate等 ## **6.1.3?屬性動畫參數調整** 屬性動畫中animation的參數如下: |屬性名稱| 屬性類型| 默認值 | 描述| | --- | --- | --- | --- | | duration| number | 1000| 動畫時長,單位為毫秒,默認時長為1000毫秒。| | temp | number| 1.0 | 動畫的播放速度,值越大動畫播放越快,值越小播放越慢,為0時無動畫效果。| | curve| Curve| Curve.Linear |動畫變化曲線,默認曲線為線性。| | delay| number | 0 | 延時播放時間,單位為毫秒,默認不延時播放。| | iterations | number | 1| 播放次數,默認一次,設置為-1時表示無限次播放。| |playMode| PlayMode| PlayMode.Normal | 設置動畫播放模式,默認播放完成后重頭開始播放。| |onFinish| function | -| 動畫播放結束時回調該函數。| 其中變化曲線curve枚舉值為: | 名稱| 描述 | | --- | --- | | Linear| 表示動畫從頭到尾的速度都是相同的。| | Ease| 表示動畫以低速開始,然后加快,在結束前變慢,CubicBezier(0.25, 0.1, 0.25, 1.0)。| | EaseIn| 表示動畫以低速開始,CubicBezier(0.42, 0.0, 1.0, 1.0)。 | | EaseOut|表示動畫以低速結束,CubicBezier(0.0, 0.0, 0.58, 1.0)。 | | EaseInOut| 表示動畫以低速開始和結束,CubicBezier(0.42, 0.0, 0.58, 1.0)。 | | FastOutSlowIn| 標準曲線,cubic-bezier(0.4, 0.0, 0.2, 1.0)。| | LinearOutSlowIn| 減速曲線,cubic-bezier(0.0, 0.0, 0.2, 1.0)| |FastOutLinearIn| 加速曲線,cubic-bezier(0.4, 0.0, 1.0, 1.0)。| | ExtremeDeceleration| 急緩曲線,cubic-bezier(0.0, 0.0, 0.0, 1.0)。 | | Sharp| 銳利曲線,cubic-bezier(0.33, 0.0, 0.67, 1.0)。| | Rhythm|節奏曲線,cubic-bezier(0.7, 0.0, 0.2, 1.0)。| |Smooth| 平滑曲線,cubic-bezier(0.4, 0.0, 0.4, 1.0)。 | | Friction| 阻尼曲線,CubicBezier(0.2, 0.0, 0.2, 1.0)。| 播放模式playMode枚舉值為: | 名稱| 描述| | --- | --- | | Normal| 動畫按正常播放。| | Reverse| 動畫反向播放。| | Alternate| 動畫在奇數次(1、3、5...)正向播放,在偶數次(2、4、6...)反向播放。| | AlternateReverse| 動畫在奇數次(1、3、5...)反向播放,在偶數次(2、4、6...)正向播放。 | 本文以參數delay和onFinish為例來演示和講解屬性動畫的參數調整。其他參數的效果可自行嘗試。 `延時播放時間delay的設置` 在單個的組件元素的屬性動畫中,一般不設置參數delay的值。而在需要設置時,往往是需要在動畫開始前做一些準備工作,具體依場景而定,本文在此不討論。 在由多個組件元素的屬性動畫組合的動畫中,例如示例動畫中的五個圖標的屬性動畫組合而成的刷新等待動畫,通過設置參數delay的值,可以使各個組件元素之間按照一定的秩序依次播放,形成跌宕起伏、鱗次櫛比的動畫效果。在此場景中,該值的大小又與duration相關聯。 該如何設置各個圖標的參數delay的值呢? 在設置delay值之前,我們先理解一個概念:延時間距。其意思是兩個圖標組件的延時參數delay的差值,即:delay2-delay1=延時間距。要想實現五個圖標之間以良好的秩序先后移動放大,各個圖標之間的延時間距是一樣的,例如延時間距為100ms時,此五個圖標的延時delay可以分別設置為100ms、200ms、300ms、400ms、500ms。 在實際開發場景中,我們該如何確定延時間距呢? 在此有個經驗可以參考:在延時間距不超過動畫時長duration時,總延時間距越接近duration,秩序性越好。其中,總延時間距為延時間距與圖標數量的乘積,即:延時間距\*圖標數量=總延時間距。 故此,我們在設置參數delay時,需要確定動畫時長duration的值。該值默認為1000ms,具體時長可依據具體的業務需要來決定。 在本示例動畫中,圖標動畫時長duration為2000ms,故延時間距為2000ms/5=400ms,五個圖標的延時參數delay可分別設置為400ms、800ms、1200ms、1600ms、2000ms。其效果如示例圖所示,圖標先后秩序明顯,視覺效果良好。 `onFinish回調函數的使用` 參數onFinish與參數iterations有關。當參數iterations播放結束時,會調用onFinish函數來進行后續的業務處理。例如提示動畫播放結束。 ~~~ Image(iconItem.imgRes) .width(this.iconWidth) .position({ x: iconItem.posX }) .objectFit(ImageFit.Contain) .animation({ duration: 2000, tempo: 3.0, delay: iconItem.delay, curve: Curve.Linear, playMode: PlayMode.Normal, iterations: 1, onFinish: () => { prompt.showToast({ message:"動畫播放結束!!!" }) } }) ~~~ ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102140234.85135546535521901605966886990679:50001231000000:2800:53FDBD1A0F28CE8FAEBCBD2EB3C0A91B543FDEE3D48E0C557D1B60F051883528.png) 當iterations設置為-1時,表示無限次播放,則onFinish回調函數不會被調用。 ## **6.1.4?關閉屬性動畫頁面** 此處需要將關閉屬性動畫區別開來: * 屬性動畫關閉,是指動畫播放結束,但是動畫組件依然存在并顯示在頁面上。 * 關閉屬性動畫頁面,是指將動畫的組件刪除或者隱藏起來。 在本示例動畫中,指將頭部刷新組件RefreshAnimHeader隱藏起來。該如何實現呢? 首先,在組件RefreshAnimHeader中添加變量state,并用@Consume監聽其值的變化,同時添加條件渲染邏輯,根據state的值來判斷是否需要關閉。當state變為IDLE狀態時,表示需要關閉屬性動畫頁面。 ~~~ @Component export default struct RefreshAnimHeader { @Consume(RefreshConstants.REFRESH_STATE_TAG) @Watch('onStateCheck') state: RefreshState; build() { Row() { if (this.state !== RefreshState.IDLE) { // start or stop animation when idle state. ForEach(CommonConstants.REFRESH_HEADER_FEATURE, (iconItem) => { this.AttrAnimIcons(iconItem) }, item => item.toString()} } } .width(CommonConstants.FULL_LENGTH) .height(CommonConstants.FULL_LENGTH) .onAppear(() => { this.onStateCheck(); }) } } ~~~ 其次,在本示例中,通過下方圖片的上移屬性動畫來關閉刷新組件RefreshAnimHeader。在組件RefreshComponent中,通過@Consume與組件RefreshAnimHeader的@Consume進行間接綁定,修改state變量的值為IDLE狀態即可關閉屬性動畫頁面。 ~~~ @Component export default struct RefreshComponent { @Consume(RefreshConstants.REFRESH_STATE_TAG) @Watch('onStateChanged') state: RefreshState; build() { List({ scroller: this.listController }) { ListItem() { ... } } .animation({ curve: Curve.Smooth, duration: RefreshConstants.REFRESH_HEADER_ANIM_DURATION, playMode: PlayMode.Normal, onFinish: () => { if (this.headerOffset === -RefreshConstants.REFRESH_HEADER_HEIGHT) { this.state = RefreshState.IDLE; } } }) } ~~~ ## **6.1.5?參考** 具體代碼信息請參考Codelab:自定義下拉刷新動畫(ArkTS) ## **七、HarmonyOS第一課:從網絡獲取數據** ## **7.1 Web組件的使用** ## **7.1.1 概述** 相信大家都遇到過這樣的場景,有時候我們點擊應用的頁面,會跳轉到一個類似瀏覽器加載的頁面,加載完成后,才顯示這個頁面的具體內容,這個加載和顯示網頁的過程通常都是瀏覽器的任務。 ArkUI為我們提供了Web組件來加載網頁,借助它我們就相當于在自己的應用程序里嵌入一個瀏覽器,從而非常輕松地展示各種各樣的網頁。 ![](https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtyPub/103/404/958/0260086000103404958.20221102130140.00495404175221314986597538603428:50001231000000:2800:B4214971A06540516367B23A49156D31CA6D59512F73173BAD42BC0CF819E3A2.png) 本文將為您介紹Web組件一些常用API的使用。 ## **7.1.2 加載網頁**
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看