1

#夏日挑战赛# 用OpenHarmony eTS 实现一个Huawei app标准布局

 2 years ago
source link: https://blog.51cto.com/harmonyos/5399401
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

用OpenHarmony eTS 实现一个Huawei app标准布局

 本文正在参加星光计划3.0 – 夏日挑战赛

Huawei 的app,我们都能看得出来是用心设计过的,值得学习。如果我们仔细去看Huawei 手机自带的app,我们会发现所有的app,无论是什么类型的app,其布局结构都是一种类似的结构,这说明这种布局结构的用途真的可以很广泛,而且体验很好…

像华为的应用市场app、联系人、浏览器、图库、智慧生活app、音乐app、我的华为、畅连 等等,你去看,全部是这种上中下的布局结构,顶部栏(top+middle)+内容展示区(content)+底部tab栏,那么,今天我们就一起来实现一个这样的布局。

2.效果展示

DAYU200真机

#夏日挑战赛# 用OpenHarmony eTS 实现一个Huawei app标准布局_通用app布局

视频地址
 https://ost.51cto.com/show/13842

#夏日挑战赛# 用OpenHarmony eTS 实现一个Huawei app标准布局_eTS_02
#夏日挑战赛# 用OpenHarmony eTS 实现一个Huawei app标准布局_eTS_03

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标准布局_通用app布局_04

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标准布局_eTS_05
#夏日挑战赛# 用OpenHarmony eTS 实现一个Huawei app标准布局_eTS_06
#夏日挑战赛# 用OpenHarmony eTS 实现一个Huawei app标准布局_eTS_07

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

 想了解更多关于开源的内容,请访问:

 51CTO 开源基础软件社区

 https://ost.51cto.com/#bkwz


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK