本节通过栅格化布局、自适应布局、响应式布局和使用资源,从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)。效果如下图所示:
注意:这个设置也能同时适配“平板/车机/智慧屏/智能穿戴”等设备,并且切换为横屏时也是适配的。
接下来,演示自适应拉伸布局下设置子组件大小和位置的两个技巧。
第一个技巧:子组件不刻意设置宽度或设置绝对宽度值,子组件间使用“Blank”组件填充,使Text组件和Button组件贴近左、右边缘。在前面代码的基础上加入如下代码:
Row() {
Text('鸿蒙开发ArkUI最佳实践')
.fontSize(20)
Blank() // 使Text组件和Button组件贴近左、右边缘
Button('订阅')
}
.width('100%')
.padding(10)
.backgroundColor('#FFFFFF')
效果如下图所示:
后面章节讲解“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”的比例瓜分了父级容器的宽度。效果如下图所示:
注意:"layoutWeight"仅适用于“Flex/Row/Column”布局组件下的子组件。
2. 自适应缩放
对于图片的展示,可以锁定宽高比例,同时将宽设置为百分比的值,实现自适应缩放,示例代码如下:
Image('/common/images/cover.png')
.width('100%')
.aspectRatio(1.5) // 指定当前组件的宽高比
效果如下图:
注意:上述锁定图片的宽高比时建议使用“.aspectRatio(1.5)”这个属性,其中宽高比值“1.5”要根据具体图片的实际宽高比确定。暂时不要使用“.objectFit(ImageFit.Contain)”这个属性来保持图片组件的宽高比。经测试,在本场景下会在图片组件下产生不可控的间距。可能是一个Bug,希望ArkUI eTS正式版时鸿蒙官方能修复这个问题。代码如下:
Image('/common/images/cover.png')
.width('100%')
.objectFit(ImageFit.Contain) // 保持宽高比进行缩小或者放大,使得图片完全显示在显示边界内
效果如下图所示:
注意:为了图片在缩放状态下都显示清晰,图片尺寸优先考虑在较大屏幕时能显示清晰。避免小屏清晰,大屏马赛克。
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) // 父级容器不设置宽度,被子组件撑开
效果如下图所示:
实际项目中,我们会配合“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. 重复布局
利用屏幕的宽度优势,将相同属性的组件横向并列排布。 一般用于卡片陈列。我的实现思路是:先通过"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})
}
}
该效果需要通过远程模拟器查看效果,本地预览器无法获得设备属性。由于远程模拟器目前仅有手机设备,下图展示在手机横竖屏时的效果,如下图所示:
该代码在折叠屏和平板上分别显示两列和三列,以下效果为我模拟而来,供参考,效果如下图:
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})
}
}
在远程模拟器上手机竖屏和横屏的效果如下图所示:
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})
}
}
在远程模拟器上手机竖屏和横屏的效果如下图所示:
前面三小节的代码可到码云下载:
【源码地址:https://gitee.com/cloudev/harmonyos3/tree/master/2.6 】
2.6.4 使用资源实现组件多态
前面讲解的技巧实现了多设备下布局的“一致性”,使同一份代码的App在不同设备上的布局均有良好表现。同时,适配了竖屏模式和横屏模式的差异。
与追求布局一致性的目标相反,在组件开发中,追求组件在多设备、多语言及“深色模式/浅色模式”的“差异性”。让组件在不同环境中呈现差异化的表现,称之为“多态”。
实现组件“多态”的关键技巧在于使用资源。
1. 资源定义
应用资源由开发者在工程的resources目录中定义,resources目录按照两级目录的形式来组织。
一级目录为base目录、限定词目录以及rawfile目录 。如下图所示:
其中:
base目录:默认存在的目录。当应用的resources资源目录中没有与设备状态匹配的限定词目录时,会自动引用该目录中的资源文件。
限定词目录:开发者自行创建的目录。可以由一个或多个表征应用场景或设备特征的限定词组合而成,包括移动国家码和移动网络码、语言、文字、国家或地区、横竖屏、设备类型、颜色模式和屏幕密度等维度。App运行时,优先从限定词目录寻找与当前设备状态匹配的资源(如,中文语言、横屏模式、深色模式),找不到合适资源才会使用base目录中的资源。限定词目录也是实现组件多态的关键。
rawfile目录: 不会根据系统的状态去匹配,rawfile目录中可以直接存放资源文件。
二级目录为资源目录,用于存放字符串、颜色、浮点数等基础元素,以及媒体等资源文件。如下图所示:
其中:
boolean.json:存放布尔型资源。
color.json:存放颜色资源。在“/base/element/color.json”目录下存放手机浅色模式和透明模式的各种颜色值。在限定词目录下的相应“color.json”中存放特定场景时生效的颜色资源。如下图所示:
float.json:存放间距、圆角、字体等资源。 限定词目录下的“float.json”如下图所示:
string.json:存放字符串资源。 原则上,除了从远程服务端数据库里取出的文字外,App中用到的文字显示,尽量设置到“string.json”中,即使是广告图片中存在文字,也建议将图片和文字分离。这样为App实现多语言版减少不必要的麻烦。默认情况下,除了默认的字符串资源,建议增加中文字符串资源和英文字符串资源。如下图所示:
meida目录:媒体资源。如下图所示:
2. 创建资源
刚创建一个ArkUI eTS项目时,并没有上图中的限定词目录,那么,它们是怎样创建出来的呢?步骤如下:
在resources目录上点击鼠标右键,选择“新建”,然后选择“资源目录”,如下图所示:
在左侧的“Available qualifiers”下针对语言、横竖屏、设备、颜色模式(深色/浅色)、屏幕密度等提供各种可选限定词类型,本例以添加对车机的支持为例。选择Device,然后点击向右箭头按钮,如下图所示:
Resource type的选项中,一般我们用得最多的是Element。如果是建立媒体资源,则选择Media。其它几种主要是用于Java UI的资源,我们不用关心。如下图所示:
然后在Device下选择Car(车机),点击“确认”按钮,如下图所示:
此时,在resources目录就建立了“/car/element”的子目录,DevEco Studio中显示为"car.element"。在该目录上使用鼠标右键,选择“新建”,然后选择Element Resource File,如下图所示:
在Root element下拉选项中,选择需要的资源文件模板,如果需要建立颜色资源,就选color,文件名也输入"color",如下图所示:
点击“确认”按钮,如下图所示:
现在,适用于车机的颜色资源“color.json”就成功创建了。如下图所示:
建议同学们利用一个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参考文档,对于每个组件的描述中都有一个支持设备的说明,全部标记对智慧屏和智能穿戴“不支持”,参考下图:
可以解读出一个信息:对智慧屏和穿戴设备的支持是迟早的事情,否则根本没必要在每个文档中多此一举。所以,在本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背景颜色
}
}
竖屏手机,中文语言环境,浅色模式:效果如下图所示:
竖屏手机,英文语言环境,浅色模式:效果如下图所示:
竖屏手机,中文语言环境,深色模式:效果如下图所示:
手机横屏,深色模式,如下图所示:
折叠屏,竖屏,浅色模式,如下图所示:
折叠屏,横屏,深色模式,如下图所示:
平板,横屏,浅色模式,如下图所示:
平板,竖屏,深色模式,如下图所示:
车机显示效果,如下图所示: