天天看點

#夏日挑戰賽# 用OpenHarmony eTS 實作一個Huawei app标準布局用OpenHarmony eTS 實作一個Huawei app标準布局

用OpenHarmony eTS 實作一個Huawei app标準布局

本文正在參加星光計劃3.0 -- 夏日挑戰賽

(目錄)

1.介紹

Huawei 的app,我們都能看得出來是用心設計過的,值得學習。如果我們仔細去看Huawei 手機自帶的app,我們會發現所有的app,無論是什麼類型的app,其布局結構都是一種類似的結構,這說明這種布局結構的用途真的可以很廣泛,而且體驗很好...

像華為的應用市場app、聯系人、浏覽器、圖庫、智慧生活app、音樂app、我的華為、暢連 等等,你去看,全部是這種上中下的布局結構,頂部欄(top+middle)+内容展示區(content)+底部tab欄,那麼,今天我們就一起來實作一個這樣的布局。

2.效果展示

DAYU200真機

視訊位址

https://ost.51cto.com/show/13842

模拟器

#夏日挑戰賽# 用OpenHarmony eTS 實作一個Huawei app标準布局用OpenHarmony eTS 實作一個Huawei app标準布局
#夏日挑戰賽# 用OpenHarmony eTS 實作一個Huawei app标準布局用OpenHarmony eTS 實作一個Huawei app标準布局

3.代碼講解

3.1準備工作

1).添加一個用來儲存image資源的ets檔案,resource_const.ets

export function initImageMap(): Map<string, Resource> {
  let imageMap = new Map()
  
  //tappage
  //tab icon
  imageMap.set('tab_01', $r('app.media.ic_public_home'))
  imageMap.set('tab_02', $r('app.media.ic_public_appstore'))
  imageMap.set('tab_03', $r('app.media.ic_gallery_album_damage_video'))
  imageMap.set('tab_04', $r('app.media.ic_gallery_search_things'))
  imageMap.set('tab_05', $r('app.media.ic_user_portrait'))

  imageMap.set('tab_01_filled', $r('app.media.ic_public_home_filled'))
  imageMap.set('tab_02_filled', $r('app.media.ic_public_appstore_filled'))
  imageMap.set('tab_03_filled', $r('app.media.ic_gallery_album_damage_video_filled'))
  imageMap.set('tab_04_filled', $r('app.media.ic_gallery_search_things_filled'))
  imageMap.set('tab_05_filled', $r('app.media.ic_user_portrait_filled'))

  //tab color
  imageMap.set('tab_filled_color', $r('app.color.filled_color'))
  imageMap.set('tab_unfilled_color', $r('app.color.unfilled_color'))

  return imageMap

}
           

為什麼要這麼做,

一是因為我發現,有時候如果直接這樣用,有時候圖檔就會錯亂,顯示的不是該圖檔。

二是用改起來友善。

Image($r('app.media.light_power'))  //這樣直接用
  .width(40)
  .height(40)
  .alignSelf(ItemAlign.End)
  .margin({ right: '10%', bottom: '3%' })
  .onClick(() => {
    router.push({ uri: 'pages/index' })
  })
           

2).string.json資源檔案中定義tab顯示文本

{
    "name": "tab_01",
    "value": "家居"
  }
,
  {
    "name": "tab_02",
    "value": "商城"
  }
,
  {
    "name": "tab_03",
    "value": "内容"
  }
,
  {
    "name": "tab_04",
    "value": "場景"
  }
,
  {
    "name": "tab_05",
    "value": "我的"
  }
           

接下來就是進入正題了,建立一個ets頁面,tabpage.ets

3.2實作一個底部tab欄

1).導入需要用的元件

//日志元件
import { CommonLog  as logger } from '@ohos/ohos_clogger'

//用于資料展示模型
import { NoticeDataModel, initOneNoticeData } from "../model/NoticeDataModel"

//引入定義的常量、視圖元件
import { initImageMap } from '../common/resource_const'

//資源管理,用于實作螢幕方向的擷取
import resourceManager from '@ohos.resourceManager';
           

2).定義tab圖示和文本顔色 狀态變量

