2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区-51CTO.COM

2.6 ArkUI实现一次开发多端部署 原创 精华

鸿蒙开发之南拳北腿
发布于 2022-5-12 15:40
浏览
7收藏

本节通过栅格化布局、自适应布局、响应式布局和使用资源,从App的弹性布局和多态组件两个维度,讲解如何实现一次开发多端部署。接着,建立一个ArkUI eTS的开发框架,这个可以作为开发新App的脚手架。

当显示环境发生变化时(如,不同屏幕尺寸的设备切换、横竖屏切换、应用分屏),我们需要及时调整内容的布局方式以适应变化。通过栅格化布局、自适应布局和响应式布局,可以达到多设备下布局的一致性。

2.6.1 栅格化布局

1. 8vp网格系统

综合权衡各类设备的屏幕特征,基于 8vp 为网格的基本单位可以对界面上元素的大小、位置、对齐方式进行更好的规划,构建更有层次感、秩序感,以及多设备上一致的布局效果。一些更小的控件(例如图标)大小也可以对齐 4vp 的网格大小。栅格化布局中的Margin和Gutter的值参考8vp/4vp网格系统设置。如,手机设备上的栅格化设计,基础栅格Margin为24vp,等于3个8vp的宽度;卡片栅格Margin为12vp,等于一个8vp加上一个4vp的宽度。

2.计算栅格的宽度

栅格的宽度在不同设备、不同环境下是动态计算的,并非固定值。与栅格宽度计算相关的三个关键参数:

Margin:栅格布局内容区距离屏幕左、右边缘的间隔。

Gutter:相邻栅格之间的间隔。

栅格数:设备屏幕宽度内可容纳的栅格数量。不同屏幕宽度下的栅格数量有一个约定。

以竖屏手机的栅格宽度计算为例:

屏幕宽度:360vp

Margin: 24vp

Gutter: 24vp

栅格数: 4个(含3个Gutter)

计算公式如下:

1个栅格的宽度 = (屏幕宽度 - Margin x 2 - Gutter x 3)/4 = (360 - 24 x 2 - 24 x 3)/4 = 60(vp)

2个栅格的宽度 = 1个栅格的宽度 + Gutter + 1个栅格的宽度 = 60 + 24 + 60 = 144(vp)

3个栅格的宽度 = 2个栅格的宽度 + Gutter + 1个栅格的宽度 = 144 + 24 + 60 = 228(vp)

4个栅格的宽度 = 3个栅格的宽度 + Gutter + 1个栅格的宽度 = 228 + 24 + 60 = 312(vp)

各类设备的栅格数、Margin、Gutter的参数,如下表所示:

参数项 手机 折叠屏 平板 车机 智慧屏
竖屏栅格数(个) 4 8 8 - -
横屏栅格数(个) 8 8 12 12 12
基础栅格Margin(vp) 24 24 24 24 48
基础栅格Gutter(vp) 24 24 24 24 24
卡片栅格Margin(vp) 12 12 12 12 48
卡片栅格Gutter(vp) 12 12 12 12 24
3. 栅格布局组件

栅格化布局组件,主要使用GridContainer布局组件、Gird布局组件和GirdItem组件,本节暂不讲解这三个组件的用法,《第4章 布局组件》中会有详细讲解。

4. 栅格断点布局

根据设备实际的屏幕宽度范围,约定栅格的数量,称为栅格断点布局。屏幕宽度小于520vp,栅格数为4;屏幕宽度大于等于520vp,且小于840vp,栅格数为8;屏幕宽度大于等于840vp,栅格数为12。

注意:由于栅格的宽度是基于设备屏幕宽度,根据公式计算而来,在不同设备上宽度存在一定的差异。为了消除这个差异导致的布局效果变差,建议栅格内的子组件排版使用自适应布局的技巧。

2.6.2 自适应布局

1. 自适应拉伸

自适应拉伸是通过设置组件与父级容器的相对比例来实现的。比如,在设计稿上,竖屏手机的宽度是“360vp”,折叠屏的宽度是“600vp”。那么,在布局组件的宽度设置上,不要使用固定值“360vp”或“600vp”,而是用“100%”这种相对值。示例代码如下:

@Entry
@Component
struct Index {
  build() {
    Column({space:8}){ // 根容器(本例使用垂直布局容器作为根容器)
      Row() { // 水平布局容器
        Text('鸿蒙开发ArkUI最佳实践')
          .fontSize(20)
      }
      .width('100%') // 相对于父级容器Column的宽度(占满)
      .padding(10)
      .backgroundColor('#FFFFFF')
    }
    .width('100%') // 相对于手机屏幕的宽度(占满)
    .height('100%') // 相对于手机屏幕的高度(占满)
    .padding({top:48,bottom:24,left:24,right:24}) // 屏幕内边距
    .backgroundColor('#F1F3F5')
  }
}

在上述代码中,根容器“Column”的宽度和高度要占满整个手机屏幕,使用“100%”这个相对值实现。在根容器内部放了一个水平布局容器“Row”,相对于根容器“Column”的宽度也是“100%”。同时,因为根容器“Column”设置了屏幕内边距"padding",其中左边距和右边距都是“24vp”,所以,实际上“Row”容器的宽度在竖屏手机的值为“312vp”(360 - 24 -24 = 312),而在折叠屏的实际宽度为“552vp”(600 -24 -24 = 552)。效果如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

注意:这个设置也能同时适配“平板/车机/智慧屏/智能穿戴”等设备,并且切换为横屏时也是适配的。

接下来,演示自适应拉伸布局下设置子组件大小和位置的两个技巧。

第一个技巧:子组件不刻意设置宽度或设置绝对宽度值,子组件间使用“Blank”组件填充,使Text组件和Button组件贴近左、右边缘。在前面代码的基础上加入如下代码:

      Row() {
        Text('鸿蒙开发ArkUI最佳实践')
          .fontSize(20)
        Blank() // 使Text组件和Button组件贴近左、右边缘
        Button('订阅')
      }
      .width('100%')
      .padding(10)
      .backgroundColor('#FFFFFF')

效果如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

后面章节讲解“Flex”布局组件时,还会介绍使用“justifyContent”实现上述效果。

第二个技巧:子组件不设置宽度,但通过“layoutWeight”设置该组件在父级容器中的宽度权重比例。在前面代码的基础上加入如下代码:

      Row() {
        Column().layoutWeight(1).height('100%').backgroundColor('#564AF7')
        Column().layoutWeight(2).height('100%').backgroundColor('#46B1E3')
        Column().layoutWeight(1).height('100%').backgroundColor('#564AF7')
      }
      .width('100%')
      .height(100)

"Row"布局组件下三个子组件"Column"通过“1:2:1”的比例瓜分了父级容器的宽度。效果如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

注意:"layoutWeight"仅适用于“Flex/Row/Column”布局组件下的子组件。

2. 自适应缩放

对于图片的展示,可以锁定宽高比例,同时将宽设置为百分比的值,实现自适应缩放,示例代码如下:

Image('/common/images/cover.png')
        .width('100%')
        .aspectRatio(1.5) // 指定当前组件的宽高比

效果如下图:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

注意:上述锁定图片的宽高比时建议使用“.aspectRatio(1.5)”这个属性,其中宽高比值“1.5”要根据具体图片的实际宽高比确定。暂时不要使用“.objectFit(ImageFit.Contain)”这个属性来保持图片组件的宽高比。经测试,在本场景下会在图片组件下产生不可控的间距。可能是一个Bug,希望ArkUI eTS正式版时鸿蒙官方能修复这个问题。代码如下:

      Image('/common/images/cover.png')
        .width('100%')
        .objectFit(ImageFit.Contain) // 保持宽高比进行缩小或者放大,使得图片完全显示在显示边界内

效果如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

注意:为了图片在缩放状态下都显示清晰,图片尺寸优先考虑在较大屏幕时能显示清晰。避免小屏清晰,大屏马赛克。

3. 自适应延伸

自适应延伸的要点在于不设置父级容器宽度,由子组件将父容器撑开。当不同设备的屏幕宽度发生变化时,组件随之发生自适应延伸显示更多数量。示例代码如下:

 Row(){
        Column().width(120).height('100%').backgroundColor('#564AF7')
        Column().width(120).height('100%').backgroundColor('#46B1E3')
        Column().width(120).height('100%').backgroundColor('#564AF7')
        Column().width(120).height('100%').backgroundColor('#46B1E3')
        Column().width(120).height('100%').backgroundColor('#564AF7')
        Column().width(120).height('100%').backgroundColor('#46B1E3')
        Column().width(120).height('100%').backgroundColor('#564AF7')
      }.height(40) // 父级容器不设置宽度,被子组件撑开

效果如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

实际项目中,我们会配合“Scroll” 可滚动容器组件,实现被遮挡子组件的显示。本页面的代码在横屏状态下,会有部分内容被遮挡,也可以通过"Scroll"纵向滚动解决。“Scroll”组件的用法在后面章节演示,非本节讲解重点,暂时不实现。

本小节代码位于“index.ets”页面,完整代码如下:

@Entry
@Component
struct Index {
  build() {
    Column({space:8}){ // 根容器(本例使用垂直布局容器作为根容器)
      Row() { // 水平布局容器
        Text('鸿蒙开发ArkUI最佳实践')
          .fontSize(20)
      }
      .width('100%') // 相对于父级容器Column的宽度(占满)
      .padding(10)
      .backgroundColor('#FFFFFF')

      Row() {
        Text('鸿蒙开发ArkUI最佳实践')
          .fontSize(20)
        Blank() // 使Text组件和Button组件贴近左、右边缘
        Button('订阅')
      }
      .width('100%')
      .padding(10)
      .backgroundColor('#FFFFFF')

      Row() {
        Column().layoutWeight(1).height('100%').backgroundColor('#564AF7')
        Column().layoutWeight(2).height('100%').backgroundColor('#46B1E3')
        Column().layoutWeight(1).height('100%').backgroundColor('#564AF7')
      }
      .width('100%')
      .height(100)

      Image('/common/images/cover.png')
        .width('100%')
        .aspectRatio(1.5) // 指定当前组件的宽高比

      Row(){
        Column().width(120).height('100%').backgroundColor('#564AF7')
        Column().width(120).height('100%').backgroundColor('#46B1E3')
        Column().width(120).height('100%').backgroundColor('#564AF7')
        Column().width(120).height('100%').backgroundColor('#46B1E3')
        Column().width(120).height('100%').backgroundColor('#564AF7')
        Column().width(120).height('100%').backgroundColor('#46B1E3')
        Column().width(120).height('100%').backgroundColor('#564AF7')
      }.height(40) // 父级容器不设置宽度,被子组件撑开
    }
    .width('100%') // 相对于手机屏幕的宽度(占满)
    .height('100%') // 相对于手机屏幕的高度(占满)
    .padding({top:48,bottom:24,left:24,right:24}) // 屏幕内边距
    .backgroundColor('#F1F3F5')
  }
}

2.6.3 响应式布局

当基本的自适应布局无法满足多终端上屏幕的体验要求时,我们需要针对不同终端的屏幕特点进行响应式的布局。常见的响应式布局样式有:分栏布局、重复布局、挪移布局和缩进布局。

1. 分栏布局

利用屏幕的宽度优势,将有上下级层级的界面同时左右显示。 比如,新闻列表页和新闻详情页。在手机上,屏幕宽度有限,那么在新闻列表页点击某条新闻后,页面跳转到新页面展示新闻详情。但是在宽屏幕设备上,可以不用跳转页面,直接在左侧展示新闻列表,在屏幕右侧展示新闻详情。完成这个案例演示,需要如下代码。初学的同学如果有一些代码看不懂,可以先不用纠结,后面章节会讲解,这里先关注分栏布局的效果:

1) 新闻列表页(/pages/news/list.ets)
/**
 * 新闻列表
 */
import {NewsDetail} from './detail'
import {NewsData, getDefaultNews, getNewsList} from '../../model/NewsDataModel'
import router from '@system.router'
import mediaquery from '@system.mediaquery'
let portraitFunc = null

@Entry
@Component
struct Index {
  @Provide newsData: NewsData = getDefaultNews()
  @Provide isLandscape: boolean = false
  orientationListener = mediaquery.matchMediaSync('screen and (1500 < width) and (orientation: landscape)')

  onPortrait(mediaQueryResult) {
    console.log("isLandscape:" + mediaQueryResult.matches);
    this.isLandscape = mediaQueryResult.matches;
  }

  aboutToAppear() {
    portraitFunc = this.onPortrait.bind(this)
    this.orientationListener.on('change', portraitFunc)
  }

  build() {
    Row(){
      Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
        Column(){
          NewsList()
        }
        .width('100%').height('100%').padding({top:40,left: 24, right: 24}).backgroundColor('#F1F3F5')
      }.layoutWeight(2).height('100%')

      if (this.isLandscape && this.newsData.newsId > 0) {
        Column() {
          NewsDetail()
        }.layoutWeight(3)
      }
    }
  }
}

/**
 * 主页新闻列表组件
 */
@Component
export struct NewsList {
  private newsList: NewsData[] = getNewsList()
  @Consume newsData: NewsData
  @Consume isLandscape:boolean

  aboutToAppear() {
    this.newsData = this.newsList[0]
  }

  build() {
    Column() {
      List() {
        ForEach(this.newsList, item => {
          ListItem() {
            Column() {
              Row() {
                Text(item.title)
                  .fontSize(17)
                  .width("55%")
                  .height(80)
                  .maxLines(4)
                  .margin(10)
                Image(item.imgUrl).width("35%").aspectRatio(1.5).margin(10)
              }
              Divider()
                .vertical(false)
                .color("#dbd8db")
                .strokeWidth(1)
            }
            .height(101)
            .width("100%")
            .onClick(() => {
              if (this.isLandscape) {
                // 如果为横屏则切换右侧详情页数据
                this.newsData = item
              } else {
                // 如果为竖屏则跳转到详情页
                router.push(
                  {
                    // 跳转到指定页面
                    uri: 'pages/news/detail',
                    params: {
                      // 跳转传递的参数
                      newsItem: item
                    }
                  })
              }
            })
          }
        }, item => item.newsId.toString())
      }
      .listDirection(Axis.Vertical) // 排列方向
    }
  }
}
2) 新闻详情页(/pages/news/detail.ets)
/**
 * 新闻详情页
 */
import router from '@system.router'
import {NewsData, getDefaultNews} from '../../model/NewsDataModel'

@Entry
@Component
struct Index {
  @Provide newsData: NewsData = getDefaultNews()

  aboutToAppear() {
    if (router.getParams()) {
      this.newsData = router.getParams().newsItem
    }
  }

  build() {
    Column() {
      NewsDetail()
    }
  }
}

/**
 * 新闻详情组件
 */
@Component
export struct NewsDetail {
  @Consume newsData: NewsData
  build() {
    Stack({ alignContent: Alignment.TopStart }){
      Scroll() {
        Column({space:10}) {
          Text(this.newsData.title)
            .fontSize(25)
          Text("reads:" + this.newsData.reads + "  likes:" + this.newsData.likes)
            .fontSize(16)
          Image(this.newsData.imgUrl).width("100%").aspectRatio(1.5)
          Text(this.newsData.content).fontSize(18)
        }.padding({left: 24, right: 24}).alignItems(HorizontalAlign.Start)
      }
    }.width('100%').height('100%').backgroundColor('#F1F3F5').padding({top:48, bottom:24})
  }
}
3) 新闻数据结构及模拟(/model/NewsDataModel.ets)
/**
 * 新闻数据
 * @param newsId 新闻ID
 * @param title 标题
 * @param newsType 新闻类型
 * @param imgUrl 图片
 * @param reads 浏览数
 * @param likes 点赞数
 * @param content 内容
 */
export class NewsData {
  newsId: number;
  title: string;
  newsType: string;
  imgUrl: string;
  reads: string;
  likes: string;
  content: string;

  constructor(newsId: number, title: string, newsType: string, imgUrl: string, reads: string, likes: string, content: string) {
    this.newsId = newsId;
    this.title = title;
    this.newsType = newsType;
    this.imgUrl = imgUrl;
    this.reads = reads;
    this.likes = likes;
    this.content = content;
  }
}

/**
 * 获取默认新闻(第一条新闻数据)
 */
export function getDefaultNews(): NewsData {
  return new NewsData(0, '', '', '', '', '', '')
}

/**
 * 新闻模拟数据
 */
const List: any[] = [
  {
    "newsId": 1,
    "title": "鸿蒙3.0开发ArkUI(eTS)最佳实践",
    "newsType": "Health",
    "imgUrl": "common/images/cover.png",
    "reads": "81",
    "likes": "54",
    "content": "本教程由浅入深阐述了鸿蒙3.0方舟开发框架最新编程语言ArkUI(eTS)的基础知识、设计标准、UI排版技巧、自定义多态组件开发、数据模拟,以及分布式全场景开发和异步编程等高级知识。全书共分为4篇:第一篇为拥抱鸿蒙3.0(第1章第4章),第二篇为鸿蒙开发进阶(第5章第9章),第三篇为鸿蒙开发高级(第10章第15章),第四篇为项目实战(第16章第18章)。书中主要内容包括:鸿蒙3.0真的来了、揭开方舟开发框架ArkUI(eTS)的神秘面纱、基础组件、布局组件、事件与手势、绘制组件、让用户界面生动而流畅、基于TS扩展的声明式开发规范、为ArkUI量身打造的多态组件库HUI、访问远程数据、使用mock模拟数据、全场景开发之分布式、多媒体、资源管理、其他高级技术、分布式新闻系统开发准备、App UI快速开发和运行前后端项目。书中包含大量应用示例,不仅可以学会理论知识还可以灵活应用。书中示例基于DevEco Studio 3.0 Beta2 for HarmonyOS环境开发,读者在学习到ArkUI(eTS)语言知识的同时还可学会方舟开发框架技术。书中通过接近商业的一个实战案例详细阐述了如何使用ArkUI(eTS)开发App,内容完整、步骤清晰,提供了工程化的解决方案。本书可作为方舟开发框架ArkUI(eTS)初学者的入门书籍,也可作为从事鸿蒙3.0App开发的技术人员的开发速查手册及培训机构的参考书籍。"
  },
  {
    "newsId": 2,
    "title": "1.1 鸿蒙3.0 App开发技术选型",
    "newsType": "Health",
    "imgUrl": "common/images/ets1.png",
    "reads": "354",
    "likes": "100",
    "content": "目前HarmonyOS 3.0最新版本为Beta2,主要支持Java UI和ArkUI(方舟开发框架)进行鸿蒙App开发,而ArkUI支持基于JS扩展的类Web开发范式和基于TS扩展的声明式开发范式(即eTS)。鸿蒙开源版本OpenHarmony在2022年3月31日已正式发布3.1 release版,仅支持Javascript和eTS两种方式。本节先基于最简单的Hello World案例,增加一个按钮,点击按钮改变文字内容。直观对比感受下这三种开发方式的差异。"
  },
  {
    "newsId": 3,
    "title": "1.2 DevEco Studio 3.0 Beta2 for HarmonyOS下载与安装",
    "newsType": "Finance",
    "imgUrl": "common/images/ets2.png",
    "reads": "91",
    "likes": "74",
    "content": "目前最新版本为“3.0 Beta2”,在这个版本支持尝鲜ArkUI(eTS)的项目开发。本节演示Windows版本的下载和安装。如上图所示,点击下载链接,弹窗中勾选“我已经阅读并同意HUAWEI DevEco Studio Beta试用协议”,点击“同意”按钮,即可开始下载。安装包有951M,请耐心等待下载完毕。"
  },
  {
    "newsId": 4,
    "title": "1.3 完成开发者认证",
    "newsType": "Finance",
    "imgUrl": "common/images/ets3.png",
    "reads": "82",
    "likes": "66",
    "content": "在基于ArkUI(eTS)开发鸿蒙应用的过程中,DevEco Stuidio为开发者提供了预览器的功能,可以查看应用的UI界面效果。预览器支持布局代码的实时预览,只需要将开发的源代码进行保存,就可以通过预览器实时查看应用/服务运行效果,方便开发者随时调整代码。需要注意的是,由于Windows系统和真机设备的字体库存在差异,可能会出现预览器界面中的字体与真机运行效果的字体存在差异。"
  },
  {
    "newsId": 5,
    "title": "1.4 调试代码、本地预览和远程模拟器",
    "newsType": "Technology",
    "imgUrl": "common/images/ets4.png",
    "reads": "703",
    "likes": "622",
    "content": "点击DevEco Studio的右侧栏“预览器”选项卡,默认预览App在手机上的运行效果。可以如下图箭头所示,点击“多设备预览”按钮,可以选择MateX2(折叠屏)、MatePadPro(平板电脑)和Car(车机),针对性查看在某类型设备上的布局效果。也可以打开“Multi-profile preview”开关,同时显示鸿蒙应用在这四种设备上的布局效果,这在开发多设备弹性布局适配时比较有用。不过,提醒一点,打开这个开关同时预览多设备效果,更新预览结果时系统开销会大一些。所以,刚开始布局时,可以先只展示一种设备的布局效果,等基本完成布局效果后再打开这个开关做多设备适配优化。这样开发效率会更高一些。"
  },
  {
    "newsId": 6,
    "title": "1.5 真机调试与应用发布",
    "newsType": "Technology",
    "imgUrl": "common/images/ets5.png",
    "reads": "354",
    "likes": "100",
    "content": "准备一部华为智能手机,确保已升级到HarmonyOS 2.0。以下是我以华为P40 Pro激活调试模式的过程,大家可以参考。如下图顺序,在手机上依次点击“设置” > 点击“关于手机” > 在版本号上快速连续点击,直到提示“您正处于开发者模式!” > 返回“设置”界面,点击“系统和更新” > 点击“开发人员选项” > 打开USB调试的开关 > 点击“确认”按钮,允许USB调试。"
  },
  {
    "newsId": 7,
    "title": "1.6 总结与回顾",
    "newsType": "Sport",
    "imgUrl": "common/images/ets6.png",
    "reads": "911",
    "likes": "543",
    "content": "俗话说“选择大于努力”,在我们准备努力掌握鸿蒙应用开发能力之前,有必要慎重选择采用的开发语言。在本章第一节,我们直观对比感受了Java UI、Js UI和eTS ArkUI的开发过程,作为一个新崛起的手机操作系统,鸿蒙需要大量开发者共同完善它的生态,作为承前启后的稳妥考量,华为首先支持了适应传统安卓开发习惯的Java UI和适应类Web开发的Js UI,让传统App低成本转换为鸿蒙App,这就是所谓“承前”。而华为最新推出的eTS ArkUI,代码简洁,开发高效,是华为未来主推的鸿蒙App开发语言,这就是“启后”。如果之前已学习过Java UI或Js UI,那么可以在学习eTS ArkUI的过程中对比加深理解;如果是刚刚接触鸿蒙开发的同学,那么,强烈建议直接从eTS ArkUI开始!"
  },
  {
    "newsId": 8,
    "title": "2.1 eTS物种起源",
    "newsType": "Sport",
    "imgUrl": "common/images/ets7.png",
    "reads": "754",
    "likes": "149",
    "content": "JavaScript (简称“JS”) 是一种轻量级解释型的编程语言(代码不进行预编译)。 JavaScript最初受Java启发而开始设计的,目的之一就是“看上去像Java”,因此语法上有类似之处,一些名称和命名规范也借自Java。JavaScript是一种属于网络的高级脚本语言,已经被广泛用于Web应用开发,常用来为网页添加各式各样的动态功能,为用户提供更流畅美观的浏览效果。通常JavaScript脚本是通过嵌入在HTML中来实现自身的功能的。JavaScript也可以用于其他场合,如服务器端编程(Node.js)。"
  },
  {
    "newsId": 9,
    "title": "2.2 基于eTS的ArkUI有什么优势",
    "newsType": "Internet",
    "imgUrl": "common/images/ets8.png",
    "reads": "714",
    "likes": "630",
    "content": "ArkUI采用极简的声明式UI描述界面语法,您只需用几行简单直观的声明式代码,即可完成界面功能, 提升HarmonyOS应用界面开发效率30%。UI开发更接近自然语义的编程方式,让开发者直观地描述UI界面 , 允许开发者以优雅的链式调用语法调用的方式配置UI结构及其属性、事件等。"
  },
  {
    "newsId": 10,
    "title": "2.3 ArkUI App设计规范",
    "newsType": "Internet",
    "imgUrl": "common/images/ets9.png",
    "reads": "854",
    "likes": "388",
    "content": "本节内容不仅适用于鸿蒙UI设计师,也是鸿蒙App开发工程师的必修课。掌握了本节阐述的关键设计规范,为开发出标准、优质的鸿蒙App打下必要的理论基础。本节涉及的很多参数,不用记忆,只要理解、留下印象即可。后面会提供封装好的框架环境,直接调用即可。"
  }
]

/**
 * 新闻列表数据
 */
export function getNewsList(): Array<NewsData> {
  let result: Array<NewsData> = []
  List.forEach(item => {
    result.push(new NewsData(item.newsId, item.title, item.newsType, item.imgUrl, item.reads, item.likes, item.content))
  })
  return result;
}

本案例涉及识别手机屏幕横竖屏,所以,需要在远程模拟器体验真实效果。最终,本案例在手机竖屏下的效果如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

在手机横屏模式、折叠屏、平板和车机下,新闻列表和新闻详情同时显示。效果如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

2. 重复布局

利用屏幕的宽度优势,将相同属性的组件横向并列排布。 一般用于卡片陈列。我的实现思路是:先通过"display"接口拿到设备的显示属性,通过"display.getDefaultDisplay()"获得当前设备的"Display"对象,然后通过该对象的“width”属性拿到显示设备的宽度(单位为像素),最后判断不同屏幕宽度时该显示几列。配合设置栅格组件"Gird"的“columnsTemplate”属性来控制显示的列数。示例代码(/pages/index2.ets)如下:

import display from '@ohos.display'

@Entry
@Component
struct Index2 {
  @State colTemplate: string = '1fr'

  aboutToAppear() {
    var displayClass = null;
    display.getDefaultDisplay((err, data) => {
      if (err) {
        return;
      }
      displayClass = data;// 获得设备的屏幕信息,displayClass.width是设备的宽度

      // 栅格断点系统
      if(displayClass.width < vp2px(520)){
        this.colTemplate = '1fr'
      }else if(displayClass.width >= vp2px(520) && displayClass.width < vp2px(840)){
        this.colTemplate = '1fr 1fr'
      }else{
        this.colTemplate = '1fr 1fr 1fr'
      }
    });
  }

  build() {
    Flex({ direction: FlexDirection.Column }) {
      Column() {
        Grid() {
          GridItem() {
            Column(){
              // 这里放置每个卡片里的组件
            }.width('100%').height(150).borderRadius(16).backgroundColor('#FFFFFF')
          }
          GridItem() {
            Column(){
              // 这里放置每个卡片里的组件
            }.width('100%').height(150).borderRadius(16).backgroundColor('#FFFFFF')
          }
          GridItem() {
            Column(){
              // 这里放置每个卡片里的组件
            }.width('100%').height(150).borderRadius(16).backgroundColor('#FFFFFF')
          }
          GridItem() {
            Column(){
              // 这里放置每个卡片里的组件
            }.width('100%').height(150).borderRadius(16).backgroundColor('#FFFFFF')
          }
          GridItem() {
            Column(){
              // 这里放置每个卡片里的组件
            }.width('100%').height(150).borderRadius(16).backgroundColor('#FFFFFF')
          }
          GridItem() {
            Column(){
              // 这里放置每个卡片里的组件
            }.width('100%').height(150).borderRadius(16).backgroundColor('#FFFFFF')
          }
        }.width('100%').columnsGap(12).rowsGap(12).columnsTemplate(this.colTemplate)
      }.alignItems(HorizontalAlign.Start)
    }.width('100%').backgroundColor('#F1F3F5').padding({top:48, bottom:24,left: 24, right: 24})
  }
}

该效果需要通过远程模拟器查看效果,本地预览器无法获得设备属性。由于远程模拟器目前仅有手机设备,下图展示在手机横竖屏时的效果,如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

该代码在折叠屏和平板上分别显示两列和三列,以下效果为我模拟而来,供参考,效果如下图:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

3. 挪移布局

光明顶至高武学“乾坤大挪移”。本案例以手机横竖屏切换时通过挪移组件位置,实现在竖屏和横屏状态下充分利用手机屏幕空间展示信息。实现思路:先判断设备的横竖屏状态,然后根据状态决定组件布局方式,示例代码(/pages/index3.ets)如下:

import mediaquery from '@system.mediaquery'
let portraitFunc = null

@Entry
@Component
struct Index3 {
  @State isLandscape: boolean = false
  orientationListener = mediaquery.matchMediaSync('screen and (orientation: landscape)')

  onPortrait(mediaQueryResult) {
    this.isLandscape = mediaQueryResult.matches;
  }

  aboutToAppear() {
    portraitFunc = this.onPortrait.bind(this)
    this.orientationListener.on('change', portraitFunc)
  }

  build() {
    Flex({ direction: (this.isLandscape ? FlexDirection.Row : FlexDirection.Column) }) {
      Column() {
        Image('/common/images/cover.png').width("100%").aspectRatio(1.5)
      }
      Column() {
        Text('本教程由浅入深阐述了鸿蒙3.0方舟开发框架最新编程语言ArkUI(eTS)的基础知识、设计标准、UI排版技巧、自定义多态组件开发、数据模拟,以及分布式全场景开发和异步编程等高级知识。全书共分为4篇:第一篇为拥抱鸿蒙3.0(第1章第4章),第二篇为鸿蒙开发进阶(第5章第9章),第三篇为鸿蒙开发高级(第10章第15章),第四篇为项目实战(第16章第18章)。')
          .fontSize(18)
      }.padding(10).layoutWeight(1)
    }.width('100%').height('100%').backgroundColor('#F1F3F5').padding({top:48, bottom:24,left: 24, right: 24})
  }
}

在远程模拟器上手机竖屏和横屏的效果如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

4. 缩进布局

为了有更好的内容显示效果,可在不同屏幕宽度的设备上进行相应的缩进处理。 比如,卡片在手机竖屏时为4个栅格的宽度;而在手机横屏状态或折叠屏设备上居中显示,且设置为6个栅格的宽度;在平板设备上居中显示,且设置为8个栅格的宽度。示例代码(/pages/index4.ets)如下:

import display from '@ohos.display'

@Entry
@Component
struct Index4 {
  @State girdNum: number = 4 // 栅格数

  aboutToAppear() {
    var displayClass = null;
    display.getDefaultDisplay((err, data) => {
      if (err) {
        return;
      }
      displayClass = data;// 获得设备的屏幕信息,displayClass.width是设备的宽度

      // 栅格断点系统
      if(displayClass.width < vp2px(520)){
        this.girdNum = 4
      }else if(displayClass.width >= vp2px(520) && displayClass.width < vp2px(840)){
        this.girdNum = 8
      }else{
        this.girdNum = 12
      }
    });
  }

  build() {
    Stack({ alignContent: Alignment.Top }){
      Scroll() {
        Column() {
          Image('/common/images/cover.png').width("100%").aspectRatio(1.5)
          Text('本教程由浅入深阐述了鸿蒙3.0方舟开发框架最新编程语言ArkUI(eTS)的基础知识、设计标准、UI排版技巧、自定义多态组件开发、数据模拟,以及分布式全场景开发和异步编程等高级知识。全书共分为4篇:第一篇为拥抱鸿蒙3.0(第1章第4章),第二篇为鸿蒙开发进阶(第5章第9章),第三篇为鸿蒙开发高级(第10章第15章),第四篇为项目实战(第16章第18章)。书中主要内容包括:鸿蒙3.0真的来了、揭开方舟开发框架ArkUI(eTS)的神秘面纱、基础组件、布局组件、事件与手势、绘制组件、让用户界面生动而流畅、基于TS扩展的声明式开发规范、为ArkUI量身打造的多态组件库HUI、访问远程数据、使用mock模拟数据、全场景开发之分布式、多媒体、资源管理、其他高级技术、分布式新闻系统开发准备、App UI快速开发和运行前后端项目。书中包含大量应用示例,不仅可以学会理论知识还可以灵活应用。书中示例基于DevEco Studio 3.0 Beta2 for HarmonyOS环境开发,读者在学习到ArkUI(eTS)语言知识的同时还可学会方舟开发框架技术。书中通过接近商业的一个实战案例详细阐述了如何使用ArkUI(eTS)开发App,内容完整、步骤清晰,提供了工程化的解决方案。本书可作为方舟开发框架ArkUI(eTS)初学者的入门书籍,也可作为从事鸿蒙3.0App开发的技术人员的开发速查手册及培训机构的参考书籍。')
            .fontSize('20fp')
        }.width(this.girdNum==4 ? '100%' : (this.girdNum==8 ? '75%' : '67%'))
      }
    }.width('100%').height('100%').backgroundColor('#F1F3F5').padding({top:48, bottom:24,left: 24, right: 24})
  }
}

在远程模拟器上手机竖屏和横屏的效果如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

前面三小节的代码可到码云下载:

【源码地址:https://gitee.com/cloudev/harmonyos3/tree/master/2.6

2.6.4 使用资源实现组件多态

前面讲解的技巧实现了多设备下布局的“一致性”,使同一份代码的App在不同设备上的布局均有良好表现。同时,适配了竖屏模式和横屏模式的差异。

与追求布局一致性的目标相反,在组件开发中,追求组件在多设备、多语言及“深色模式/浅色模式”的“差异性”。让组件在不同环境中呈现差异化的表现,称之为“多态”。

实现组件“多态”的关键技巧在于使用资源。

1. 资源定义

应用资源由开发者在工程的resources目录中定义,resources目录按照两级目录的形式来组织。

一级目录为base目录、限定词目录以及rawfile目录 。如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

其中:

base目录:默认存在的目录。当应用的resources资源目录中没有与设备状态匹配的限定词目录时,会自动引用该目录中的资源文件。

限定词目录:开发者自行创建的目录。可以由一个或多个表征应用场景或设备特征的限定词组合而成,包括移动国家码和移动网络码、语言、文字、国家或地区、横竖屏、设备类型、颜色模式和屏幕密度等维度。App运行时,优先从限定词目录寻找与当前设备状态匹配的资源(如,中文语言、横屏模式、深色模式),找不到合适资源才会使用base目录中的资源。限定词目录也是实现组件多态的关键。

rawfile目录: 不会根据系统的状态去匹配,rawfile目录中可以直接存放资源文件。

二级目录为资源目录,用于存放字符串、颜色、浮点数等基础元素,以及媒体等资源文件。如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

其中:

boolean.json:存放布尔型资源。

color.json:存放颜色资源。在“/base/element/color.json”目录下存放手机浅色模式和透明模式的各种颜色值。在限定词目录下的相应“color.json”中存放特定场景时生效的颜色资源。如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

float.json:存放间距、圆角、字体等资源。 限定词目录下的“float.json”如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

string.json:存放字符串资源。 原则上,除了从远程服务端数据库里取出的文字外,App中用到的文字显示,尽量设置到“string.json”中,即使是广告图片中存在文字,也建议将图片和文字分离。这样为App实现多语言版减少不必要的麻烦。默认情况下,除了默认的字符串资源,建议增加中文字符串资源和英文字符串资源。如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

meida目录:媒体资源。如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

2. 创建资源

刚创建一个ArkUI eTS项目时,并没有上图中的限定词目录,那么,它们是怎样创建出来的呢?步骤如下:

在resources目录上点击鼠标右键,选择“新建”,然后选择“资源目录”,如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

在左侧的“Available qualifiers”下针对语言、横竖屏、设备、颜色模式(深色/浅色)、屏幕密度等提供各种可选限定词类型,本例以添加对车机的支持为例。选择Device,然后点击向右箭头按钮,如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

Resource type的选项中,一般我们用得最多的是Element。如果是建立媒体资源,则选择Media。其它几种主要是用于Java UI的资源,我们不用关心。如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

然后在Device下选择Car(车机),点击“确认”按钮,如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

此时,在resources目录就建立了“/car/element”的子目录,DevEco Studio中显示为"car.element"。在该目录上使用鼠标右键,选择“新建”,然后选择Element Resource File,如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

在Root element下拉选项中,选择需要的资源文件模板,如果需要建立颜色资源,就选color,文件名也输入"color",如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

点击“确认”按钮,如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

现在,适用于车机的颜色资源“color.json”就成功创建了。如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

建议同学们利用一个Hello World项目,参照上面的步骤,多尝试下不同资源组合,看看都能建立什么样的限定词资源。

3. 资源引用

在工程中,通过“$r(‘app.type.name’)”的形式引用应用资源。“$r”代表resources目录,“app”代表应用内定义的资源,“type”代表资源类型(或资源的存放位置),可以取“color”、“float”、“string”、“media”等,name代表资源命名,由开发者定义资源时确定,如上图中使用"color_1"这个name定义了一个“红色”资源。如果要设置一个文字的颜色为红色,那么可以使用如下代码:

Text('Hello World')
    .fontSize('20fp')
    .fontColor($r("app.color.color_1"))

引用rawfile下资源时使用“$rawfile(‘filename’)”的形式,当前$rawfile仅支持Image控件引用图片资源,filename需要表示为rawfile目录下的文件相对路径,文件名需要包含后缀,路径开头不可以以"/"开头。

2.6.5 ArkUI eTS开发框架

虽然目前ArkUI eTS暂时仅支持手机、折叠屏、平板和车机,但是,通过观察鸿蒙官方的eTS API参考文档,对于每个组件的描述中都有一个支持设备的说明,全部标记对智慧屏和智能穿戴“不支持”,参考下图:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

可以解读出一个信息:对智慧屏和穿戴设备的支持是迟早的事情,否则根本没必要在每个文档中多此一举。所以,在本eTS开发框架中提前提供对智慧屏和智能穿戴设备的支持。

该UI框架命名为“HUI”,“H”是指HarmonyOS。随着后续课程的推进节奏,会持续丰富此框架。第一次提交的项目代码已配置好沉浸式体验的状态栏,同时,根据“2.3 ArkUI App设计规范”建立了配套的应用资源。

【源码地址:https://gitee.com/cloudev/HUI

2.6.6 小试牛刀

基于初始化Hello World页面,使用资源改造页面,代码如下:

@Entry
@Component
struct Index {
  build() {
    Column({space:8}) {
      Text($r("app.string.entry_MainAbility")) // 使用字符串资源输入文字
        .fontColor($r("app.color.fgLevel1")) // 文字颜色,适配深色模式/浅色模式
        .fontSize($r("app.float.fontSizeH6")) // 设置字号为6号标题
        .fontWeight(Number($r("app.float.fontWeightH6"))) // 设置6号标题的字重

      Text($r("app.string.mainability_description")) // 设置正文文本
        .fontColor($r("app.color.fgLevel2")) // 子标题采用辅助色
        .fontSize($r("app.float.fontSizeSubTitle1")) // 设置子标题字号
        .fontWeight(Number($r("app.float.fontWeightSubTitle1"))) // 设置子标题字重

      Image($r("app.media.cover")) // 使用媒体资源
        .width("100%")
        .aspectRatio(1.5)
        .borderRadius($r("app.float.radius_L")) // 图片圆角

      Text($r("app.string.specialColumn")) // 设置正文文本
        .fontColor($r("app.color.fgLevel1")) // 文字颜色
        .fontSize($r("app.float.fontSizeBody1")) // 设置正文字号
        .fontWeight(Number($r("app.float.fontWeightBody1"))) // 设置正文字重
    }
    .width('100%')
    .height('100%')
    .padding({top: $r("app.float.spaceTop"), bottom:$r("app.float.spaceBottom"), left:$r("app.float.spaceLeft"), right: $r("app.float.spaceRight")}) // 屏幕边缘间隔
    .backgroundColor($r("app.color.appBg")) // App背景颜色
  }
}

