作者:王石,胡瑞濤
上節回顧
在#跟着小白一起學鴻蒙# [四]移植開源庫節我們學習了在OpenHarmony下如何移植編譯運作一個開源庫,接下來我們來熟悉下hap應用的結構架構和如何自己開發一個簡單的hap程式。
簡介
HAP檔案是在OpenHarmony系統下編譯生成的可執行檔案。HAP 包是由代碼、資源、第三方庫以及應用配置檔案打包生成的子產品包,主要分為兩種類型:entry 和 feature。
entry:應用的主子產品,作為 OpenHarmony 應用的入口,提供了應用的基礎功能。
feature:應用的動态特性子產品,作為應用能力的擴充,可以根據使用者的需求和裝置的類型進行選擇性安裝。
OpenHarmony 使用者應用程式包可以隻包含一個基礎的 entry 包,也可以包含一個基礎的 entry 包和一個或多個功能型的 feature 包。
工具
- DevEco Studio 3.0 Beta4
- HUAWEI DevEco Studio For OpenHarmony是基于IntelliJ IDEA Community開源版本打造,面向OpenHarmony全場景多裝置的一站式內建開發環境(IDE),DevEco Studio 3.0支援在HarmonyOS 3.0 Beta版上開發應用及服務,并已适配ArkUI聲明式程式設計範式、ArkCompiler方舟編譯,同時提供低代碼開發、雙向預覽、全新建構工具、模拟器、調試調優、資訊中心等功能,為開發者提供工程模闆建立、開發、編譯、調試、釋出等E2E的OpenHarmony應用/服務開發。
- 下載下傳連結: HUAWEI DevEco Studio和SDK下載下傳和更新 | HarmonyOS開發者
-
安裝步驟: 下載下傳與安裝軟體-快速開始-HUAWEI DevEco Studio For OpenHarmony使用指南-工具-HarmonyOS應用開發 | HarmonyOS
注:建議在選項界面都勾選DevEco Studio,Add “bin” folder to the PATH,Add "Open Folder as Project"
- 配置開發環境:配置開發環境-快速開始-HUAWEI DevEco Studio For OpenHarmony使用指南-工具-HarmonyOS應用開發 | HarmonyOS
ETS
- 基于TS擴充的聲明式開發範式的方舟開發架構是一套開發極簡、高性能、跨裝置應用的UI開發架構,支援開發者高效的建構跨裝置應用UI界面。
- 基于TS擴充的聲明式開發範式提供了一系列基礎元件,這些元件以聲明方式進行組合和擴充來描述應用程式的UI界面,并且還提供了基本的資料綁定和事件處理機制,幫助開發者實作應用互動邏輯。
@Entry //用@Entry裝飾的自定義元件用作頁面的預設入口元件,也可以了解為頁面的根節點。 一個頁面有且僅能有一個@Entry,隻有被@Entry修飾的元件或者其子元件,才會在頁面上顯示。 @Component //@Component裝飾的struct表示該結構體具有元件化能力,能夠成為一個獨立的元件,這種類型的元件也稱為自定義元件,在build方法裡描述UI結構。 struct Hello { //在聲明式UI中,所有的頁面都是由元件構成。元件的資料結構為struct @State myText: string = 'World' build() { //build函數用于定義元件的聲明式UI描述,在build方法中以聲明式方式進行組合自定義元件或系統内置元件。 Column() { //Column:沿垂直方向布局的容器。 Text('Hello') //Text:顯示一段文本的元件。 .fontSize(30) Text(this.myText) .fontSize(32) Divider() //Divider:提供分隔器元件,分隔不同内容塊/内容元素。 Button() { //Button:按鈕元件,可快速建立不同樣式的按鈕,通常用于響應使用者的點選操作。 Text('Click me') .fontColor(Color.Red) }.onClick(() => { this.myText = 'UI' }) .width(500) .height(200) } } }
基本概念
- 裝飾器:方舟開發架構定義了一些具有特殊含義的裝飾器,用于裝飾類、結構、方法和變量。裝飾器就是某一種修飾,給被裝飾的對象賦予某一種能力,比如@Entry就是頁面入口的能力,@Component就是元件化能力。
- 自定義元件:可重用的UI單元,可以與其他元件組合,如@Component裝飾的struct Hello。
- UI描述:聲明性描述UI結構,例如build()方法中的代碼塊。
- 内置元件:架構中預設内置的基本元件和布局元件,開發者可以直接調用,僅用于解釋UI描述規範。如Column、Text、Divider、Button等。
- 屬性方法:用于配置元件屬性,如fontSize()、width()、height()、color()等。
- 事件方法:在事件方法的回調中添加元件響應邏輯。例如,為Button元件添加onClick方法,在onClick方法的回調中添加點選響應邏輯。
應用
1. 建立
- 在DevEco Studio的歡迎頁,選擇Create Project開始建立一個新工程。
- 根據工程建立向導,在OpenHarmony頁簽,選擇“Empty Ability”模闆,點選Next。

- 點選Next,各個參數保持預設值即可,點選Finish。
關于各個參數的詳細介紹,請參考建立OpenHarmony工程-工程管理-HUAWEI DevEco Studio For OpenHarmony使用指南-工具-HarmonyOS應用開發 | HarmonyOS
- Project name:工程的名稱,可以自定義。
- Project type:工程的類型,辨別該工程是一個傳統方式的需要安裝的應用(Application)或原子化服務(Atomic service),預設類型為Application。
- Bundle name:軟體包名稱,預設情況下,應用ID也會使用該名稱,應用釋出時,應用ID需要唯一。如果“Project type”選擇了Atomic service,則Bundle name的字尾名必須是.hmservice。
- Save location:工程檔案本地存儲路徑。
- **Compile SDK:**編譯的SDK版本。
- Model:選擇FA模型或Stage模型(Stage模型僅Compile API為9及以上支援)。
- Enable Super Visual:選擇開發模式,部分模闆支援低代碼開發,可選擇打開該開關。
- **Language:**開發語言。
- Compatible SDK:相容的SDK最低版本。
- Device type:該工程模闆支援的裝置類型。
- **Show in service center:**是否在服務中心露出。
- 工程建立完成後,DevEco Studio會自動進行工程的同步,同步成功如下圖所示。
- 将搭載OpenHarmony标準系統的開發闆與電腦連接配接。
- 點選File > Project Structure > Project > Signing Configs界面勾選“Automatically generate signature”,等待自動簽名完成即可,點選“OK”。如下圖所示:
- 為了保證OpenHarmony應用的完整性和來源可靠,在應用建構時需要對應用進行簽名。經過簽名的應用才能在真機裝置上安裝、運作、和調試。如果沒有配置簽名,會報錯:hvigor WARN: Will skip sign ‘hap’,Invalid signingConfig is configured for 'default' product.
2. 目錄結構
- 新項目的目錄結構 entry為應用的主子產品,類似與Android Studio的app子產品,一個APP中,對于同一裝置類型必須有且隻有一個entry類型的HAP,可獨立安裝運作。
#沖刺創作新星# #跟着小白一起學鴻蒙#[六]第一個hap應用 -
使用previewer檢視程式的預覽圖
點選View > Tool Windows > Project > Previewer 如下圖所示:
- 方法一:
- 方法二:
- 成功預覽後會生成.preview結構:
-
使用build編譯程式
點選Build > Rebuild 進行編譯; Build > Build Hap(s) /App(s) >Build Hap(s) 生成hap檔案
- hap檔案的生成路徑:entry/build/outpus/default
-
目錄結構中檔案分類如下:
.ets結尾的eTS(extended TypeScript)檔案,用于描述UI布局、樣式、事件互動和頁面邏輯。
各個檔案夾和檔案的作用:
- app.ets檔案用于全局應用邏輯和應用生命周期管理。
- pages目錄用于存放所有元件頁面。
- common目錄用于存放公共代碼檔案,比如:自定義元件和公共方法。
說明:
- 資源目錄resources檔案夾位于src/main下,此目錄下資源檔案的詳細規範以及子目錄結構規範參看資源檔案的分類。
- 頁面支援導入TypeScript和JavaScript檔案。
3. 基礎元件應用
- 如上所說,基于TS擴充的聲明式開發範式提供了一系列基礎元件,如:基礎元件,容器元件,媒體元件,繪制元件和畫布元件等,本節我們主要使用容器元件。
- list清單包含一系列相同寬度的清單項。适合連續、多行呈現同類資料,例如圖檔和文本。
- listitem用來展示清單具體item,寬度預設充滿List元件,必須配合List來使用。
-
text可以顯示一段文本。
編寫一個簡單的ui界面,其homepage為:
import ConfigData from '../../Utils/ConfigData'; import {SubEntryComponent} from '../../component/subEntryComponent'; @Entry @Component struct HomePage { @State message: string = 'The Test' build() { Column(){ GridContainer({columns:12, sizeType: SizeType.Auto, gutter: vp2px(1) === 2 ? '12vp' : '0vp', margin: vp2px(1) === 2 ? '24vp' : '0vp'}) { Row({}) { Column() {} .width(ConfigData.WH_100_100) .height(ConfigData.WH_100_100) .useSizeType({ xs: { span: 0, offset: 0 }, sm: { span: 0, offset: 0 }, md: { span: 0, offset: 0 }, lg: { span: 2, offset: 0 } }); Column() { Text(this.message) .fontSize($r("app.float.font_30")) .lineHeight($r("app.float.lineHeight_41")) .fontWeight(FontWeight.Bold) .fontFamily('HarmonyHeiTi') .textAlign(TextAlign.Start) .width(ConfigData.WH_100_100) .padding({left: $r('app.float.distance_26'), top: $r('app.float.distance_12'), bottom: $r('app.float.distance_17')}) Column({space:'16vp'}){ List(){ ListItem(){ SubEntryComponent({targetPage:"pages/test1",title:$r("app.string.test1")}) } .padding({top: $r('app.float.distance_2'), bottom: $r('app.float.distance_2')}) ListItem(){ SubEntryComponent({targetPage:"pages/test2",title:$r("app.string.test2")}) } .padding({top: $r('app.float.distance_2'), bottom: $r('app.float.distance_2')}) ListItem(){ SubEntryComponent({targetPage:"pages/test3",title:$r("app.string.test3")}) } .padding({top: $r('app.float.distance_2'), bottom: $r('app.float.distance_2')}) ListItem(){ SubEntryComponent({targetPage:"pages/test4",title:$r("app.string.test4")}) } .padding({top: $r('app.float.distance_2'), bottom: $r('app.float.distance_2')}) } } .width(ConfigData.WH_100_100) } .padding({left: $r('app.float.distance_24'), right: $r('app.float.distance_24')}) .width(ConfigData.WH_100_100) .height(ConfigData.WH_100_100) .useSizeType({ xs: { span: 12, offset: 0 }, sm: { span: 12, offset: 0 }, md: { span: 12, offset: 0 }, lg: { span: 8, offset: 2 } }); Column() {} .width(ConfigData.WH_100_100) .height(ConfigData.WH_100_100) .useSizeType({ xs: { span: 0, offset: 12 }, sm: { span: 0, offset: 12 }, md: { span: 0, offset: 12 }, lg: { span: 2, offset: 10 } }) } .width(ConfigData.WH_100_100) .height(ConfigData.WH_100_100); } .width(ConfigData.WH_100_100) .height(ConfigData.WH_100_100); } .backgroundColor($r("sys.color.ohos_id_color_sub_background")) .width(ConfigData.WH_100_100) .height(ConfigData.WH_100_100); } }
- 調用的subEntryComponent為:
import ConfigData from '../Utils/ConfigData'; @Component export struct SubEntryComponent{ private targetPage: string; private title:string | Resource; @State isTouched:boolean = false; private date: any = null; private deviceId: any = null; build() { Navigator({target: this.targetPage}){ Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems:ItemAlign.Center }) { Row() { Text(this.title) .fontSize($r('app.float.font_30')) .lineHeight($r('app.float.wh_value_32')) .fontColor($r('app.color.font_color_182431')) .fontWeight(FontWeight.Medium) .margin({left: $r('app.float.distance_8')}) .textAlign(TextAlign.Start) .height($r('app.float.wh_value_32')); } Image(('app.media.ic_settings_arrow')) .width($r('app.float.wh_value_12')) .height($r('app.float.wh_value_100')) .margin({right: $r('app.float.distance_8')}); } .height(ConfigData.WH_100_100) .width(ConfigData.WH_100_100) .borderRadius($r('app.float.radius_12')) .linearGradient(this.isTouched ? { angle: 90, direction: GradientDirection.Right, colors: [[$r("app.color.DCEAF9"), 0.0], [$r("app.color.FAFAFA"), 1.0]] } : { angle: 90, direction: GradientDirection.Right, colors: [[$r("sys.color.ohos_id_color_foreground_contrary"), 1], [$r("sys.color.ohos_id_color_foreground_contrary"), 1]]}) .onTouch((event: TouchEvent) => { if (event.type === TouchType.Down) { this.isTouched = true; } if (event.type === TouchType.Up) { this.isTouched = false; } }) } .params( {date: this.date, deviceId: this.deviceId} ) .padding($r('app.float.distance_4')) .height($r('app.float.wh_value_100')) .borderRadius($r('app.float.radius_24')) .backgroundColor($r("sys.color.ohos_id_color_foreground_contrary")); } }
- 預覽圖:
4. 界面跳轉
在實作界面跳轉時一般需要用到targetPage或者router:url方法。
- targetpage方法
targetpage:"{頁面路徑}"
- router方法
定義: const 'NAME' = '路徑'; 實作: Router.push({ uri: PAGE_URI_TEST_NAME });
-
js标簽配置
開發架構需要應用的config.json中配置相關的js标簽,其中包含了執行個體名稱、頁面路由、視圖視窗配置資訊。pages定義每個頁面入口元件的路由資訊,每個頁面由頁面路徑和頁面名組成,頁面的檔案名就是頁面名。比如:
說明:
- pages清單中第一個頁面為應用的首頁入口。
- 頁面檔案名不能使用元件名稱,比如:Text.ets、Button.ets等。
- 每個頁面檔案中必須包含頁面入口元件(@Entry裝飾)。
- 預覽圖:
5. api元件
- 在基于TS擴充的聲明式開發範式中有衆多的API參考,初學者可以使用這些元件練習,以此來加深對ets的熟悉。如:
// 提供在給定範圍内選擇評分的元件 @Entry @Component struct RatingExample { @State rating: number = 1 @State indicator: boolean = false build() { Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { Text('current score is ' + this.rating).fontSize(20) Rating({ rating: this.rating, indicator: this.indicator }) .stars(5) .stepSize(0.5) .onChange((value: number) => { this.rating = value }) }.width(350).height(200).padding(35) } }
- 預覽圖:
6. 自定義元件
元件的成員變量可以通過兩種方式初始化:
- 本地初始化,如:
@State counter: Counter = new Counter() ts
- 在構造元件時通過構造參數初始化,如:
MyComponent({counter: $myCounter})
如可以使用CustomDialogController類顯示自定義彈窗
- 導入對象
dialogController : CustomDialogController = new CustomDialogController(value:{builder: CustomDialog, cancel?: () => void, autoCancel?: boolean})
-
使用open()和close()對其進行開關顯示。
open(): void
顯示自定義彈窗内容,若已顯示,則不生效。
close(): void
關閉顯示的自定義彈窗,若已關閉,則不生效。
- 示例:
class ClassA { public a:number constructor(a: number) { this.a = a } } @Entry @Component struct Parent { @State parentState: ClassA = new ClassA(1) build() { Column() { Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { CompA({ aState: new ClassA(2), aLink: $parentState }) } Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { CompA({ aLink: $parentState }) } Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { CompA({ aState: new ClassA(3), aLink: $parentState }) } } } } @Component struct CompA { @State aState: any = false @Link aLink: ClassA build() { Column() { CompB({ bLink: $aLink, bProp: this.aState }) CompB({ bLink: $aState, bProp: false }) } } } @Component struct CompB { @Link bLink: ClassA @Prop bProp: boolean build() { Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { Text(JSON.stringify(this.bLink.a)).fontSize(30) Text(JSON.stringify(this.bProp)).fontSize(30).fontColor(Color.Red) }.margin(10) } }
GITEE路徑:
https://gitee.com/wshikh/ohosexample.git