我們將對(duì)于多頁(yè)面以及更多有趣的功能展開(kāi)敘述,這次我們對(duì)于 HarmonyOS 的很多有趣常用組件并引出一些其他概念以及解決方案赶撰、頁(yè)面跳轉(zhuǎn)傳值、生命周期、啟動(dòng)模式(UiAbility),樣式的書寫吉嫩、狀態(tài)管理以及動(dòng)畫等方面進(jìn)行探討
頁(yè)面之間的跳轉(zhuǎn)以及數(shù)據(jù)傳遞
頁(yè)面之間的跳轉(zhuǎn)需要用到 router 模塊的 pushUrl 方法磨隘,所以第一步是要導(dǎo)入 router 模塊缤底,然后在用戶交互 API 中使用該方法進(jìn)行頁(yè)面的跳轉(zhuǎn)(我這里使用的是按鈕點(diǎn)擊)
import router from '@ohos.router'
router.pushUrl({
url: 'pages/Second'
})
然后我們需要將要跳轉(zhuǎn)的到的目標(biāo)頁(yè)面進(jìn)行一個(gè)頁(yè)面路由配置(main_pages.json),忽略掉這一步的話頁(yè)面跳轉(zhuǎn)時(shí)會(huì)報(bào)錯(cuò)的番捂,報(bào)錯(cuò)會(huì)提示讓你去查看日志个唧,而日志是說(shuō)路由有問(wèn)題,沒(méi)辦法跳轉(zhuǎn)设预,如果你的路徑書寫的沒(méi)問(wèn)題的話徙歼,那么就是沒(méi)有配置了
- 路由配置
{
"src": [
"pages/Index",
"pages/Second"
]
}
完整代碼如下:
- 主頁(yè)面
// 導(dǎo)入 router 路由模塊
import router from '@ohos.router'
@Entry
@Component
struct Index {
build() {
Row() {
Column() {
Text("主頁(yè)面")
.fontSize(32)
.fontColor('#404040')
.fontWeight(FontWeight.Bold)
Button("點(diǎn)擊去往第二個(gè)頁(yè)面")
.margin(32)
.padding(12)
.backgroundColor('#ff21b88d')
.onClick(()=> {
// 路由跳轉(zhuǎn)指定頁(yè)面
router.pushUrl({
url: 'pages/Second'
})
})
}.width('100%')
}.height('100%')
}
}
- 目標(biāo)頁(yè)面
@Entry
@Component
struct Second {
build() {
Row() {
Column() {
Text("Hello ArkTs")
.fontWeight(FontWeight.Bold)
}.width('100%')
}.height('100%')
}
}
頁(yè)面之間傳值
頁(yè)面之間的傳值還是靠 pushUrl 方法的 params 參數(shù)來(lái)傳值
router.pushUrl({
url: 'pages/Second',
params: {title:'頁(yè)面之間的傳值'}
})
接收方需要使用 router 實(shí)例的 getParams 方法來(lái)進(jìn)行接收
@State message:string = router.getParams()?.["title"]
tip: PI9及以上,router.pushUrl()方法新增了mode參數(shù)鳖枕,可以將mode參數(shù)配置為router.RouterMode.Single單實(shí)例模式和router.RouterMode.Standard多實(shí)例模式魄梯。
router.pushUrl({
url: 'pages/Second',
params: {
src: '數(shù)據(jù)',
}
}, router.RouterMode.Single)
除了我們上面所說(shuō)的 pushUrl 方法,其實(shí)還有一種方法可以進(jìn)行頁(yè)面跳轉(zhuǎn):router.replaceUrl()宾符,該方法新增了mode參數(shù)酿秸,可以將mode參數(shù)配置為router.RouterMode.Single單實(shí)例模式和router.RouterMode.Standard多實(shí)例模式。在單實(shí)例模式下:如果目標(biāo)頁(yè)面的url在頁(yè)面棧中已經(jīng)存在同url頁(yè)面魏烫,離棧頂最近同url頁(yè)面會(huì)被移動(dòng)到棧頂辣苏,替換當(dāng)前頁(yè)面,并銷毀被替換的當(dāng)前頁(yè)面哄褒,移動(dòng)后的頁(yè)面為新建頁(yè)稀蟋,頁(yè)面棧的元素?cái)?shù)量會(huì)減1;如果目標(biāo)頁(yè)面的url在頁(yè)面棧中不存在同url頁(yè)面呐赡,按多實(shí)例模式跳轉(zhuǎn)退客,頁(yè)面棧的元素?cái)?shù)量不變。
還是那句話链嘀,具體問(wèn)題具體方案萌狂,因?yàn)閼?yīng)用是有一個(gè)頁(yè)面棧的,如果用戶在進(jìn)入目標(biāo)頁(yè)面之后通過(guò)一些途徑再次重復(fù)進(jìn)入頁(yè)面管闷,而頁(yè)面椫嘟牛或者說(shuō)我們開(kāi)發(fā)者不進(jìn)行分辨以及預(yù)知的話,就會(huì)出現(xiàn)重復(fù)跳轉(zhuǎn)包个、找不到跳轉(zhuǎn)前頁(yè)面刷允,頁(yè)面將會(huì)出現(xiàn)無(wú)法返回或者陷入一個(gè)頁(yè)面的死循環(huán)等問(wèn)題
router.replaceUrl({
url: 'pages/Second',
params: {
src: 'Index頁(yè)面?zhèn)鱽?lái)的數(shù)據(jù)',
}
}, router.RouterMode.Single)
UIAbilIty - 應(yīng)用程序入口
uiability 就是我們的應(yīng)用程序入口冤留,是系統(tǒng)調(diào)度單元,可以有多個(gè)也可以在一個(gè)入口中進(jìn)行所有操作树灶,具體情況具體方案纤怒,就比如我們經(jīng)常使用的聊天工具,里面會(huì)內(nèi)置小游戲等天通,如果我們通過(guò)該聊天工具進(jìn)入了小游戲泊窘,那么該小游戲會(huì)重新開(kāi)一個(gè) uiability,這樣我們通過(guò)任務(wù)管理器即可進(jìn)行兩個(gè)應(yīng)用的互相切換而不影響用戶的體驗(yàn)
它的意思相當(dāng)于 Vue 或者 Uniapp中的 main 程序入口文件像寒,是一個(gè)應(yīng)用程序入口
在這個(gè)入口文件中烘豹,我們可以通過(guò)它的生命周期來(lái)做很多的事情,當(dāng)應(yīng)用程序打開(kāi)創(chuàng)建诺祸、當(dāng)他進(jìn)入了后臺(tái)携悯、退出后臺(tái)等等,但WindowStageCreate和WindowStageDestroy也就是使用虛線標(biāo)明的兩個(gè)只能算是狀態(tài)筷笨,uiability生命周期只有四個(gè)
-
Create
:Create狀態(tài)為在應(yīng)用加載過(guò)程中憔鬼,UIAbility實(shí)例創(chuàng)建完成時(shí)觸發(fā),系統(tǒng)會(huì)調(diào)用onCreate()回調(diào)胃夏≈峄颍可以在該回調(diào)中進(jìn)行應(yīng)用初始化操作,例如變量定義資源加載等仰禀,用于后續(xù)的UI界面展示照雁。 -
WindowStageCreate
、WindowStageDestroy
:UIAbility實(shí)例創(chuàng)建完成之后悼瘾,在進(jìn)入Foreground之前囊榜,系統(tǒng)會(huì)創(chuàng)建一個(gè)WindowStage审胸。WindowStage創(chuàng)建完成后會(huì)進(jìn)入onWindowStageCreate()回調(diào)亥宿,可以在該回調(diào)中設(shè)置UI界面加載、設(shè)置WindowStage的事件訂閱砂沛。官網(wǎng)給我們提供了這樣一張生命周期圖
-
Foreground
烫扼、Background
:Foreground和Background狀態(tài)分別在UIAbility實(shí)例切換至前臺(tái)和切換至后臺(tái)時(shí)觸發(fā),對(duì)應(yīng)于onForeground()回調(diào)和onBackground()回調(diào) -
Destroy
:Destroy狀態(tài)在UIAbility實(shí)例銷毀時(shí)觸發(fā)碍庵∮称螅可以在onDestroy()回調(diào)中進(jìn)行系統(tǒng)資源的釋放、數(shù)據(jù)的保存等操作静浴。
我們可以打開(kāi)應(yīng)用入口文件查看這些生命周期:
import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from '@ohos.hilog';
import window from '@ohos.window';
export default class EntryAbility extends UIAbility {
onCreate(want, launchParam) {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
}
onDestroy() {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage) {
// Main window is created, set main page for this ability
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
windowStage.loadContent('pages/Index', (err, data) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
});
}
onWindowStageDestroy() {
// Main window is destroyed, release UI related resources
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
}
onForeground() {
// Ability has brought to foreground
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
}
onBackground() {
// Ability has back to background
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
}
}
UIAbility啟動(dòng)模式
UIAbility當(dāng)前支持singleton(單實(shí)例模式)堰氓、multiton(多實(shí)例模式)和specified(指定實(shí)例模式)3種啟動(dòng)模式
- 在單例模式中,每次調(diào)用startAbility()方法時(shí)苹享,如果應(yīng)用進(jìn)程中該類型的UIAbility實(shí)例已經(jīng)存在双絮,則復(fù)用系統(tǒng)中的UIAbility實(shí)例,系統(tǒng)中只存在唯一一個(gè)該UIAbility實(shí)例。即在最近任務(wù)列表中只存在一個(gè)該類型的UIAbility實(shí)例
- 在多實(shí)例模式中囤攀,每次調(diào)用startAbility()方法時(shí)软免,都會(huì)在應(yīng)用進(jìn)程中創(chuàng)建一個(gè)該類型的UIAbility實(shí)例。即在最近任務(wù)列表中可以看到有多個(gè)該類型的UIAbility實(shí)例
- 在指定實(shí)例模式中焚挠,UIAbility實(shí)例新創(chuàng)建之前膏萧,允許開(kāi)發(fā)者為該實(shí)例創(chuàng)建一個(gè)字符串Key,新創(chuàng)建的UIAbility實(shí)例綁定Key之后蝌衔,后續(xù)每次調(diào)用startAbility方法時(shí)榛泛,都會(huì)詢問(wèn)應(yīng)用使用哪個(gè)Key對(duì)應(yīng)的UIAbility實(shí)例來(lái)響應(yīng)startAbility請(qǐng)求。如果匹配有該UIAbility實(shí)例的Key噩斟,則直接拉起與之綁定的UIAbility實(shí)例挟鸠,否則創(chuàng)建一個(gè)新的UIAbility實(shí)例。運(yùn)行時(shí)由UIAbility內(nèi)部業(yè)務(wù)決定是否創(chuàng)建多實(shí)例亩冬。
然后我們?cè)趍odule.json5文件中的“l(fā)aunchType”字段配置為對(duì)應(yīng)實(shí)例模式即可艘希。
如多實(shí)例模式:
{
"module": {
"abilities": [
{
"launchType": "multiton"
}
]
}
}
image
單獨(dú)介紹這個(gè)圖片的用意就是想要說(shuō)它的資源引用特點(diǎn)以及和常見(jiàn)前端開(kāi)發(fā)的不同之處
- 網(wǎng)絡(luò)圖片引用
Image("https://ts1.cn.mm.bing.net/th/id/R-C.66d7b796377883a92aad65b283ef1f84?rik=sQ%2fKoYAcr%2bOwsw&riu=http%3a%2f%2fwww.quazero.com%2fuploads%2fallimg%2f140305%2f1-140305131415.jpg&ehk=Hxl%2fQ9pbEiuuybrGWTEPJOhvrFK9C3vyCcWicooXfNE%3d&risl=&pid=ImgRaw&r=0")
.alt($r('app.media.icon'))// 使用alt,在網(wǎng)絡(luò)圖片加載成功前使用占位圖
.width("264vp")
// 像素單位有4中表示單位 vp硅急、px覆享、fp、lpx
網(wǎng)絡(luò)圖片引用官網(wǎng)說(shuō)是要在module.json5中配置訪問(wèn)網(wǎng)絡(luò)權(quán)限营袜,我就在網(wǎng)上隨便找了一張圖片撒顿,然后我發(fā)現(xiàn)沒(méi)有配置也可以用,這個(gè)的話荚板,能用就用凤壁,不能用就配置上,后續(xù)慢慢了解這個(gè)小東西
"requestPermissions": [{
"name": "ohos.permission.INTERNET"
}],
2.第二種是使用PixelMap數(shù)據(jù)加載圖片跪另,讀取寫入圖像數(shù)據(jù)以及獲取圖像信息
大多用于圖形解碼等需求的拧抖,這個(gè)我也不是很懂,我就不闡述了
3.第三種是使用Resource數(shù)據(jù)加載圖片免绿,這個(gè)需要將圖片放在指定目錄下
Image($r("app.media.todolist"))
.width(64)
resource 可以新建引用資源文件唧席,我們右擊 resource 文件夾新建引用資源json文件
這樣我們可以寫一些常用寬度高度等來(lái)提高復(fù)用性,便于修改維護(hù)
Image($r("app.media.todolist"))
.width($r("app.float.image"))
harmonyOS的樣式怎么使用
對(duì)于習(xí)慣組件式開(kāi)發(fā)且邏輯層嘲驾、樣式層淌哟、架構(gòu)層分離開(kāi)發(fā)的我來(lái)說(shuō),這個(gè)樣式的繪畫有點(diǎn)不習(xí)慣辽故,寫個(gè)樣式還真得看看官網(wǎng)徒仓,所以針對(duì)樣式的使用我寫了一個(gè)登陸頁(yè)面來(lái)練習(xí),開(kāi)發(fā)起來(lái)倒也是方便誊垢,沒(méi)用多少代碼掉弛,一個(gè)視覺(jué)上過(guò)的去的頁(yè)面就開(kāi)發(fā)出來(lái)了喻杈,尤其是它的 Row 和 Column 容器,掌握了真的很好用狰晚,用來(lái)做橫向豎向布局很舒服
下面的代碼沒(méi)有寫注釋筒饰,但是不難理解,一定可以幫你解決一些樣式上的困擾
import router from '@ohos.router'
@Builder function ImageBuilder(src) {
Image(src)
.width(32)
.height(32)
}
@Entry
@Component
struct Index {
@State username:string = ''
@State password:string = ''
build() {
Row() {
Column() {
Image($r("app.media.icon"))
.width($r("app.float.image_size"))
.height($r("app.float.image_size"))
.margin(32)
Text("登錄系統(tǒng)")
.fontSize(24)
.fontColor('#404040')
.fontWeight(700)
.margin(12)
Text("登錄系統(tǒng)以使用更多功能")
TextInput({text:this.username,placeholder:'請(qǐng)輸入用戶名'})
.margin(32)
.padding({top:12,left:24})
TextInput({text:this.password,placeholder:'請(qǐng)輸入用戶密碼'})
.type(InputType.Password)
.margin({top:0,left:32,right:32})
.padding({top:12,left:24})
Row() {
Text("驗(yàn)證碼登錄")
.fontColor("#ff1a81d0")
.fontWeight(500)
Text("忘記密碼")
.fontColor("#ff1a81d0")
.fontWeight(500)
}.width("75%")
.margin({top:24})
.justifyContent(FlexAlign.SpaceBetween)
Button("登錄")
.width("90%")
.margin(64)
.padding(12)
.backgroundColor('#ff1a81d0')
.fontWeight(700)
.onClick(()=> {
// 路由跳轉(zhuǎn)指定頁(yè)面
router.pushUrl({
url: 'pages/Second',
params: {title:'頁(yè)面之間的傳值'}
})
})
Row({space: 32}) {
ImageBuilder($r("app.media.icon"))
ImageBuilder($r("app.media.icon"))
ImageBuilder($r("app.media.icon"))
}.margin({top:32})
Text("其他方式登錄")
.margin({top:24})
.fontSize(12)
.fontColor("#ff1a81d0")
}.width('100%')
}.height('100%')
}
}
獲取 input 的用戶輸入字符可以使用 Input 內(nèi)置的 onChange 方法壁晒,該方法會(huì)自己接受 value 也就是用戶輸入值瓷们,然后相應(yīng)的對(duì)其進(jìn)行操作即可
.onChange((value: string) => {
})
順便說(shuō)個(gè)有趣的加載組件 :
-
LoadingProgress
:
LoadingProgress()
.color(Color.Blue)
.height(60)
.width(60)
我們把剛才頁(yè)面的logo圖標(biāo)使用這個(gè)加載logo替換一下
這是一個(gè)動(dòng)態(tài)的加載圖標(biāo),我截圖所以看不出來(lái)秒咐,挺好看的谬晕,我們可以在登錄等待時(shí)間使用它撐一段時(shí)間,讓用戶不那么尷尬
harmonyOS的組件還是很多很棒的携取,其他有趣的組件我們可以去其官網(wǎng)學(xué)習(xí)
List
HarmonyOS同樣也有 list 滾動(dòng)列表組件便于我們開(kāi)發(fā)
該組件有三個(gè)重要可選參數(shù):
- space:列表間距
- scroller:控制列表滾動(dòng)
- initiallndex:初次加載 list 所顯示的 item
@Entry
@Component
struct Second {
@Builder listItemComponent(item:string) {
Row({space:12}) {
Image($r("app.media.icon"))
.width(32)
Text(item).fontWeight(FontWeight.Bold)
}.width("100%")
.justifyContent(FlexAlign.SpaceBetween)
}
@State list:Array<object> = [
{
id:0,
title:'測(cè)試1'
},
{
id:1,
title:'測(cè)試2'
},
{
id:2,
title:'測(cè)試3'
}
]
build() {
Column() {
List({space:12}) {
ForEach(this.list,(item:object) => {
ListItem() {
this.listItemComponent(item["title"])
}.width("90%")
.backgroundColor("#ffffff")
.padding({top:12,left:24,right:24,bottom:12})
.borderRadius(24)
},item=> item.id)
}.alignListItem(ListItemAlign.Center)
}.backgroundColor("#efefef")
.height("100%")
.justifyContent(FlexAlign.SpaceEvenly)
}
}
Grid
grid 只有一個(gè)可選參數(shù) scroller 來(lái)控制 grid 的滾動(dòng)
以上圖可滾動(dòng) Grid 為例我們查看其示例代碼攒钳,想要實(shí)現(xiàn)滾動(dòng)效果,我們只需要給寬高任意一方限定即可八雷滋,并配置相應(yīng)屬性不撑,例如僅設(shè)置columnsTemplate屬性,不設(shè)置rowsTemplate屬性晤斩,就可以實(shí)現(xiàn)Grid列表的滾動(dòng)
- 組件頁(yè)面
import GridModel from './grid';
import { GridData } from './gridData';
@Component
export struct GridAssembly {
build() {
Grid() {
ForEach(GridModel.getGridModel(),(item:GridData) => {
GridItem() {
Column() {
Image(item.img)
.height(52)
.width(52)
.margin({bottom:12})
Text(item.title)
.fontWeight(FontWeight.Bold)
}
}
},item => JSON.stringify(item))
}.height(124)
.columnsTemplate('1fr 1fr 1fr')
.rowsGap(12)
.columnsGap(12)
.backgroundColor('#fff')
.width("90%")
.borderRadius(24)
.padding(24)
}
}
- 數(shù)據(jù)構(gòu)建
import { GridData } from './gridData';
export default class GridModel {
public static getGridModel():Array<GridData> {
let data:Array<GridData> = [
new GridData("測(cè)試文字01",$r("app.media.we_chat")),
new GridData("測(cè)試文字02",$r("app.media.we_chat")),
new GridData("測(cè)試文字03",$r("app.media.we_chat")),
new GridData("測(cè)試文字04",$r("app.media.we_chat")),
new GridData("測(cè)試文字05",$r("app.media.we_chat")),
new GridData("測(cè)試文字06",$r("app.media.we_chat")),
new GridData("測(cè)試文字07",$r("app.media.we_chat"))
]
return data;
}
}
- 參數(shù)類型定義
export class GridData {
title:string;
img?:Resource;
constructor(title:string,img?:Resource) {
this.title = title;
this.img = img;
}
}
無(wú)論是 Grid 組件還是 List 組件焕檬,我們都可以使用 onScrollIndex 來(lái)監(jiān)聽(tīng)列表的滾動(dòng),還有更多 API 我們可以前往其官網(wǎng)查看學(xué)習(xí) 例如:onScrollStop 等
tabbar
不得不說(shuō)澳泵,harmonyOS 的tabbar 和 uniapp的不同點(diǎn)在于uniapp是統(tǒng)一配置实愚,全局唯一,默認(rèn)的底部欄不是很靈活多變兔辅,uniapp支持的平臺(tái)太多了腊敲,也難怪,不怪它??
而 harmonyOS 可以定義其超出滾動(dòng)维苔、側(cè)邊tabbar等等功能碰辅,且 tabbar 是一個(gè)組件,這樣的話蕉鸳,我們即可以無(wú)限創(chuàng)建??
此組件有三個(gè)參數(shù):
- barPosition:指定頁(yè)簽位置來(lái)創(chuàng)建Tabs容器組件
- index:指定初次初始頁(yè)簽索引
- controller:設(shè)置Tabs控制器
屬性:
且需要 TabContent 組件來(lái)配合
這是一個(gè)最簡(jiǎn) tabbar乎赴,我試了一下...不好看,不過(guò)倒也適合那種頂部 navbar 的
所以我決定使用 @Builder 來(lái)構(gòu)建一個(gè)稍微好看點(diǎn)的 tabbar
我一開(kāi)始不了解這個(gè) tabbar 的邏輯潮尝,結(jié)果配的一塌糊涂了,因?yàn)閠abbar要跳轉(zhuǎn)頁(yè)面饿序,跳轉(zhuǎn)頁(yè)面就要把頁(yè)面寫到 TabContent 里面勉失,這就需要導(dǎo)入其他頁(yè)面,而我創(chuàng)建的頁(yè)面需要使用 @Entry 裝飾器來(lái)聲明原探,這兩個(gè)直接沖突了乱凿,不能導(dǎo)出使用 @Entry 聲明的頁(yè)面
...而且在當(dāng)前一個(gè)頁(yè)面寫 tabbar顽素,本頁(yè)面跳轉(zhuǎn)本頁(yè)面且其他頁(yè)面沒(méi)有和 tabbar 直接聯(lián)系,感覺(jué)有點(diǎn)不合邏輯徒蟆,所以我想了一個(gè)辦法胁出,要不我寫一個(gè) 專門的 tabbar 頁(yè)面,然后跳轉(zhuǎn)其他頁(yè)面段审,我去試了試全蝶,用到是可以用,可是...
是個(gè)警告寺枉,并且這個(gè)警告告訴我這是有安全問(wèn)題的抑淫,可能引起引擎錯(cuò)誤...(請(qǐng)不必在意我的項(xiàng)目很亂,我是用來(lái)練習(xí)的)
想必也不是這樣寫的姥闪,隨后我把兩個(gè)頁(yè)面的 @Entry 裝飾器刪了始苇,不警告了...
我百度出來(lái)的一大批都是(包括官網(wǎng))都是使用一個(gè)普通組件來(lái)充當(dāng)頁(yè)面進(jìn)行示例的,所以目前就這樣寫吧筐喳,邏輯上也是比較完美的催式,如果后續(xù)了解到了我會(huì)回來(lái)評(píng)論,大家也可以在評(píng)論區(qū)評(píng)論一起探討,代碼我也貼到下面
- tabbar.ets
import userPage from './user'
import homePage from './Home'
@Entry
@Component
struct tabbarPage{
private controller = new TabsController();
@State currentIndex:number = 0;
@Builder TabbarBuilder(title:string,index:number,selectImg:Resource,Img:Resource) {
Column() {
Image(this.currentIndex == index ? selectImg : Img)
.width(32)
.height(32)
Text(title)
.fontColor(this.currentIndex == index ? "#fff" : "#000")
}
.onClick(()=> {
this.currentIndex = index;
this.controller.changeIndex(index);
})
}
build() {
Tabs({barPosition:BarPosition.End,controller:this.controller}) {
TabContent() {
homePage()
}.tabBar(this.TabbarBuilder("主頁(yè)",0,$r("app.media.home"),$r("app.media.home_no")))
TabContent() {
userPage()
}.tabBar(this.TabbarBuilder("用戶頁(yè)面",1,$r("app.media.user"),$r("app.media.user_no")))
}.vertical(false)
.barWidth("100%")
.barHeight(64)
.barMode(BarMode.Fixed)
.onChange((index:number)=> {
this.currentIndex = index;
})
}
}
- HOME
@Component
export default struct homePage {
build() {
Text("主頁(yè)")
}
}
- User
@Component
export default struct userPage {
build() {
Text("用戶頁(yè)面")
}
}
@Watch
這也是一個(gè)裝飾器避归,我們?cè)谏弦徽抡f(shuō)過(guò) @State蓄氧、@Prop、@Link槐脏、@Component等等
因?yàn)橐ㄟ^(guò)組件狀態(tài)來(lái)引出 @Watch 喉童,所以我再大體闡述一下:
- @State:?jiǎn)蝹€(gè)組件中的數(shù)據(jù)驅(qū)動(dòng)試圖
- @Prop:父組件單向控制子組件數(shù)據(jù)驅(qū)動(dòng)試圖,且該裝飾器子組件來(lái)使用
- @Link:父子組件之間的雙向數(shù)據(jù)傳遞或者說(shuō)綁定顿天,且該裝飾器子組件來(lái)使用
而 @Watch 的作用是監(jiān)聽(tīng)狀態(tài)值的變化堂氯,如果狀態(tài)值發(fā)生變化,那么會(huì)觸發(fā)執(zhí)行我們定義好的回調(diào)函數(shù)牌废,實(shí)現(xiàn)當(dāng)前監(jiān)聽(tīng)數(shù)據(jù)改變牽動(dòng)其他狀態(tài)的改變
以下代碼我沒(méi)有使用組件間的數(shù)據(jù)傳遞來(lái)配合 @Watch 使用咽白,但是足夠描述該裝飾器功能,我們通過(guò)監(jiān)聽(tīng)數(shù)據(jù)的變化鸟缕,如果數(shù)據(jù)大于等于5晶框,那么會(huì)觸發(fā) numMaxFunc 函數(shù)來(lái)改變其他變量或者狀態(tài),且可以與其他裝飾器相配合在一起使用
@Component
export default struct homePage {
@State flag:boolean = true;
@State @Watch("numMaxFunc") num:number = 0;
@State text:string = "+ 1";
numMaxFunc() {
if(this.num >= 5) {
this.num--;
this.text = "-1";
this.flag = !this.flag;
}
}
build() {
Column({space:12}) {
Text(`${ this.num }`)
Button(this.text)
.onClick(()=> {
if(this.flag) {
this.num++;
} else {
this.num--;
}
})
Button("點(diǎn)擊切換按鈕功能")
.onClick(()=> {
this.flag = !this.flag;
if (this.text == "+ 1") {
this.text = "- 1";
} else {
this.text = "+ 1";
}
})
}
}
}
跨組件層級(jí)雙向同步狀態(tài):@Provide和@Consume
@Provide和@Consume懂从,應(yīng)用于與后代組件的雙向數(shù)據(jù)同步授段,應(yīng)用于狀態(tài)數(shù)據(jù)在多個(gè)層級(jí)之間傳遞的場(chǎng)景。不同于上文提到的父子組件之間通過(guò)命名參數(shù)機(jī)制傳遞番甩,@Provide和@Consume擺脫參數(shù)傳遞機(jī)制的束縛侵贵,實(shí)現(xiàn)跨層級(jí)傳遞
這是官網(wǎng)給出的定義。不過(guò)也確實(shí)該有缘薛,否則業(yè)務(wù)需求繁瑣的話窍育,需要多頁(yè)面互相數(shù)據(jù)共享卡睦,一個(gè)數(shù)據(jù)傳十來(lái)個(gè)組件先不說(shuō),能亂死
我依稀記得在當(dāng)初學(xué)習(xí) React 的時(shí)候漱抓,雖然也有對(duì)應(yīng)的傳值方法表锻,但是感覺(jué)有點(diǎn)BT了,兄弟組件傳個(gè)值搞得什么狀態(tài)提升乞娄、發(fā)布訂閱模式瞬逊、context 狀態(tài)數(shù)傳參,不過(guò)有時(shí)候還是很喜歡 React 的...
這個(gè)效果不演示了补胚,很簡(jiǎn)單码耐,我從官網(wǎng)截了一張圖,一看便知
彈窗
警告彈窗
我們可以使用內(nèi)置 API AlterDialog 的 show 方法來(lái)顯示警告框溶其,具體配置如下面的代碼骚腥,效果如上圖,和我們?cè)?Uniapp 中使用的方式差不多瓶逃,一個(gè)內(nèi)置 API束铭,配置即可
@Component
export default struct userPage {
// 警告彈框
alterFunc() {
AlertDialog.show({
title:"標(biāo)題",
message:"內(nèi)容",
primaryButton: {
value:"關(guān)閉操作",
action:()=> {
// 點(diǎn)擊調(diào)用回調(diào)
}},
secondaryButton:{
value:"確認(rèn)操作",
fontColor:'red',
action:()=> {
// 點(diǎn)擊調(diào)用回調(diào)
}
}
})
}
build() {
Button("點(diǎn)擊出現(xiàn)彈框")
.onClick(()=> {
this.alterFunc();
})
}
}
這是一個(gè)基本的彈框,我們可以根據(jù)它的屬性來(lái)設(shè)置不一樣的效果厢绝,比如顯示在底部而不是正中央:
以下是彈框的其他配置契沫,比如我們點(diǎn)擊彈框以外的遮罩層是否關(guān)閉彈窗
日期彈框
@Component
export default struct userPage {
@State dateStr:string = '';
// 警告彈框
alterFunc() {
DatePickerDialog.show({
start: new Date('1970-01-01'), // 開(kāi)始日期
end: new Date(), // 結(jié)束日期
selected: new Date(), // 默認(rèn)選中日期
lunar:false, // 是否為農(nóng)歷
// 選中后點(diǎn)擊確定
onAccept:(val:DatePickerResult)=> {
this.dateStr = `-${val.year} -- ${val.month + 1} -- ${val.day}-`
}
})
}
build() {
Column() {
Text(this.dateStr)
Button("點(diǎn)擊出現(xiàn)彈框")
.onClick(()=> {
this.alterFunc();
})
}
}
}
還有剩下幾個(gè)彈框樣式,我就不一一贅述了昔汉,我們可以去官網(wǎng)查看
- 列表選擇彈框
- 時(shí)間滑動(dòng)選擇器彈框
- 文本滑動(dòng)選擇器彈框
主要寫一下自定義彈框懈万,如果這些樣式無(wú)法滿足我們的需求,那么我們可以使用裝飾器 @CustomDialog 自定義自己的彈框
- 創(chuàng)建自定義彈窗
import { HobbyBean } from './dataObj';
@CustomDialog
@Component
export default struct CustomDialogWidget {
@State hobbyBeans:Array<HobbyBean> = [];
@Link hobbies: string;
private controller: CustomDialogController;
// 這是一個(gè)生命周期方法靶病,當(dāng)對(duì)話框即將出現(xiàn)時(shí)會(huì)被調(diào)用 (該生命周期被用來(lái)初始化數(shù)據(jù))
aboutToAppear() {
// 獲取當(dāng)前彈框組件的上下文
// let context: Context = getContext(this);
// // 從上下文中獲取資源管理器
// let manager = context.resourceManager;
// // 從資源管理器中獲取一個(gè)字符串?dāng)?shù)組会通,這個(gè)字符串?dāng)?shù)組包含了關(guān)于各種愛(ài)好的數(shù)據(jù)
// manager.getStringArrayValue($r('app.strarray.hobbies_data'), (error, hobbyResult) => {
// // 字符串?dāng)?shù)組進(jìn)行遍歷,為每個(gè)業(yè)余愛(ài)好項(xiàng)創(chuàng)建一個(gè)新的 HobbyBean 對(duì)象
// hobbyResult.forEach((hobbyItem: string) => {
// let hobbyBean:HobbyBean = new HobbyBean();
// hobbyBean.title = hobbyItem;
// hobbyBean.isCheck = false;
// this.hobbyBeans.push(hobbyBean);
// });
// });
this.hobbyBeans = [
{
"id":0,
"title":"寫代碼",
"isCheck": false
},
{
"id":1,
"title":"打籃球",
"isCheck": false
},
{
"id":2,
"title":"跑步",
"isCheck": false
}
]
}
// 將數(shù)組中所有選中項(xiàng)的標(biāo)題連接成一個(gè)字符串娄周,并將其賦值給hobbies變量
setHobbiesValue(hobbyBeans: HobbyBean[]) {
let hobbiesText: string = '';
hobbiesText = hobbyBeans.filter((isCheckItem: HobbyBean) =>
isCheckItem?.isCheck)
.map((checkedItem: HobbyBean) => {
return checkedItem.title;
}).join(',');
this.hobbies = hobbiesText;
}
build() {
Column({space:12}) {
Text("興趣愛(ài)好選擇")
List() {
ForEach(this.hobbyBeans, (item: HobbyBean) => {
ListItem() {
Row() {
Text(item.title)
Toggle({ type: ToggleType.Checkbox, isOn: false })
.onChange((isCheck) => {
item.isCheck = isCheck;
})
}
}
}, item => item.id)
}
Row() {
Button("取消")
.onClick(() => {
this.controller.close();
})
Button("確認(rèn)")
.onClick(() => {
this.setHobbiesValue(this.hobbyBeans);
this.controller.close();
})
}.width("100%")
.justifyContent(FlexAlign.SpaceBetween)
}.backgroundColor('#fff')
.padding(32)
.width('90%')
.borderRadius(24)
}
}
可以看到我注釋了一段代碼涕侈,我本來(lái)是寫好了數(shù)據(jù)打算在彈框生命周期中將引用類型數(shù)據(jù)進(jìn)行一個(gè)處理,然后使用 foreach 遍歷一下煤辨,結(jié)果死活獲取不到上下文(應(yīng)該就是這樣獲取吧裳涛,有朋友知道可以在評(píng)論區(qū)評(píng)論,我們一起探討研究),先用死數(shù)據(jù)代替一下將功能寫出來(lái)
關(guān)于上面代碼的 hobbyBean 數(shù)據(jù)類型是我自己定義的众辨,上面也有相關(guān)引用
export class HobbyBean {
id:number;
title:string;
isCheck:boolean;
}
然后我們就可以在頁(yè)面中使用我們定義好的彈框了
import CustomDialogWidget from './dialog';
@Component
export default struct userPage {
@State hobbies:string = '';
customDialogController: CustomDialogController = new CustomDialogController({
// 彈窗內(nèi)容構(gòu)造器
builder: CustomDialogWidget({
hobbies:$hobbies
}),
alignment: DialogAlignment.Bottom,
customStyle: true,
offset: { dx: 0,dy: -20 }
});
build() {
Column({space:12}) {
Text(this.hobbies)
Button("點(diǎn)擊選擇你的愛(ài)好")
.onClick(()=> {
this.customDialogController.open();
})
}
}
}
Video
視頻播放組件端三,通過(guò) VideoController 對(duì)象可以控制一個(gè)或多個(gè)video的狀態(tài)或者他們的屬性,其他屬性泻轰、事件技肩、配置以及剛才提到的 VideoController 對(duì)象請(qǐng)前往官網(wǎng)學(xué)習(xí)查閱
我們使用真機(jī)模擬吧,我發(fā)現(xiàn) 預(yù)覽模式 不支持...視頻組件
這字也是真小浮声,我以為哪里寫錯(cuò)了我調(diào)試了半天虚婿,真服了,真機(jī)運(yùn)行如下:
Video({
src:"http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4",
// 預(yù)覽圖片
previewUri:$r("app.media.app_icon")
})
.width("90%")
.height(240)
.objectFit(ImageFit.Contain)
動(dòng)畫
屬性動(dòng)畫
這是基礎(chǔ)頁(yè)面開(kāi)發(fā)的最后一個(gè)內(nèi)容了泳挥,首先我們通過(guò)元素屬性來(lái)實(shí)現(xiàn)動(dòng)畫效果
我們給元素組件添加 animation 動(dòng)畫屬性即可實(shí)現(xiàn)動(dòng)畫效果然痊,下圖是 animation 的具體參數(shù)配置
@Component
export default struct userPage {
@State flag:boolean = false;
@State imgWidth:string = "64vp";
@State imgUrl:Resource = $r("app.media.home");
build() {
Column() {
Text(`參數(shù):${this.flag},${this.imgWidth}`)
Image(this.imgUrl)
.width(this.imgWidth)
.position({x:this.imgWidth,y:this.imgWidth})
.animation({
duration:1000,
tempo:0.8,
curve:Curve.LinearOutSlowIn,
delay:0,
// iterations:-1,
playMode:PlayMode.Normal
})
.onClick(()=> {
if(!this.flag) {
this.imgWidth = "92vp";
this.imgUrl = $r("app.media.we_chat");
this.flag = !this.flag;
} else {
this.imgWidth = "64vp";
this.imgUrl = $r("app.media.home");
this.flag = !this.flag;
}
})
}
}
}
其他動(dòng)畫
除了屬性動(dòng)畫,HarmonyOS 還提供了很多的動(dòng)畫屉符,例如 sharedTransition 不同頁(yè)面同元素的過(guò)渡轉(zhuǎn)場(chǎng)動(dòng)畫,transition 組件內(nèi)轉(zhuǎn)場(chǎng)動(dòng)畫剧浸,PageTransitionEnter 頁(yè)面間轉(zhuǎn)場(chǎng)動(dòng)畫等等
官網(wǎng)根據(jù)不同動(dòng)畫的性質(zhì)做了區(qū)分,有興趣可以查尋對(duì)應(yīng)關(guān)鍵字在官網(wǎng)實(shí)現(xiàn)更多有趣的動(dòng)畫矗钟,在這里我就不贅訴了唆香,感覺(jué)這篇文章稍微有點(diǎn)長(zhǎng)了
ArkTs API 文檔還有很多功能待我們發(fā)現(xiàn),推薦閱讀一遍官方文檔吨艇,我相信會(huì)有一些奇妙的感悟躬它,頁(yè)面的構(gòu)建和書寫基礎(chǔ)篇就先寫到這里