竖屏手机,中文语言环境,浅色模式:效果如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

竖屏手机,英文语言环境,浅色模式:效果如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

竖屏手机,中文语言环境,深色模式:效果如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

手机横屏,深色模式,如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

折叠屏,竖屏,浅色模式,如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

折叠屏,横屏,深色模式,如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

平板,横屏,浅色模式,如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

平板,竖屏,深色模式,如下图所示:

2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

车机显示效果,如下图所示:
2.6 ArkUI实现一次开发多端部署-鸿蒙开发者社区

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
14
收藏 7
回复
举报
14条回复
按时间正序
/
按时间倒序
小威爱学习
小威爱学习

很详细!感谢分享!

回复
2022-5-16 19:56:21
被占据名字的白日梦
被占据名字的白日梦

谢谢老师分享

回复
2022-5-16 20:17:50
天意林
天意林

请教个:这里面的功能,有什么是Android目前达不到的吗?

回复
2022-6-6 09:49:07
鸿蒙开发之南拳北腿
鸿蒙开发之南拳北腿 回复了 天意林
请教个:这里面的功能,有什么是Android目前达不到的吗?

如果单纯从单机功能来看,和Android没多大差别。但是,鸿蒙的核心魅力在于分布式,可以利用不同设备的特性,赋能为超级终端。因此,在鸿蒙开发过程中,全场景的开发才是精髓所在。

1
回复
2022-6-6 15:51:57
wx62a0536bed05b
wx62a0536bed05b

老师,示例的list.ets中,matchMediaSync显示黄色,提示mediaquery无此属性。如图:

老师,这个怎么办?

 

回复
2022-6-9 15:35:32
鸿蒙开发之南拳北腿
鸿蒙开发之南拳北腿 回复了 wx62a0536bed05b
老师,示例的list.ets中,matchMediaSync显示黄色,提示mediaquery无此属性。如图: 老师,这个怎么办?

import mediaquery from '@system.mediaquery'

这个有没有引用?

回复
2022-6-9 18:14:59
wx62a0536bed05b
wx62a0536bed05b 回复了 鸿蒙开发之南拳北腿
import mediaquery from '@system.mediaquery' 这个有没有引用?

已经引用了。

不过,虽然显示黄底提示,远程预览时还是能正常执行的,不影响效果。

谢谢老师。

1
回复
2022-6-10 08:46:23
鸿蒙开发之南拳北腿
鸿蒙开发之南拳北腿 回复了 wx62a0536bed05b
已经引用了。 不过,虽然显示黄底提示,远程预览时还是能正常执行的,不影响效果。 谢谢老师。

目前ArkUI eTS还处于beta版阶段,只要不影响功能,暂时不用太在意,官方应该会有优化的。

回复
2022-6-10 15:24:13
wx62a0536bed05b
wx62a0536bed05b

老师,我又遇到了一个问题,就是“.fontWeight(Number($r("app.float.fontWeightH6")))”这种设置没起作用,改变float.json里fontWeightH6的值如从500改为700或者900,显示都没有变化,不论是本地预览器还是远程模拟器,都是这样。难道又是beta版Bug?

回复
2022-6-16 08:43:36
鸿蒙开发之南拳北腿
鸿蒙开发之南拳北腿

应该不是Bug。这里有两个点提醒一下:
1.fontWeightH6的值是根据鸿蒙的相关规范设置的,不建议修改。

2.如果仅仅是为了做实验,修改了这个值,因为它是资源,想要生效,需要重新编译一次。

如果重新编译后还不行,方便的话,将你的代码发给我,我帮你调试一下看看。

回复
2022-6-16 13:56:53
Whyalone
Whyalone

强!!

回复
2022-6-16 16:36:47
鸿蒙开发之南拳北腿
鸿蒙开发之南拳北腿 回复了 Whyalone
强!!

哈哈,谢谢鼓励!

回复
2022-6-16 16:39:24
wx62a0536bed05b
wx62a0536bed05b 回复了 鸿蒙开发之南拳北腿
应该不是Bug。这里有两个点提醒一下:1.fontWeightH6的值是根据鸿蒙的相关规范设置的,不建议修改。 2.如果仅仅是为了做实验,修改了这个值,因为它是资源,想要生效,需要重新编译一次。 如果重新编译后还不行,方便的话,将你的代码发给我,我帮你调试一下看看。

    老师,我是实验而已,不会修改fontWeightH6的值的。我是试着把fontWeightH6的值改为900,结果发现,文本并没有变粗。

    我发现,这种通过float.json资源设置fontWeight值都没有生效,也就是没有效果,文本的字重没有变化,还是默认的样子,不管是系统定义的还是自定义的,都一样。当然,直接在ets文件里设置如fontWeight(900)是有效的, 而.fontWeight(Number($r("app.float.***")))则无效。无关编译,因为重新编译,或者设置后关闭项目重新启动,都不行。不是我的代码问题,我就在你的HUI项目上测试也不行。

    难道是我电脑有问题?

1
回复
2022-6-16 21:31:42
鸿蒙开发之南拳北腿
鸿蒙开发之南拳北腿 回复了 wx62a0536bed05b
老师,我是实验而已,不会修改fontWeightH6的值的。我是试着把fontWeightH6的值改为900,结果发现,文本并没有变粗。 我发现,这种通过float.json资源设置fontWeight值都没有生效,也就是没有效果,文本的字重没有变化,还是默认的样子,不管是系统定义的还是自定义的,都一样。当然,直接在ets文件里设置如fontWeight(900)是有效的, 而.fontWeight(Number($r("app.float.***")))则无效。无关编译,因为重新...

不是你的电脑问题,我这边也复现了这个问题。同时,今天我写教程的时候发现Counter()组件代码不被识别,之前没这个问题。可以先不管这个问题。

 

我会继续关注你提的这个问题。

1
回复
2022-6-16 21:56:40
回复
    相关推荐