//tab icon
@State tab_01_icon: Resource = initImageMap().get('tab_01_filled')
@State tab_02_icon: Resource = initImageMap().get('tab_02')
@State tab_03_icon: Resource = initImageMap().get('tab_03')
@State tab_04_icon: Resource = initImageMap().get('tab_04')
@State tab_05_icon: Resource = initImageMap().get('tab_05')
//tab 文本顔色
@State tab_01_color: Resource = initImageMap().get('tab_filled_color')
@State tab_02_color: Resource = initImageMap().get('tab_unfilled_color')
@State tab_03_color: Resource = initImageMap().get('tab_unfilled_color')
@State tab_04_color: Resource = initImageMap().get('tab_unfilled_color')
@State tab_05_color: Resource = initImageMap().get('tab_unfilled_color')
           

3).接下來看看build() 的布局,

最外層用Column容器布局,然後是Flex布局,top欄,middle,Content都是一行一行的,是以用Row容器布局,先占個位。

底部的tab欄用Flex布局,每個tab用Column布局,Column是上下2層,一個image,一個文本。

5個tab,是以每個tab的width設為20%,為了更美觀,給最外層的Column設定個背景圖檔 。

build() {
  Column() {
    //用Flex布局  
    Flex({ direction: FlexDirection.Column, wrap: FlexWrap.NoWrap }) {
        //top欄
        Row() {}.width('100%').height('80vp')
        //middle
        Row() {}.width('100%').height('150vp')
        //content
        Row() {}.width('100%').height('100%')
        //bottom tab
        Flex() {
          Column() {
            Image(this.tab_01_icon)
              .width('100%')
              .height('50%')
              .flexShrink(0)
              .objectFit(ImageFit.Contain)
            Text($r('app.string.tab_01'))
              .width('100%')
              .height('50%')
              .flexShrink(0)
              .fontColor(this.tab_01_color)
              .fontSize('15fp')
              .textAlign(TextAlign.Center)
          }.onClick(() => {
            this.current_tab_index = 1
            this.switchTab()
          })
          .width('20%')
          .height('100%')

          Column() {
            Image(this.tab_02_icon)
              .width('100%')
              .height('50%')
              .flexShrink(0)
              .objectFit(ImageFit.Contain)
            Text($r('app.string.tab_02'))
              .width('100%')
              .height('50%')
              .flexShrink(0)
              .fontColor(this.tab_02_color)
              .fontSize('15fp')
              .textAlign(TextAlign.Center)
          }
          .onClick(() => {
            this.current_tab_index = 2
            this.switchTab()
          })
          .width('20%')
          .height('100%')

          Column() {
            Image(this.tab_03_icon)
              .width('100%')
              .height('50%')
              .flexShrink(0)
              .objectFit(ImageFit.Contain)
            Text($r('app.string.tab_03'))
              .width('100%')
              .height('50%')
              .flexShrink(0)
              .fontColor(this.tab_03_color)
              .fontSize('15fp')
              .textAlign(TextAlign.Center)
          }
          .onClick(() => {
            this.current_tab_index = 3
            this.switchTab()
          })
          .width('20%')
          .height('100%')

          Column() {
            Image(this.tab_04_icon)
              .width('100%')
              .height('50%')
              .flexShrink(0)
              .objectFit(ImageFit.Contain)
            Text($r('app.string.tab_04'))
              .width('100%')
              .height('50%')
              .flexShrink(0)
              .fontColor(this.tab_04_color)
              .fontSize('15fp')
              .textAlign(TextAlign.Center)
          }
          .onClick(() => {
            this.current_tab_index = 4
            this.switchTab()
          })
          .width('20%')
          .height('100%')

          Column() {
            Image(this.tab_05_icon)
              .width('100%')
              .height('50%')
              .flexShrink(0)
              .objectFit(ImageFit.Contain)
            Text($r('app.string.tab_05'))
              .width('100%')
              .height('50%')
              .flexShrink(0)
              .fontColor(this.tab_05_color)
              .fontSize('15fp')
              .textAlign(TextAlign.Center)
          }
          .onClick(() => {
            this.current_tab_index = 5
            this.switchTab()
          })
          .width('20%')
          .height('100%')
        }
        .width('100%')
        .height('90vp')
        .align(Alignment.Center)
        .flexShrink(0)
        .backgroundColor('#ffdbc9c9')
        .margin({ top: '5vp'})
        .padding({ top: '5vp', bottom: '5vp', left: '5vp', right: '5vp' })
        
    }
  }
  .width('100%')
  .height('100%')
  .alignItems(HorizontalAlign.End)
  //設定個背景圖檔  
  .backgroundImage($r('app.media.community_notice'), ImageRepeat.XY)
}
           
