HarmonyOS應(yīng)用開(kāi)發(fā)——頁(yè)面

我們將對(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界面展示照雁。
  • WindowStageCreateWindowStageDestroy: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ā)的不同之處

  1. 網(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)

image.png

以上圖可滾動(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 裝飾器刪了始苇,不警告了...

image.png

我百度出來(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 自定義自己的彈框

  1. 創(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ǔ)篇就先寫到這里

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市东涡,隨后出現(xiàn)的幾起案子冯吓,更是在濱河造成了極大的恐慌,老刑警劉巖疮跑,帶你破解...
    沈念sama閱讀 212,222評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件组贺,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡祖娘,警方通過(guò)查閱死者的電腦和手機(jī)失尖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,455評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)渐苏,“玉大人掀潮,你說(shuō)我怎么就攤上這事≌裕” “怎么了胧辽?”我有些...
    開(kāi)封第一講書人閱讀 157,720評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)公黑。 經(jīng)常有香客問(wèn)我邑商,道長(zhǎng),這世上最難降的妖魔是什么凡蚜? 我笑而不...
    開(kāi)封第一講書人閱讀 56,568評(píng)論 1 284
  • 正文 為了忘掉前任人断,我火速辦了婚禮,結(jié)果婚禮上朝蜘,老公的妹妹穿的比我還像新娘恶迈。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,696評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布暇仲。 她就那樣靜靜地躺著步做,像睡著了一般。 火紅的嫁衣襯著肌膚如雪奈附。 梳的紋絲不亂的頭發(fā)上全度,一...
    開(kāi)封第一講書人閱讀 49,879評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音斥滤,去河邊找鬼将鸵。 笑死,一個(gè)胖子當(dāng)著我的面吹牛佑颇,可吹牛的內(nèi)容都是我干的顶掉。 我是一名探鬼主播,決...
    沈念sama閱讀 39,028評(píng)論 3 409
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼挑胸,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼痒筒!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起嗜暴,我...
    開(kāi)封第一講書人閱讀 37,773評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤凸克,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后闷沥,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體萎战,經(jīng)...
    沈念sama閱讀 44,220評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,550評(píng)論 2 327
  • 正文 我和宋清朗相戀三年舆逃,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蚂维。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,697評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡路狮,死狀恐怖虫啥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情奄妨,我是刑警寧澤涂籽,帶...
    沈念sama閱讀 34,360評(píng)論 4 332
  • 正文 年R本政府宣布,位于F島的核電站砸抛,受9級(jí)特大地震影響评雌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜直焙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,002評(píng)論 3 315
  • 文/蒙蒙 一景东、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧奔誓,春花似錦斤吐、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,782評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)庄呈。三九已至,卻和暖如春臼婆,著一層夾襖步出監(jiān)牢的瞬間抒痒,已是汗流浹背幌绍。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,010評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工颁褂, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人傀广。 一個(gè)月前我還...
    沈念sama閱讀 46,433評(píng)論 2 360
  • 正文 我出身青樓颁独,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親伪冰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子誓酒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,587評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容