0x01 概要敘述
(1)鴻蒙系統
- 鴻蒙是華為公司開發的作業系統,在多端使用
- 以手機為中心,包括手錶、平板等
- “萬物互聯”思想
- 各類應用間接為使用者帶來作業系統的用途
- “鴻蒙應用千帆起,輕舟已過萬重山”
(2)準備工作
a. 語言
- 鴻蒙系統應用的開發語言:ArkTS
- 是 TypeScript 的超集
- 統一了程式設計體驗
- ArkTS 包括 HTML、CSS、JavaScript
區別:ArkTS 是語言,ArkUI 是框架
b. 工具
開發工具:DevEco Studio
- 在官網下載安裝包並開啟
- 遇到多選可以全選
- 安裝完成後不需要重啟
- Basic Setup 中全部使用 Install
- 如果本地存在 Node.js、Ohpm,可以選擇 Local 並定位到相關目錄
- 建議使用 Install 安裝官方推薦版本
- Ohpm(Open Harmony Package Management)是開放鴻蒙包管理器
- SDK Setup 中全部選擇 Accept
- 選擇 Create Project 建立專案
- 選擇 Empty Ability
- 配置專案資訊
- Project name:專案名稱
- Bundle name:應用上線的唯一標識
- 公司域名翻轉與應用名稱,如 com.example.application
- Save location:專案儲存路徑
- Compile SDK:編譯開發工具包
- Model:選擇模型(FA 是鴻蒙開發早期模型)
- Device type:選擇應用適配裝置
c. 專案目錄
- app:應用模組
- entry:入口模組
- 在一個專案中可能會包含多個模組,但 entry 是唯一的主模組
- AppScope:應用全域性配置
- 各個模組可以共享的配置
- src:開發的原始碼目錄
- main:主目錄
- ohosTest:測試目錄
- EntryAbility.ts:當前模組入口檔案
- pages:頁面目錄
- Index.ets:主頁面,每個頁面的檔案的字尾為 ets
- resources:資源目錄,包括文字、圖片、音影片等;還包括國際化相關功能子目錄,如 en-US、zh-CN
- module.json5:當前模組配置檔案
d. 頁面程式碼結構
-
一個應用中往往包含多個頁面
-
一個頁面就是一個結構描述
- 關鍵字
struct
用於描述一個自定義元件,名稱與檔名相同,使用駝峰命名法 - 頁面是元件,而元件不一定是頁面,
- 一個頁面可以拆分成多個元件,提高元件複用率(元件化)
- 關鍵字
-
一個元件中必須包含以下內容:
build()
,用於構建元件 UI 介面,其中:- 一般編寫 ArkTS 提供的內建元件
- 只能包含一個根元件
@Component
/@CustomDialog
,元件裝飾器/自定義對話方塊裝飾器
-
@Entry
,該裝飾器可以將元件作為單獨頁面在 Preview 中進行預覽 -
@State
,該裝飾器作用於元件的內部變數,當變數修改後(資料監視),頁面會自動重新渲染;宣告時必須初始化 -
元件可以不斷進行巢狀
build() { Row() { Column() { Row() { // ... } } } }
-
元件是一個封裝好的物件,包括屬性(樣式)、方法(事件)
build() { Column() { Text("Item 1") Text("Item 2") Text("Item 3") }.width(300) }
300
是虛擬畫素,根據螢幕換算- 對於列容器,預設寬高由內容決定
-
舉例:
@Entry @Component struct Index { @State message: string = 'Hello World' build() { Row() { Column() { Text(this.message) .fontSize(50) .fontWeight(FontWeight.Bold) } .width('100%') } .height('100%') } }
e. 除錯
- 工具中的 Preview 提供單頁面預覽功能
- 工具中的 Device Manager 中允許連線並使用模擬真機進行除錯
(3)常用樣式
a. 文字樣式
-
fontSize
:字號Text("Hello, world!").fontSize(60)
-
fontColor
:文字顏色Text("Hello, world!").fontColor(Color.Red) // 或 Text("Hello, world!").fontColor("#f00")
-
fontWeight
:字重Text("Hello, world!").fontWeight(FontWeight.Bold) // 或 Text("Hello, world!").fontWeight(800)
-
fontStyle
:字樣Text("Hello, world!").fontStyle(FontStyle.Normal) // 常規 Text("Hello, world!").fontStyle(FontStyle.Italic) // 斜體
-
fontFamily
:字型 -
textAlign
:對齊方向Text("Hello, world!") .width("100%") .textAlign(TextAlign.Start)
- 必須指定寬度後,才可以設定對齊方向
-
lineHeight
:行高Text("Hello, world!").lineHeight(300)
-
decoration
:劃線Text("Hello, world!").decoration({type: TextDecorationType.Underline}) // 下劃線 Text("Hello, world!").decoration({type: TextDecorationType.Overline}) // 上劃線 Text("Hello, world!").decoration({type: TextDecorationType.LineThrough}) // 刪除線
b. 背景樣式
backgroundColor
:背景顏色backgroundImage
:背景圖片
c. 盒子模型
-
width
:寬度 -
height
:高度 -
padding
:內邊距Text("Hello, world!").padding({top:10})
-
border
:邊框Text("Hello, world!").border({style: BorderStyle.Solid, color: Color.Red, radius: 50})
-
margin
:外邊距Text("Hello, world!").margin(10)
-
列間距(行間距同理)
Column({space: 16}) {}
(4)常用事件
-
事件三要素:事件源、事件型別、事件處理
- 事件處理推薦使用箭頭函式:
(引數列表) => 函式體
,方便訪問元件內其他屬性與方法
Button("Click").onClick(()=>{ console.log("Log") })
- 事件處理推薦使用箭頭函式:
-
可以使用
bind
將this
繫結到普通函式中@Entry @Component struct Index { text: string = "This is a piece of text." handle() { console.log(this.text); } build() { Column() { Button("Click").onClick(this.handle.bind(this)) } .width("100%") .height("100%") } }
0x02 頁面設計
(1)ArkUI 常用內建元件
a. Text 文字元件
-
語法:
Text(content?: string | Resource)
-
長文字最大行數與省略顯示
Text("This is a long long sentence.") .width(100) .maxLines(1) .textOverflow({overflow:TextOverflow.Ellipsis})
-
國際化
-
src/main/resources/base/element/string.json
{ "string": [ { "name": "username", "value": "使用者名稱" }, { "name": "password", "value": "密碼" } ] }
-
src/main/resources/en_US/element/string.json
{ "string": [ { "name": "username", "value": "username" }, { "name": "password", "value": "password" } ] }
-
src/main/resources/zh_CN/element/string.json
{ "string": [ { "name": "username", "value": "使用者名稱" }, { "name": "password", "value": "密碼" } ] }
-
Index.ets
Column() { Row() { Text($r('app.string.username')) .fontSize(50) } Row() { Text($r('app.string.password')) .fontSize(50) } } .width('100%') .height('100%')
-
b. TextInput 輸入框元件
-
語法:
TextInput(value?:{placeholder?: ResourceStr, text?: ResourceStr, controller?: TextInputController})
-
登入表單
Column({space: 20}) { Row() { Text($r('app.string.username')) .fontSize(22) .width("20%") TextInput({placeholder: "輸入賬號"}) .width("70%") } Row() { Text($r('app.string.password')) .fontSize(22) .width("20%") TextInput({placeholder: "輸入密碼"}) .width("70%") .type(InputType.Password) } }
c. Button 按鈕元件
-
語法:
Button(options?: {type?: ButtonType, stateEffect?: boolean})
-
登入表單按鈕組
Row({ space: 20 }) { Button($r('app.string.login')) .fontSize(22) Button($r('app.string.reset')) .fontSize(22) .type(ButtonType.Normal) }
-
完善登入頁面
@Entry @Component struct Index { @State username: string = "" @State password: string = "" build() { Column({space: 20}) { Row() { Text("登入 Login") } Row() { Text($r('app.string.username')) TextInput({placeholder: "輸入賬號", text: this.username}) .onChange(content => this.username = content) } Row() { Text($r('app.string.password')) TextInput({placeholder: "輸入密碼", text: this.password}) .type(InputType.Password) .onChange(content => this.password = content) } Row({ space: 20 }) { Button($r('app.string.login')) .onClick(() => { console.log("username:" + this.username) console.log("password:" + this.password) }) Button($r('app.string.reset')) .onClick(() => { this.username = "" this.password = "" }) } } } }
d. Blank 空白元件
-
語法:
Blank(min?: number | string)
-
佔據父容器中剩餘空間
-
調整表單對齊
Column({ space: 20 }) { Row() { Text("Item1") Blank() TextInput() .width(200) } .width("80%") Row() { Text("Item2") Blank() TextInput() .width(200) } .width("80%") } .width('100%') .height('100%')
e. Image 圖片元件
-
語法:
Image(src: string | PixelMap | Resource)
-
可以渲染與展示本地圖片和網路圖片
Column({ space: 20 }) { Image($r('app.media.logo')) .width("50%") Image("https://developer.huawei.com/allianceCmsResource/resource/HUAWEI_Developer_VUE/images/HW-LOGO.svg") .width("50%") } .width('100%') .height('100%')
f. Slider 滑塊元件
-
語法:
Slider(options?: {value?: number, min?: number, max?: number, step?: number, style?: SliderStyle, direction?: Axis, reverse?: boolean})
-
舉例
Column({ space: 20 }) { Slider({ min: 0, // 最小值 max: 20, // 最大值 value: this.value, // 當前值 step: 2, // 步長 style: SliderStyle.InSet // 樣式 }) .trackColor(Color.Red) // 軌道顏色 .selectedColor(Color.Pink) // 選中顏色 .trackThickness(9) // 軌道厚度 .onChange(value => this.value = value) Text(this.value.toString()) } .width('100%') .height('100%') .backgroundColor("#ccc")
-
圖片尺寸設定案例:
@Entry @Component struct Index { @State widthValue:number = 100 minWidth:number = 50 maxWidth:number = 340 build() { Column({ space: 20 }) { Text("圖片尺寸設定") Row() { Image($r("app.media.image")) .width(this.widthValue) } Row() { Text("圖片寬度 ") TextInput({ text: parseInt(this.widthValue.toFixed(0)).toString() }) .onChange(value => this.widthValue = widthValue) } Row() { Button("縮小").onClick(() => this.widthValue -= 1) Button("放大").onClick(() => this.widthValue += 1) } Slider({ min: this.minWidth, max: this.maxWidth, value: this.widthValue, step: 1 }) .onChange(value => this.widthValue = value) } } }
完整程式碼:https://gitee.com/srigt/harmony/blob/master/圖片寬度自定義
g. List 列表元件
-
語法:
List(value?:{space?: number | string, initialIndex?: number, scroller?: Scroller})
-
其子元件只能是
ListItem(value?: string)
或ListItemGroup(options?: {header?: CustomBuilder, footer?: CustomBuilder, space?: number | string})
- 在
ListItem
中可以使用其他元件 ListItem
元件的swipeAction()
支援側滑手勢,其中傳入元件用於設定側滑的內容
- 在
-
舉例 1:電商平臺商品列表
import router from '@ohos.router' interface IProduct { id: number, imageURL: string, name: string, price: number, discounted?: number } @Entry @Component struct Page { titleBgColor: string = "#fafafa" contentBgColor: string = "#eee" products: Array<IProduct> = [ { id: 1, imageURL: "", name: "Product 1", price: 7599, discounted: 500 } ] build() { Column() { Row() { Button() { Image($r('app.media.arrow')) } .onClick(() => { router.back() }) Text("商品列表") Blank() Button() { Image($r('app.media.refresh')) } .onClick(() => { console.log("Refresh") }) } .backgroundColor(this.titleBgColor) List({ space: 20 }) { ForEach(this.products, (item) => { ListItem() { Row() { Image(item.imageURL) Column({ space: 10 }) { Text(item.name) if(item.discounted) { Text("價格:¥" + item.price) .fontColor("#aaa") .decoration({ type: TextDecorationType.LineThrough}) Text("折後價:¥" + (item.price - item.discounted)) Text("優惠:¥" + item.discounted) } else { Text("價格:¥" + item.price) } } .layoutWeight(1) } } .border({ width: 2, style: BorderStyle.Solid, color: this.contentBgColor, radius: 20 }) }) } } .backgroundColor(this.contentBgColor) } }
-
舉例 2:通訊錄
interface IAddressItem { group: string, contactList: string[] } @Entry @Component struct Index { addressBook: IAddressItem[] = [ { group: "家人", contactList: ["張三", "李四"] }, { group: "朋友", contactList: ["王五", "趙六"] }, { group: "同事", contactList: ["田七"] } ] @Builder groupHeader(group: string) { Text(group) .fontSize(30) .fontWeight(FontWeight.Bold) } build() { Column() { Text("通訊錄") .fontSize(50) .fontWeight(FontWeight.Bolder) List({ space: 20 }) { ForEach(this.addressBook, (item:IAddressItem) => { ListItemGroup({ header: this.groupHeader(item.group) }) ForEach(item.contactList, (item:string) => { ListItem() { Text(item) .fontSize(20) } }) }) } } .width("100%") .height('100%') .padding({ left: 10, right: 10 }) } }
h. 自定義對話方塊
-
構建自定義對話方塊元件
@CustomDialog struct MyDialog { controller: CustomDialogController build() { Column() { Text("自定義對話方塊") Button("關閉對話方塊") .onClick(() => { this.controller.close() }) } } }
-
將對話方塊元件註冊到頁面中
@Entry @Component struct Index { controller: CustomDialogController = new CustomDialogController({ builder: MyDialog({}) }) }
-
繫結點選事件觸發對話方塊
build() { Column() { Button("開啟對話方塊") .onClick(() => { this.controller.open() }) } .width('100%') .height('100%') }
i. 自定義導航
-
語法:
Tabs(value?: {barPosition?: BarPosition, index?: number, controller?: TabsController})
-
舉例:
@Component struct AComponent { build() { Text("A 元件內容") } } @Component struct BComponent { build() { Text("B 元件內容") } } @Component struct CComponent { build() { Text("C 元件內容") } } @Entry @Component struct Index { @State currentIndex: number = 0 @Builder customTabBarContent(icon: Resource, title: string, index: number) { Column({ space: 6 }) { Image(icon) .width(20) .fillColor(this.currentIndex == index ? Color.Green : Color.Black) Text(title) .fontSize(16) .fontColor(this.currentIndex == index ? Color.Green : Color.Black) } } build() { Column() { Tabs() { TabContent() { AComponent() }.tabBar(this.customTabBarContent($r("app.media.icon"), "A 元件", 0)) TabContent() { BComponent() }.tabBar(this.customTabBarContent($r("app.media.icon"), "B 元件", 1)) TabContent() { CComponent() }.tabBar(this.customTabBarContent($r("app.media.icon"), "C 元件", 2)) } .barPosition(BarPosition.End) .vertical(false) // 不使用垂直佈局 .scrollable(false) // 關閉頁面滑動切換 .onChange((index: number) => { this.currentIndex = index }) } .width("100%") .height("100%") } }
(2)元件化開發
- 元件化:將整個頁面分割為多個部分,並使用單獨的元件描述每個部分,使得一個頁面由多個元件構成
a. @Builder 自定義構建函式
-
構建函式中只能寫入元件
-
語法:
@Builder 函式名(引數) { 函式體; }
-
自定義構建函式可以在
build()
中呼叫 -
完善電商平臺商品列表
@Builder header() { Row() { Button() { Image($r('app.media.arrow')) } .backgroundColor(this.titleBgColor) .onClick(() => { router.back() }) Text("商品列表") Blank() Button() { Image($r('app.media.refresh')) } .backgroundColor(this.titleBgColor) .onClick(() => { console.log("Refresh") }) } .backgroundColor(this.titleBgColor) } @Builder productCard(item:IProduct) { Row() { Image(item.imageURL) Column({ space: 10 }) { Text(item.name) if(item.discounted) { Text("價格:¥" + item.price) .fontColor("#aaa") .decoration({ type: TextDecorationType.LineThrough}) Text("折後價:¥" + (item.price - item.discounted)) Text("優惠:¥" + item.discounted) } else { Text("價格:¥" + item.price) } } } } build() { Column() { this.header() List({ space: 20 }) { ForEach(this.products, (item) => { ListItem() { this.productCard(item) } .border({ width: 2, style: BorderStyle.Solid, color: this.contentBgColor, radius: 20 }) }, (item:IProduct) => { return item.id.toString() }) } } .backgroundColor(this.contentBgColor) }
b. @Component 自定義元件
-
自定義構建函式僅能在當前元件中使用,無法複用到其他元件,因此需要自定義元件
-
一般寫在 ets/components 目錄下
-
完善電商平臺商品列表
-
components/Header.ets
import router from '@ohos.router' @Component export default struct Header { title: string = "Undefined" titleBgColor: string = "#fafafa" contentBgColor: string = "#eee" build() { Row() { Button() { Image($r('app.media.arrow')) } .backgroundColor(this.titleBgColor) .onClick(() => { router.back() }) Text(this.title) Blank() Button() { Image($r('app.media.refresh')) } .backgroundColor(this.titleBgColor) .onClick(() => { console.log("Refresh") }) } .backgroundColor(this.titleBgColor) } }
-
entry/Page.ets
build() { Column() { Header({ title: "商品列表" }) // ... } }
-
-
自定義元件使用成本更高,但複用性更強,且其中資料獨立
c. @BuilderParam 構建引數
-
將自定義構建函式作為引數傳遞到自定義元件
-
完善電商平臺商品列表
-
components/Header.ets
@Component export default struct Header { // ... @BuilderParam rightItem: () => void build() { Row() { // ... this.rightItem() } } }
-
entry/Page.ets
import Header from '../components/Header' @Entry @Component struct Page { // ... @Builder refreshButton() { Button() { Image($r('app.media.refresh')) } .onClick(() => { console.log("Refresh") }) } build() { Column() { Header({ title: "商品列表", rightItem: this.refreshButton }) // ... } } }
完整程式碼:https://gitee.com/srigt/harmony/tree/master/商品列表
-
(3)頁面佈局
a. 線性佈局
Row
:行佈局,從左至右- 主軸:從左至右
- 側軸(交叉軸):從上至下
Column
:列布局,從上至下- 主軸:從上至下
- 側軸(交叉軸):從左至右
- 主軸使用
justifyContent(FlexAlign.*)
調整對齊,FlexAlign
列舉包括:Start
:從開始處(預設)Cneter
:居中End
:從結束處SpaceBetween
:均分且開始和結束處不留空間SpaceAround
:均分且間隔比為 \(0.5:1:1:\ \ldots\ :1:0.5\)SpaceEvenly
:均分且間隔空間相同
- 側軸使用
aligmItems
調整對齊,分為:VerticalAlign
:Row
行佈局,其列舉包括:Top
:從頂部Center
:居中(預設)Bottom
:從底部
HorizontalAlign
:Column
列布局,其列舉包括:Start
:從開始處Center
:居中(預設)End
:從結束處
layoutWeight
:填充父容器主軸方向的空閒空間
b. 層疊佈局
-
子元件按照順序依次入棧,後一個子元件覆蓋前一個子元件
-
語法:
Stack(value?: { alignContent?: Alignment })
-
舉例:
@Entry @Component struct Index { build() { Stack({}) { Column() {} .width('100%') .height('100%') .backgroundColor(Color.Red) Row() {} .width("50%") .height("50%") .backgroundColor(Color.Green) } } }
c. 網格佈局
-
由行列分割的單元格所組成,透過指定專案所在的單元格完成佈局
-
語法:
Grid(scroller?: Scroller)
-
類似 List 元件,Grid 要求其中每一項的子元件包含在
GridItem
中 -
常用屬性:
rowsTemplate()
:行模板,設定每行的模板,包括列數與列寬columnsTemplate()
:列模板,設定每列的模板,包括行數與行寬rowsGap()
:行間距columnsGap()
:列間距
-
舉例:
@Entry @Component struct Page { array: number[] = [1, 2, 3, 4, 5, 6] build() { Grid() { ForEach(this.array, (item: number) => { GridItem() { Text(item.toString()) .width("100%") .height(100) .border({ width: 2, color: Color.Black }) .fontSize(30) } }) } .width("100%") .height(220) .rowsTemplate("1fr 1fr") .columnsTemplate("1fr 1fr 1fr") .rowsGap(10) .columnsGap(10) } }
(4)資料請求
-
一般資料請求步驟
-
匯入對應模組
import http from '@ohos.net.http';
-
在方法中,建立 HTTP 請求物件
import http from '@ohos.net.http'; @Component struct Index { httpHandler() { let httpRequest = http.createHttp() } // ... }
-
呼叫
request(url, options)
傳送請求httpHandler() { let httpRequest = http.createHttp() let promise = httpRequest.request( "http://example.com", { method: http.RequestMethod.GET } ) }
url
為請求地址、options
為請求配置- 一個 HTTP 請求物件僅能呼叫一次
request()
方法
-
獲取響應結果
httpHandler() { let httpRequest = http.createHttp() let promise = httpRequest.request( "http://example.com", { method: http.RequestMethod.GET } ) promise.then( (httpResponse:http.HttpResponse) => { console.log('Result: ' + httpResponse.result.toString()); } ) }
-
-
預設採用非同步方式請求
-
可以使用
async
與await
變為同步Button("登入") .fontSize(22) .onClick(async () => { let httpRequest = http.createHttp() let response = await httpRequest.request( `http://10.200.21.163:8080/login?username=${this.username}&password=${this.password}`, { method: http.RequestMethod.GET } ) console.log(response.result.toString()) })
-
-
完善登入頁面
Button("登入") .fontSize(22) .onClick(() => { let httpRequest = http.createHttp() let promise = httpRequest.request( `http://localhost:8080/login?username=${this.username}&password=${this.password}`, { method: http.RequestMethod.GET } ) promise.then((httpResponse:http.HttpResponse) => { console.log(httpResponse.result.toString()) }) })
(5)動畫效果
-
透過設定關鍵幀實現動畫效果
-
使用
animateTo(value: AnimateParam, event: () => void): void
方法value
:物件型別,用於配置動畫引數,包括延時、變化曲線等event
:回撥函式,用於配置動畫關鍵幀的資料
-
舉例:
@Entry @Component struct Index { @State scaleX: number = 0 @State scaleY: number = 0 build() { Column({ space: 30 }) { Button("開始動畫") .margin(30) .onClick(() => { animateTo({ duration: 500 }, () => { this.scaleX = 1 this.scaleY = 1 }) }) Row() .width(200) .height(200) .backgroundColor(Color.Red) .scale({ x: this.scaleX, y: this.scaleY }) } .width("100%") .height("100%") } }
0x03 渲染控制
(1)條件渲染
-
使用
if
、else
和else if
語句 -
if
、else if
後跟隨的條件語句可以使用狀態變數 -
調整登入按鈕
Button() { Row() { if(this.isLoading) { LoadingProgress() .width(30) .color(Color.White) } else { Text("登入") .fontSize(22) .fontColor(Color.White) } } }
(2)迴圈渲染
-
使用
ForEach
語句 -
語法:
ForEach( arr: Array, itemGenerator: (item: any, index: number) => void, keyGenerator?: (item: any, index: number) => string )
arr
:陣列,陣列包含多個元素,陣列長度決定元件渲染個數itemGenerator
:子元件生成函式,用於生成頁面元件,引數分別為陣列每項的值與索引keyGenerator
:(可選)鍵值生成函式,用於指定每項的id:string
,引數分別為陣列每項的值與索引
-
舉例:
@Entry @Component struct Index { students:string[] = ["Alex", "Bob", "Charles", "David"] build() { Column() { ForEach(this.students, (item:string, index:number) => { Row() { Text(index.toString()) .fontSize(50) Blank() Text(item) .fontSize(50) } .width("80%") }) } .width('100%') .height('100%') } }
(3)資料懶載入
-
使用
LazyForEach
語句 -
語法:
LazyForEach( dataSource: IDataSource, itemGenerator: (item: any, index: number) => void, keyGenerator?: (item: any, index: number) => string ): void
-
用法與
ForEach
類似,其中資料來源為IDataSource
interface IDataSource { totalCount(): number; getData(index: number): Object; registerDataChangeListener(listener: DataChangeListener): void; unregisterDataChangeListener(listener: DataChangeListener): void; }
totalCount
:獲得資料總數getData
:獲取索引值對應的資料registerDataChangeListener
:註冊資料改變的監聽器unregisterDataChangeListener
:登出資料改變的監聽器
0x04 狀態管理
(1)@State
-
狀態:元件中的需要
@State
修飾器修飾的資料 -
特點:狀態資料會透過宣告式 UI 元件的方式展示到頁面中,並且資料的變化會被 ArkUI 底層實時監控
-
如果
@State
裝飾的變數是物件,則 ArkUI 會監視物件和其中屬性值的變化
(2)@Observed 與 @ObjectLink
-
如果屬性值是物件,且該物件的值發生了變化,則可以使用以下方法監視:
-
重新
new
一個物件 -
使用
@Observed
搭配@ObjectLink
@Observed class Car { name: string price: number constructor(name: string, price: number) { this.name = name this.price = price } } class Person { name: string car: Car constructor(name: string, car: Car) { this.name = name this.car = car } } @Component struct CarInfo { @ObjectLink car: Car build() { Text(`車名:${this.car.name}\n車價:${this.car.price}`) } } @Entry @Component struct Index { person: Person = new Person("張三", new Car("智界S7", 210000)) build() { Column() { Text(`姓名:${this.person.name}`) CarInfo({ car: this.person.car }) Button("車價減 1000") .onClick(() => { this.person.car.price -= 1000 }) } } }
-
-
如果傳遞方法,需要繫結
this
-
舉例:待辦列表
@Observed class TodoItem {} @Component struct TodoComponent { @ObjectLink item: TodoItem index: number remove: (index: number) => void customSize: number build() { Row() { Button() { Image($r("app.media.todo")) .width(this.customSize) } Text(this.item.name) Blank() Button() { Image($r('app.media.remove')) .width(this.customSize) } .onClick(() => { this.remove(this.index) }) } } } @Entry @Component struct Index { @State TodoList: TodoItem[] = [] customSize: number = 25 newItemName: string = "" remove(index: number) { this.TodoList.splice(index, 1) } @Builder Header() {} build() { Column({ space: 20 }) { Text("待辦列表") this.Header() List({ space: 16 }) { ForEach(this.TodoList, (item: TodoItem, index: number) => { ListItem() { TodoComponent({ customSize: this.customSize, item: item, index: index, remove: this.remove.bind(this) }) } }) } } } }
完整程式碼:https://gitee.com/srigt/harmony/tree/master/待辦列表
-
(3)@Prop
-
@Prop
專門用於處理父子元件的之間單向的資料傳遞- 子元件資料變化不會影響父元件
-
舉例:
@Component struct Child { @Prop message: string build() { Text(`Child: ${this.message}`) .fontSize(20) } } @Entry @Component struct Parent { @State message: string = 'Hello World' build() { Column({ space: 30 }) { Text(`Parent: ${this.message}`) .fontSize(20) .onClick(() => { this.message = 'Changed' }) Child({ message: this.message }) } .width('100%') .height('100%') } }
- 當觸發點選事件後,父元件
message
的值發生了變化,在@Prop
的作用下,子元件也隨著變化重新渲染
- 當觸發點選事件後,父元件
-
區別於
@State
,@Prop
不需要在子元件初始化,而是等待來自父元件的資料 -
@Prop
只能裝飾簡單型別的屬性 -
@Prop
的原理是:將父元件的屬性值複製一份到子元件
(4)@Link
-
@Link
與@Prop
作用相同- 相同:都專門用於處理父子元件的之間的資料傳遞
- 不同:
@Link
可以雙向資料傳遞,並且可以裝飾任何型別的屬性
-
舉例:
@Component struct Child { @Link message: string build() { Text(`Child: ${this.message}`) .fontSize(20) .onClick(() => { this.message = 'Changed' }) } } @Entry @Component struct Parent { @State message: string = 'Hello World' build() { Column({ space: 30 }) { Text(`Parent: ${this.message}`) .fontSize(20) Child({ message: $message }) } .width('100%') .height('100%') } }
- 當觸發點選事件後,子元件
message
的值發生了變化,在@Link
的作用下,父元件也隨著變化重新渲染
- 當觸發點選事件後,子元件
-
如果在子元件使用
@Link
,則父元件傳遞時,需要使用$
,如子元件({ 子元件屬性名: $父元件屬性名 })
-
@Link
的原理是,將父元件屬性的地址值傳遞給子元件
(5)@Provide 與 @Consume
-
@Provide
與@Consume
搭配使用,實現任意元件之間雙向的資料傳遞 -
採用隱式資料傳遞
- 提供方元件僅負責提供資料,而不指定目標元件
- 消費方元件直接消費來自提供方元件的資料
-
提供方可以為資料配置別名
-
舉例:
@Component struct Grandchild { @Consume msg: string build() { Text(`Grandchild: ${this.msg}`) .fontSize(20) .onClick(() => { this.msg = 'Change from grandchild' }) } } @Component struct Child { build() { Grandchild() } } @Entry @Component struct Parent { @Provide('msg') message: string = 'Hello World' build() { Column({ space: 30 }) { Text(`Parent: ${this.message}`) .fontSize(20) .onClick(() => { this.message = 'Change from parent' }) Child() } .width('100%') .height('100%') } }
-
此方法對效能有所損耗(缺點)
(6)@Watch 監視器
-
監視物件是元件中的資料,當資料發生改變,監視器就會觸發相應的方法
-
舉例:
@Entry @Component struct Index { @State @Watch('calcTotal') array: number[] = [0, 1, 2, 3] @State total: number = 0 calcTotal(): void { this.total = 0 this.array.forEach(element => this.total += element); } aboutToAppear() { this.calcTotal() } build() { Column() { Text(`陣列全部元素和為:${this.total}`) Button("向陣列新增元素") .onClick(() => { this.array.push(10) }) } .width("100%") .height('100%') } }
0x05 頁面路由
(1)概念
- 路由:一種實現在一個應用程式中頁面之間相互跳轉與資料傳遞的技術
- 頁面棧:一個類似棧的頁面容器,當前頁面為棧底頁面
- 為防止頁面棧溢位,頁面棧中最多存放 32 個頁面
(2)使用步驟
-
在 src/main/resources/base/profile/main_pages.json 中註冊路由
{ "src": [ "pages/A", "pages/B" ] }
-
在頁面中引入路由模組:
import router from '@ohos.router'
-
在 A 頁面呼叫方法:
pushUrl(options: RouterOptions): Promise<void>
-
其中,
RouterOptions
為:interface RouterOptions { url: string; params?: Object; }
import router from '@ohos.router' @Entry @Component struct A { build() { Column() { Text("A 頁面") .fontSize(50) .fontWeight(FontWeight.Bold) Button("跳轉到 B 頁面") .onClick(() => { router.pushUrl({ url: 'pages/B' }) }) } .width("100%") .height("100%") } }
- 也可以使用
replaceUrl(options: RouterOptions): Promise<void>
,區別在於replaceUrl
方法會替換棧頂頁面,導致無法使用下述back
方法返回上一個頁面
-
-
在 B 頁面呼叫方法:
back(options?: RouterOptions ): void
import router from '@ohos.router' @Entry @Component struct B { build() { Column() { Text("B 頁面") .fontSize(50) .fontWeight(FontWeight.Bold) Button("返回") .onClick(() => { router.back() }) } .width('100%') .height('100%') } }
-
使用 A 頁面傳遞引數
router.pushUrl({ url: 'pages/B', params: { name: "張三", age: 18 } })
-
在 B 頁面接受引數
interface IParams { name: string age: number } @Entry @Component struct B { @State person: IParams = { name: "", age: 0 } aboutToAppear() { this.person = router.getParams() as IParams } build() { Column() { // ... Text(`${this.person.name} - ${this.person.age}`) } } }
(3)路由模式
-
路由模式有兩種,包括:
- 單例模式:Single,每個頁面僅建立一次
- 標準模式:Standard,每次都建立新的頁面例項
-
路由模式在
pushUrl
方法的第二引數中指定,如:router.pushUrl( { url: 'pages/B' }, router.RouterMode.Single )
綜合案例
- 待辦列表
- 集卡抽獎
-End-