#夏日挑戰賽# 用OpenHarmony eTS 實作一個Huawei app标準布局用OpenHarmony eTS 實作一個Huawei app标準布局

4).實作點選tab 切換的效果

定義目前操作的tab索引

//目前操作的tab
current_tab_index = 1
           

5).定義切換tab函數switchTab()

設定目前點選tab的選中效果,同時其它tab取消選中效果。該方法還可以繼續優化。

//切換Tab
switchTab() {

  if (this.current_tab_index == 1) {
    if (this.tab_01_icon.id != initImageMap().get('tab_01_filled').id) {

      logger.getInstance(this).debug(`===========${JSON.stringify(this.tab_01_icon)}`)
      logger.getInstance(this).debug(`===========${JSON.stringify(initImageMap().get('tab_01_filled'))}`)
      //目前選中
      this.tab_01_icon = initImageMap().get('tab_01_filled')
      this.tab_01_color = initImageMap().get('tab_filled_color')
      //重置其它
      this.tab_02_icon = initImageMap().get('tab_02')
      this.tab_02_color = initImageMap().get('tab_unfilled_color')

      this.tab_03_icon = initImageMap().get('tab_03')
      this.tab_03_color = initImageMap().get('tab_unfilled_color')

      this.tab_04_icon = initImageMap().get('tab_04')
      this.tab_04_color = initImageMap().get('tab_unfilled_color')

      this.tab_05_icon = initImageMap().get('tab_05')
      this.tab_05_color = initImageMap().get('tab_unfilled_color')
    }
  }
  if (this.current_tab_index == 2) {
    if (this.tab_02_icon.id != initImageMap().get('tab_02_filled').id) {

      logger.getInstance(this).debug(`===========${JSON.stringify(this.tab_02_icon)}`)
      logger.getInstance(this).debug(`===========${JSON.stringify(initImageMap().get('tab_02_filled'))}`)
      //目前選中
      this.tab_02_icon = initImageMap().get('tab_02_filled')
      this.tab_02_color = initImageMap().get('tab_filled_color')
      //重置其它
      this.tab_01_icon = initImageMap().get('tab_01')
      this.tab_01_color = initImageMap().get('tab_unfilled_color')

      this.tab_03_icon = initImageMap().get('tab_03')
      this.tab_03_color = initImageMap().get('tab_unfilled_color')

      this.tab_04_icon = initImageMap().get('tab_04')
      this.tab_04_color = initImageMap().get('tab_unfilled_color')

      this.tab_05_icon = initImageMap().get('tab_05')
      this.tab_05_color = initImageMap().get('tab_unfilled_color')
    }
  }
  if (this.current_tab_index == 3) {
    if (this.tab_03_icon.id != initImageMap().get('tab_03_filled').id) {

      logger.getInstance(this).debug(`===========${JSON.stringify(this.tab_03_icon)}`)
      logger.getInstance(this).debug(`===========${JSON.stringify(initImageMap().get('tab_03_filled'))}`)
      //目前選中
      this.tab_03_icon = initImageMap().get('tab_03_filled')
      this.tab_03_color = initImageMap().get('tab_filled_color')
      //重置其它
      this.tab_02_icon = initImageMap().get('tab_02')
      this.tab_02_color = initImageMap().get('tab_unfilled_color')

      this.tab_01_icon = initImageMap().get('tab_01')
      this.tab_01_color = initImageMap().get('tab_unfilled_color')

      this.tab_04_icon = initImageMap().get('tab_04')
      this.tab_04_color = initImageMap().get('tab_unfilled_color')

      this.tab_05_icon = initImageMap().get('tab_05')
      this.tab_05_color = initImageMap().get('tab_unfilled_color')
    }
  }
  if (this.current_tab_index == 4) {
    if (this.tab_04_icon.id != initImageMap().get('tab_04_filled').id) {

      logger.getInstance(this).debug(`===========${JSON.stringify(this.tab_04_icon)}`)
      logger.getInstance(this).debug(`===========${JSON.stringify(initImageMap().get('tab_04_filled'))}`)
      //目前選中
      this.tab_04_icon = initImageMap().get('tab_04_filled')
      this.tab_04_color = initImageMap().get('tab_filled_color')
      //重置其它
      this.tab_02_icon = initImageMap().get('tab_02')
      this.tab_02_color = initImageMap().get('tab_unfilled_color')

      this.tab_03_icon = initImageMap().get('tab_03')
      this.tab_03_color = initImageMap().get('tab_unfilled_color')

      this.tab_01_icon = initImageMap().get('tab_01')
      this.tab_01_color = initImageMap().get('tab_unfilled_color')

      this.tab_05_icon = initImageMap().get('tab_05')
      this.tab_05_color = initImageMap().get('tab_unfilled_color')
    }
  }
  if (this.current_tab_index == 5) {
    if (this.tab_05_icon.id != initImageMap().get('tab_05_filled').id) {

      logger.getInstance(this).debug(`===========${JSON.stringify(this.tab_05_icon)}`)
      logger.getInstance(this).debug(`===========${JSON.stringify(initImageMap().get('tab_05_filled'))}`)
      //目前選中
      this.tab_05_icon = initImageMap().get('tab_05_filled')
      this.tab_05_color = initImageMap().get('tab_filled_color')
      //重置其它
      this.tab_02_icon = initImageMap().get('tab_02')
      this.tab_02_color = initImageMap().get('tab_unfilled_color')

      this.tab_03_icon = initImageMap().get('tab_03')
      this.tab_03_color = initImageMap().get('tab_unfilled_color')

      this.tab_04_icon = initImageMap().get('tab_04')
      this.tab_04_color = initImageMap().get('tab_unfilled_color')

      this.tab_01_icon = initImageMap().get('tab_01')
      this.tab_01_color = initImageMap().get('tab_unfilled_color')
    }
  }

}
           

6).在點選tab時設定目前操作的tab索引并調用switchTab() 函數

.onClick(() => {
        this.current_tab_index = 5
        this.switchTab()
      })
           

3.3實作一個頂部工具欄

因為我們想實作一個,向上滑動時,隐藏middle欄的内容,在top欄顯示縮減版的middle資訊。

是以定義一個show_top_title 變量,用于控制top title的顯隐,定義一個show_mid_title狀态變量控制middle title的顯隐。

//控制元件顯隐
@State show_top_title: boolean = false
@State show_mid_title: boolean = true
           

top欄包含一個文本(初始時不顯示),一個搜尋按鈕,一個添加按鈕,我們希望top欄的操作按鈕能靠右邊,是以注意設定

.alignItems(HorizontalAlign.End)

//top欄
Row() {
  Column() {
    if (this.show_top_title) {
      Text('步二神探的家')
        .height('100%')
        .width('100%')
        .fontSize(24)
        .fontWeight(FontWeight.Bolder)
        .fontWeight('#CCFFF')
    }
  }
  .width('60%')
  .height('100%')
  .padding({ left: 10 })

  Column() {
    Image($r('app.media.ic_public_search'))
      .width('50vp')
      .height('100%')
      .borderRadius(30)
      .margin({ right: 10 })
      .objectFit(ImageFit.ScaleDown)
    //.backgroundColor('#bbdd11')
  }
  .width('20%')
  .height('100%')
  //.backgroundColor('#bbdd11')
  .alignItems(HorizontalAlign.End)
  .onClick(() => {
    logger.getInstance(this).debug(`you click '🔍' button`)
  })

  Column() {
    Image($r('app.media.ic_public_add'))
      .width('50vp')
      .height('100%')
      .borderRadius(30)
      .margin({ right: 10 })
      .objectFit(ImageFit.ScaleDown)
    //.backgroundColor('#bbdd11')
  }
  .width('20%')
  .height('100%')
  //.backgroundColor('#bbdd11')
  .alignItems(HorizontalAlign.End)
  .onClick(() => {
    logger.getInstance(this).debug(`you click '+' button`)
  })
}
.width('100%')
.height('80vp')
.align(Alignment.End)
.backgroundColor('#ffdbc9c9')
           

3.4實作一個Grid網格展示

1).模拟資料清單,該資料來源 NoticeDataModel 的模拟資料,資料結構是一個通知,包括标題和内容,僅用于示範。

import { NoticeDataModel, initOneNoticeData } from "../model/NoticeDataModel"
           
//資料清單
@State notice_list: NoticeDataModel[] = [
  initOneNoticeData(),
  initOneNoticeData(),
  initOneNoticeData(),
  initOneNoticeData(),
  initOneNoticeData(),
  initOneNoticeData(),
  initOneNoticeData(),
  initOneNoticeData(),
  initOneNoticeData(),
  initOneNoticeData(),
  initOneNoticeData(),
  initOneNoticeData(),
  initOneNoticeData(),
  initOneNoticeData(),
  initOneNoticeData(),
  initOneNoticeData(),
  initOneNoticeData(),
  initOneNoticeData()
]
           

2).content 布局使用Grid實作一個網格效果,使用ForEach進行周遊notice_list的資料,

ForEach的使用要注意,最後的部分, item => item.id) , 一般用唯一性的字段指派,可以提高更改後重新渲染的性能。

.columnsTemplate('1fr 1fr') 可以控制 列格式,顯示為2列 ,可以是1列,3列

//content
Row() {
  Grid() {
    ForEach(this.notice_list, item => {
      GridItem() {
        Column() {
          Text(item.notice_title)
            .fontSize(18)
            .width('100%')
          Text(item.notice_content)
            .fontSize(15)
            .width('100%')
            .padding({ top: 5 })
            .fontColor('#6D7278')
        }
        .width('100%')
        .height(160)
        .borderRadius(15)
        .padding({ top: 10, left: 10 })
        .backgroundColor(0xF9CF93)
      }
    }, item => item.id)
  }
  //列格式,顯示為2列  
  .columnsTemplate('1fr 1fr')
  .columnsGap(20)
  .rowsGap(20)
  .margin({ top: 10 })
  .onScrollIndex((first: number) => {
    logger.getInstance(this).debug(`${first.toString()}`)
    if (first == 4) {
      this.show_top_title = true;
      this.show_mid_title = false;
    }
    if (first == 2) {
      this.show_top_title = false;
      this.show_mid_title = true;
    }
  })
}
.width('100%')
.height('100%')
.padding({ left: '5vp', right: '5vp' })
.alignItems(VerticalAlign.Top)
//.backgroundColor('#ffc1dfe0')
           
1列 2列 3清單
#夏日挑戰賽# 用OpenHarmony eTS 實作一個Huawei app标準布局用OpenHarmony eTS 實作一個Huawei app标準布局
#夏日挑戰賽# 用OpenHarmony eTS 實作一個Huawei app标準布局用OpenHarmony eTS 實作一個Huawei app标準布局
#夏日挑戰賽# 用OpenHarmony eTS 實作一個Huawei app标準布局用OpenHarmony eTS 實作一個Huawei app标準布局

3.當向上滑動content時,隐藏middle的内容,同時顯示top欄中的文本内容。

onScrollIndex回調函數

.onScrollIndex((first: number) => {
  logger.getInstance(this).debug(`${first.toString()}`)
  if (first == 4) {
    this.show_top_title = true;
    this.show_mid_title = false;
  }
  if (first == 2) {
    this.show_top_title = false;
    this.show_mid_title = true;
  }
})
           

4.思考總結

4.1圖示下載下傳

1.華為提供的HarmonyOS圖示庫

2.阿裡巴巴矢量圖示庫

4.2實作螢幕方向的擷取

通過resourceManager擷取getConfiguration,然後config.direction擷取螢幕方向,這部分代碼在HarmonyOS可以正常擷取,但在OpenHarmony中還無法使用。

import resourceManager from '@ohos.resourceManager';
           
resourceManager.getResourceManager('com.example.lanls')
  .then(mgr => {

    logger.getInstance(this).debug(`=====${JSON.stringify(mgr)}`)
    mgr.getConfiguration()
      .then(config => {
        logger.getInstance(this).debug(`${JSON.stringify(config)}`)
        //DIRECTION_VERTICAL = 0,
        this.is_landscape = config.direction.valueOf() == 1 ? true : false
      })
      .catch(error => {
        logger.getInstance(this).error("getstring promise " + error);
      });
  })
  .catch(error => {
    logger.getInstance(this).error("error occurs" + error);
  });
           
DIRECTION_VERTICAL = 0,
DIRECTION_HORIZONTAL = 1
           

5.完整代碼

附件:https://ost.51cto.com/resource/2076