未验证 提交 8d09ed80 编写于 作者: O openharmony_ci 提交者: Gitee

!9901 一多文档修改

Merge pull request !9901 from zengyawen/master
# 一次开发,多端部署 # 一次开发,多端部署
- [前言](about-this-document.md) - [前言](foreword.md)
- [简介](introduction.md) - [简介](introduction.md)
- [从一个例子开始](start-with-a-example.md)
- 应用UX设计 - 应用UX设计
- [设计原则和要点](design-principles.md) - [设计原则和要点](design-principles.md)
- 应用架构设计 - 应用架构设计
- [应用导航结构设计要求](navigation-design.md) - [应用导航结构设计要求](navigation-design.md)
- [应用页面结构设计](page-design.md) - [应用页面结构设计](page-design.md)
- 界面布局 - 界面布局
- [概述](layout-design-intro.md) - [概述](interface-layout-design-intro.md)
- 布局基础 - 布局基础
- [栅格系统](layout-grid.md) - [栅格系统](design-grid.md)
- [自适应布局](layout-adaptive.md) - [自适应布局](design-adaptive-layout.md)
- [响应式布局](layout-responsive.md) - [响应式布局](design-responsive-layout.md)
- [布局基础运用案例](layout-design-cases.md) - [布局基础运用案例](design-layout-cases.md)
- 人机交互 - 人机交互
- [交互基础](interaction-basics.md) - [交互基础](interaction-basics.md)
- [常见输入方式](common-input-modes.md) - [常见输入方式](common-input-modes.md)
- [交互事件归一](design-interaction-events-unification.md) - [交互事件归一](design-interaction-event-normalization.md)
- 视觉风格 - 视觉风格
- [视觉基础](visual-style-basics.md) - [视觉基础](visual-basics.md)
- [色彩](visual-style-color.md) - [色彩](visual-style-color.md)
- [字体](visual-style-font.md) - [字体](visual-style-font.md)
- [图标](visual-style-icon.md) - [图标](visual-style-icon.md)
- [多态控件](design-polymorphic-components.md) - [多态控件](design-polymorphic-controls.md)
- [设计自检表](design-checklist.md) - [设计自检表](design-checklist.md)
- [资源](resource.md) - [设计交付](design-delivery.md)
- [IDE使用](ide-usage.md) - [资源](design-resources.md)
- 一多能力的页面开发介绍 - [工程管理](ide-using.md)
- 页面开发的一多能力介绍
- [简介](page-development-intro.md)
- 布局能力 - 布局能力
- [布局能力简介](layout-intro.md) - [布局简介](layout-intro.md)
- 自适应布局 - [自适应布局](adaptive-layout.md)
- [自适应布局简介](adaptive-layout-intro.md) - [响应式布局](responsive-layout.md)
- [拉伸能力](adaptive-layout-stretching.md) - [典型布局场景](typical-layout-scenario.md)
- [均分能力](adaptive-layout-equalization.md) - 典型页面场景
- [占比能力](adaptive-layout-proportion.md) - [应用市场首页](appgallery-home-page.md)
- [缩放能力](adaptive-layout-scaling.md) - [音乐专辑页](music-album-page.md)
- [延伸能力](adaptive-layout-extension.md) - [交互归一](interaction-event-normalization.md)
- [隐藏能力](adaptive-layout-hiding.md) - [多态组件](polymorphic-controls.md)
- [折行能力](adaptive-layout-wrapping.md)
- 响应式布局
- [栅格断点系统](grid-breakpoint.md)
- [媒体查询](media-query.md)
- [典型场景](responsive-layout-cases.md)
- [交互归一](interaction-events-unification.md)
- [多态组件](polymorphic-components.md)
- [资源使用](resource-usage.md) - [资源使用](resource-usage.md)
- [一多能力的功能开发介绍](development-intro.md) - [功能开发的一多能力介绍](development-intro.md)
- 案例应用 - [案例应用](case.md)
- 短信应用 - [常见问题](faq.md)
- [概览](sms-intro.md)
- 会话详情页面
- [页面结构](sms-session-page-structure.md)
- [顶部标题栏](sms-session-page-title-bar.md)
- [底部输入栏](sms-session-page-input-field.md)
- [信息列表](sms-session-page-message-list.md)
- [组合成型](sms-session-page-combined.md)
- [总结](sms-session-summary.md)
- [桌面应用](cases-home-screen.md)
- [常见问题](faqs.md)
# 前言
本指导的目的是快速及全面地指导读者使用OpenHarmony提供的“一次开发,多端部署”(本指导中简称“一多”)能力开发多设备应用。在应用开发前,开发者应尽可能全面考虑应用支持多设备的情况,避免在后期加入新的类型设备时需要对应用架构进行大幅调整。
## 本指导面向的读者
本指导适合开发OpenHarmony应用的UX设计师、应用开发人员,本指导书统称为“应用开发者”。这两者是根据开发角色不同而进行的区分。对于个人或者规模较小的应用,两者可能是同一个个体,但应当从角色上加以区分。
## 如何阅读本指导
应用在需求明确后,开发过程大致分为:应用设计(包含界面UX设计、业务功能设计)- 工程设计和创建 - 功能代码实现。本指导也是基于这个流程进行的内容编排。
阅读本文档时,应尽量按照章节顺序进行阅读。如果区分开发角色,那么UX设计师可以仅阅读第3章,而开发人员可以从第4章开始阅读。但无论何种角色,我们强烈建议阅读第2章。
本文档各章节简介如下:
- 第1章[前言](about-this-document.md)说明本指导面向的读者、如何阅读本指导等,指引读者更好地阅读。
- 第2章[简介](introduction.md)简短介绍了“一多”的背景、定义、目标、以及用于指导后续开发的一些基础知识。
- 第3章[应用UX设计](design-principles.md)介绍了应用UX设计理念。主要阐述了应用设计之初UX设计的原则和要点。该章节主要面向应用的UX设计师。
UX设计原则应该考虑多设备的“差异性” 、“一致性”、“灵活性”、“兼容性”。
UX设计要点则从6个方面阐述如何进行多设备应用设计,分别是“自适应应用架构”、“响应式界面布局”、“交互归一”、“视觉参数化”、“多态控件”、“针对性优化”。
最后,给出设计自检表,用于检查应用UX设计是否合理。
- 第4章[IDE使用](ide-usage.md)介绍了从工程角度如何开始开发应用,这非常有用,让读者可以直接上手创建多设备应用的工程,是后面学习“一多”能力的上手基础。
- 第5章[一多能力的页面开发介绍](layout-intro.md)、第6章[一多能力的功能开发介绍](development-intro.md)介绍了OpenHarmony提供的“一多”能力,其中每个能力都提供了代码示例和UX效果,让读者可以快速学习“一多”能力。
- 第7章提供了两个案例应用,阐述了从应用设计到开发这一过程中如何实践前面章节介绍的设计思路或“一多”能力,让读者可以整体上掌握“一多”在应用开发过程中的知识。
- 第8章[常见问题](faqs.md)提供了常见问题(FAQ),方便读者查阅。
本指导在介绍过程中还包括一些“说明”。这些“说明”,表示例外情况或者额外信息的补充。
# 均分能力
均分能力是指容器组件尺寸发生变化时,增加或减小的空间均匀分配给容器组件内所有空白区域。它常用于内容数量固定、均分显示的场景,比如工具栏、底部菜单栏等。
均分能力通常通过使用 **Flex均分布局**[Flex组件](../../reference/arkui-ts/ts-container-flex.md)的justifyContent属性设置为FlexAlign.SpaceEvenly)实现,即子元素在Flex主轴方向等间距布局,相邻元素之间的间距、第一个元素与行首的间距、最后一个元素到行尾的间距都完全一样。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 均分能力是特殊场景下拉伸能力的简单实现。也可以借助其它方式实现均分能力,如在每个组件间添加Blank组件等。
## 示例:
父容器尺寸变化过程中,图标及文字的尺寸不变,图标间的间距及图标离左右边缘的距离同时均等改变。
![zh-cn_image_0000001291935425](figures/zh-cn_image_0000001291935425.gif)
```ts
@Entry
@Component
struct EquipartitionCapabilitySample {
const list: number [] = [0, 1, 2, 3];
@State rate: number = 0.6;
// 底部滑块,可以通过拖拽滑块改变容器尺寸
@Builder slider() {
Slider({ value: this.rate * 100, min: 30, max: 60, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.onChange((value: number) => {
this.rate = value / 100;
})
.position({ x: '20%', y: '80%' })
}
build() {
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Column() {
// 均匀分配父容器主轴方向的剩余空间
Flex({ justifyContent: FlexAlign.SpaceEvenly }) {
ForEach(this.list, (item) => {
Column() {
Image($r("app.media.icon")).width(48).height(48).margin({ top: 8 })
Text('App name')
.width(64)
.height(30)
.lineHeight(15)
.fontSize(12)
.textAlign(TextAlign.Center)
.margin({ top: 8 })
.padding({ bottom: 15 })
}.width(80).height(102)
})
}
// 均匀分配父容器主轴方向的剩余空间
Flex({ justifyContent: FlexAlign.SpaceEvenly }) {
ForEach(this.list, (item) => {
Column() {
Image($r("app.media.icon")).width(48).height(48).margin({ top: 8 })
Text('App name')
.width(64)
.height(30)
.lineHeight(15)
.fontSize(12)
.textAlign(TextAlign.Center)
.margin({ top: 8 })
.padding({ bottom: 15 })
}.width(80).height(102)
})
}
}
.width(this.rate * 100 + '%')
.height(222)
.padding({ top: 16 })
.backgroundColor('#FFFFFF')
.borderRadius(16)
this.slider()
}
.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
}
}
```
# 延伸能力
延伸能力是指容器组件内的子组件,按照其在列表中的先后顺序,随容器组件尺寸变化显示或隐藏。它可以根据显示区域的尺寸,显示不同数量的元素。
延伸能力通常有两种实现方式:
- 通过[List组件](../../reference/arkui-ts/ts-container-list.md)实现。
- 通过[Scroll组件](../../reference/arkui-ts/ts-container-scroll.md)配合[Row组件](../../reference/arkui-ts/ts-container-row.md)[Column组件](../../reference/arkui-ts/ts-container-column.md)实现。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> List、Row或Column组件中子节点的在页面显示时就已经全部完成了布局计算及渲染,只不过受限于父容器尺寸,用户只能看到一部分。随着父容器尺寸增大,用户可以看到的子节点数目也相应的增加。用户还可以通过手指滑动触发列表滑动,查看被隐藏的子节点。
## 示例:
当父容器的尺寸发生改变时,页面中显示的图标数量随之发生改变。
分别通过List组件实现及通过Scroll组件配合Row组件实现。
![zh-cn_image_0000001245295918](figures/zh-cn_image_0000001245295918.gif)
(1)通过List组件实现。
```ts
@Entry
@Component
struct ExtensionCapabilitySample1 {
@State rate: number = 0.60;
const appList: number [] = [0, 1, 2, 3, 4, 5, 6, 7];
// 底部滑块,可以通过拖拽滑块改变容器尺寸
@Builder slider() {
Slider({ value: this.rate * 100, min: 8, max: 60, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.height(50)
.onChange((value: number) => {
this.rate = value / 100;
})
.position({ x: '20%', y: '80%' })
}
build() {
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Row({ space: 10 }) {
// 通过List组件实现隐藏能力
List({ space: 10 }) {
ForEach(this.appList, (item) => {
ListItem() {
Column() {
Image($r("app.media.icon")).width(48).height(48).margin({ top: 8 })
Text('App name')
.width(64)
.height(30)
.lineHeight(15)
.fontSize(12)
.textAlign(TextAlign.Center)
.margin({ top: 8 })
.padding({ bottom: 15 })
}.width(80).height(102)
}.width(80).height(102)
})
}
.padding({ top: 16, left: 10 })
.listDirection(Axis.Horizontal)
.width('100%')
.height(118)
.borderRadius(16)
.backgroundColor(Color.White)
}
.width(this.rate * 100 + '%')
this.slider()
}
.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
}
}
```
(2)通过Scroll组件配合Row组件实现。
```ts
@Entry
@Component
struct ExtensionCapabilitySample2 {
private scroller: Scroller = new Scroller()
@State rate: number = 0.60;
@State appList: number [] = [0, 1, 2, 3, 4, 5, 6, 7];
// 底部滑块,可以通过拖拽滑块改变容器尺寸
@Builder slider() {
Slider({ value: this.rate * 100, min: 8, max: 60, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.height(50)
.onChange((value: number) => {
this.rate = value / 100;
})
.position({ x: '20%', y: '80%' })
}
build() {
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
// 通过Scroll和Row组件实现隐藏能力
Scroll(this.scroller) {
Row({ space: 10 }) {
ForEach(this.appList, () => {
Column() {
Image($r("app.media.icon")).width(48).height(48).margin({ top: 8 })
Text('App name')
.width(64)
.height(30)
.lineHeight(15)
.fontSize(12)
.textAlign(TextAlign.Center)
.margin({ top: 8 })
.padding({ bottom: 15 })
}.width(80).height(102)
})
}
.padding({ top: 16, left: 10 })
.height(118)
.borderRadius(16)
.backgroundColor(Color.White)
}
.scrollable(ScrollDirection.Horizontal)
.width(this.rate * 100 + '%')
this.slider()
}
.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
}
}
```
# 隐藏能力
隐藏能力是指容器组件内的子组件,按照其预设的显示优先级,随容器组件尺寸变化显示或隐藏,其中相同显示优先级的子组件同时显示或隐藏。它是一种比较高级的布局方式,常用于分辨率变化较大,且不同分辨率下显示内容有所差异的场景。主要思想是通过增加或减少显示内容,来保持最佳的显示效果。
隐藏能力通过设置**布局优先级**(displayPriority属性)来控制显隐,当布局主轴方向剩余尺寸不足以满足全部元素时,按照布局优先级大小,从小到大依次隐藏,直到容器能够完整显示剩余元素。具有相同布局优先级的元素将同时显示或者隐藏。
可以访问[布局约束](../../reference/arkui-ts/ts-universal-attributes-layout-constraints.md),了解displayPriority属性的详细信息。
## 示例:
父容器尺寸发生变化时,其子元素按照预设的优先级显示或隐藏。
![zh-cn_image_0000001245136646](figures/zh-cn_image_0000001245136646.gif)
```ts
@Entry
@Component
struct HiddenCapabilitySample {
@State rate: number = 0.45;
// 底部滑块,可以通过拖拽滑块改变容器尺寸
@Builder slider() {
Slider({ value: this.rate * 100, min: 10, max: 45, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.height(50)
.onChange((value: number) => {
this.rate = value / 100;
})
.position({ x: '20%', y: '80%' })
}
build() {
Flex({ direction: FlexDirection.Column,
justifyContent: FlexAlign.Center,
alignItems: ItemAlign.Center }) {
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Image($r("app.media.favorite"))
.width(48)
.height(48)
.objectFit(ImageFit.Contain)
.margin({ left: 12, right: 12 })
.displayPriority(1) // 布局优先级
Image($r("app.media.down"))
.width(48)
.height(48)
.objectFit(ImageFit.Contain)
.margin({ left: 12, right: 12 })
.displayPriority(2) // 布局优先级
Image($r("app.media.pause"))
.width(48)
.height(48)
.objectFit(ImageFit.Contain)
.margin({ left: 12, right: 12 })
.displayPriority(3) // 布局优先级
Image($r("app.media.next"))
.width(48)
.height(48)
.objectFit(ImageFit.Contain)
.margin({ left: 12, right: 12 })
.displayPriority(2) // 布局优先级
Image($r("app.media.list"))
.width(48)
.height(48)
.objectFit(ImageFit.Contain)
.margin({ left: 12, right: 12 })
.displayPriority(1) // 布局优先级
}
.width(this.rate * 100 + '%')
.height(96)
.borderRadius(16)
.backgroundColor('#FFFFFF')
this.slider()
}
.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
}
}
```
# 自适应布局简介
多设备间最大的差异是屏幕分辨率,差异分辨率适配离不开自适应布局的能力。针对常见的开发场景,方舟开发框架提炼了七种自适应布局能力。这些布局可以独立使用,也可多种布局叠加使用。
| 自适应布局类别 | 自适应布局能力 | 使用场景 |
| -------- | -------- | -------- |
| 自适应拉伸 | [拉伸能力](adaptive-layout-stretching.md) | 容器组件尺寸发生变化时,增加或减小的空间**全部分配**给容器组件内**指定区域**。 |
| [均分能力](adaptive-layout-equalization.md) | 容器组件尺寸发生变化时,增加或减小的空间**均匀分配**给容器组件内**所有空白区域**。 ||
| 自适应缩放 | [占比能力](adaptive-layout-proportion.md) | 子组件的宽高**按照预设的比例**,随容器组件发生变化。 |
| [缩放能力](adaptive-layout-scaling.md) | 子组件的宽高**按照预设的比例**,随容器组件发生变化,且变化过程中子组件的**宽高比不变**。 ||
| 自适应延伸 | [延伸能力](adaptive-layout-extension.md) | 容器组件内的子组件,按照其**在列表中的先后顺序**,随容器组件尺寸变化显示或隐藏。 |
| [隐藏能力](adaptive-layout-hiding.md) | 容器组件内的子组件,按照其**预设的显示优先级**,随容器组件尺寸变化显示或隐藏。**相同显示优先级的子组件同时显示或隐藏**。 ||
| 自适应折行 | [折行能力](adaptive-layout-wrapping.md) | 容器组件尺寸发生变化时,如果布局方向尺寸不足以显示完整内容,**自动换行**。 |
下面我们依次介绍这几种自适应布局能力。
# 占比能力
占比能力是指子组件的宽高按照预设的比例,随父容器组件发生变化。
占比能力通常有两种实现方式:
- 将子组件的宽高设置为父组件宽高的百分比,详见[尺寸设置](../../reference/arkui-ts/ts-universal-attributes-size.md)[长度类型](../../ui/ts-types.md#长度类型)
- 通过layoutWeight属性配置互为兄弟关系的组件在父容器主轴方向的布局权重,详见[尺寸设置](../../reference/arkui-ts/ts-universal-attributes-size.md)
- 当父容器尺寸确定时,其子组件按照开发者配置的权重比例分配父容器中主轴方向的空间。
- 仅当父容器是Row、Colomn或者Flex时,layoutWeight属性才会生效。
- 设置layoutWeight属性后,组件本身的尺寸会失效。比如同时设置了.width('40%')和.layoutWeight(1),那么只有.layoutWeight(1)会生效。
layoutWeight存在使用限制,所以实际使用过程中大多通过将子组件宽高设置为父组件的百分比来实现占比能力。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 占比能力在实际开发中使用的非常广泛,可以通过很多不同的方式实现占比能力,如还可以通过[Grid组件](../../reference/arkui-ts/ts-container-grid.md)的columnsTemplate属性设置网格容器中列的数量及其宽度比例,或通过配置子组件在栅格(本章后文将详细介绍栅格系统)中占据不同的列数来实现占比能力。本小节仅介绍最基础和常用的实现方式,局限性较大或比非常小众的实现方式,本文不再展开介绍。
## 示例:
简单的播放控制栏,其中“上一首”、“播放/暂停”、“下一首”的layoutWeight属性都设置为1,因此它们按照“1:1:1”的比例均分父容器主轴方向的空间。
将三个按钮的.layoutWeight(1)分别替换为.width('33%')、.width('34%')、.width('33%'),也可以实现与当前同样的显示效果。
![zh-cn_image_0000001292374353](figures/zh-cn_image_0000001292374353.gif)
```ts
@Entry
@Component
struct ProportionCapabilitySample {
@State rate: number = 0.5;
// 底部滑块,可以通过拖拽滑块改变容器尺寸
@Builder slider() {
Slider({ value: 100, min: 25, max: 50, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.height(50)
.onChange((value: number) => {
this.rate = value / 100;
})
.position({ x: '20%', y: '80%' })
}
build() {
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Column() {
Row() {
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Image($r("app.media.down"))
.width(48)
.height(48)
}
.height(96)
.layoutWeight(1) // 设置子组件在父容器主轴方向的布局权重
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Image($r("app.media.pause"))
.width(48)
.height(48)
}
.height(96)
.layoutWeight(1) // 设置子组件在父容器主轴方向的布局权重
.backgroundColor('#66F1CCB8')
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Image($r("app.media.next"))
.width(48)
.height(48)
}
.height(96)
.layoutWeight(1) // 设置子组件在父容器主轴方向的布局权重
}
.width(this.rate * 100 + '%')
.height(96)
.borderRadius(16)
.backgroundColor('#FFFFFF')
}
this.slider()
}
.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
}
}
```
# 缩放能力
缩放能力是指子组件的宽高按照预设的比例,随容器组件发生变化,且变化过程中子组件的宽高比不变。
缩放能力通过使用百分比布局配合**固定宽高比**(aspectRatio属性)实现当容器尺寸发生变化时,内容自适应调整。
可以访问[布局约束](../../reference/arkui-ts/ts-universal-attributes-layout-constraints.md),了解aspectRatio属性的详细信息。
## 示例:
为方便查看效果,示例中特意给Column组件加了边框。可以看到Column组件随着其Flex父组件尺寸变化而缩放的过程中,始终保持预设的宽高比,其中的图片也始终正常显示。
![zh-cn_image_0000001245614634](figures/zh-cn_image_0000001245614634.gif)
```ts
@Entry
@Component
struct ScaleCapabilitySample {
@State width: number = 400;
@State height: number = 400;
// 底部滑块,可以通过拖拽滑块改变容器尺寸
@Builder slider() {
Slider({ value: this.width, min: 100, max: 400, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.height(50)
.onChange((value: number) => {
this.width = value;
})
.position({ x: '20%', y: '80%' })
Slider({ value: this.height, min: 100, max: 400, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.height(50)
.onChange((value: number) => {
this.height = value;
})
.position({ x: '20%', y: '87%' })
}
build() {
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Column() {
Image($r("app.media.illustrator")).width('100%').height('100%')
}
.aspectRatio(1) // 固定宽高比
.border({ width: 2, color: "#66F1CCB8"}) // 边框,仅用于展示效果
}
.backgroundColor("#FFFFFF")
.height(this.width)
.width(this.height)
this.slider()
}.width('100%')
.height('100%')
.backgroundColor("#F1F3F5")
}
}
```
# 拉伸能力
拉伸能力是指容器组件尺寸发生变化时,增加或减小的空间全部分配给容器组件内指定区域。
拉伸能力通常通过Flex布局的flexGrow和flexShrink属性实现,详见[Flex布局](../../reference/arkui-ts/ts-universal-attributes-flex-layout.md)
- flexGrow:仅当父容器宽度大于所有子组件宽度的总和时,该属性生效。配置了此属性的子组件,按照比例拉伸,分配父容器的多余空间。
- flexShrink:仅当父容器宽度小于所有子组件宽度的总和时,该属性生效。配置了此属性的子组件,按照比例收缩,分配父容器的不足空间。
特别的,当开发者期望将父容器的剩余空间全部分配给某空白区域时,也可以通过Blank组件实现。注意仅当父组件为Row\Column\Flex组件时,Blank组件才会生效,详见[Blank组件](../../reference/arkui-ts/ts-basic-components-blank.md)
## 示例1:
本示例中的页面由中间的内容区(包含一张图片)以及两侧的留白区组成。
中间内容区的宽度设置为400vp,同时将flexGrow属性设置为1,flexShrink属性设置为0。
两侧留白区的宽度设置为150vp,同时将flexGrow属性设置为0,flexShrink属性设置为1。
因此父容器的基准尺寸是700vp(150vp+400vp+150vp)。
当父容器的尺寸大于700vp时,父容器中多余的空间全部分配给中间内容区。
当父容器的尺寸小于700vp时,左右两侧的留白区按照“1:1”的比例收缩(即平均分配父容器的不足空间)。
![zh-cn_image_0000001245613530](figures/zh-cn_image_0000001245613530.gif)
```ts
@Entry
@Component
struct FlexibleCapabilitySample1 {
@State width: number = 402;
// 底部滑块,可以通过拖拽滑块改变容器尺寸。
@Builder slider() {
Slider({ value: this.width, min: 402, max: 1000, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.onChange((value: number) => {
this.width = value;
})
.position({ x: '20%', y: '80%' })
}
build() {
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center,
alignItems: ItemAlign.Center }) {
Column() {
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Center,
alignItems: ItemAlign.Center }) {
// 通过flexGrow和flexShink属性,将多余的空间全部分配给图片,将不足的控件全部分配给两侧空白区域。
Row().width(150).height(400).backgroundColor('#FFFFFF')
.flexGrow(0).flexShrink(1)
Image($r("app.media.illustrator")).width(400).height(400)
.objectFit(ImageFit.Contain)
.backgroundColor("#66F1CCB8")
.flexGrow(1).flexShrink(0)
Row().width(150).height(400).backgroundColor('#FFFFFF')
.flexGrow(0).flexShrink(1)
}.width(this.width)
}
this.slider()
}.width('100%').height('100%').backgroundColor('#F1F3F5')
}
}
```
## 示例2:
文字和开关的尺寸固定,仅有中间空白区域(Blank组件)随父容器尺寸变化而伸缩。
![zh-cn_image_0000001266042114](figures/zh-cn_image_0000001266042114.gif)
```ts
@Entry
@Component
struct FlexibleCapabilitySample2 {
@State rate: number = 0.8;
// 底部滑块,可以通过拖拽滑块改变容器尺寸
@Builder slider() {
Slider({ value: this.rate * 100, min: 30, max: 80, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.onChange((value: number) => {
this.rate = value / 100;
})
.position({ x: '20%', y: '80%' })
}
build() {
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center,
alignItems: ItemAlign.Center }) {
Column() {
Row() {
Text('飞行模式')
.fontSize(16)
.width(135)
.height(22)
.fontWeight(FontWeight.Medium)
.lineHeight(22)
Blank() // 通过Blank组件实现拉伸能力
Toggle({ type: ToggleType.Switch })
.width(36)
.height(20)
}
.height(55)
.borderRadius(12)
.padding({ left: 13, right: 13 })
.backgroundColor('#FFFFFF')
.width(this.rate * 100 + '%')
}
this.slider()
}.width('100%').height('100%').backgroundColor('#F1F3F5')
}
}
```
# 折行能力
折行能力是指容器组件尺寸发生变化,当布局方向尺寸不足以显示完整内容时自动换行。它常用于横竖屏适配或默认设备向平板切换的场景。
折行能力通过使用 **Flex折行布局** (将warp属性设置为FlexWrap.Wrap)实现,当横向布局尺寸不足以完整显示内容元素时,通过折行的方式,将元素显示在下方。
可以访问[Flex组件](../../reference/arkui-ts/ts-container-flex.md),了解Flex组件的详细用法。
## 示例:
父容器中的图片尺寸固定,当父容器尺寸发生变化,其中的内容做自适应换行。
![zh-cn_image_0000001292215677](figures/zh-cn_image_0000001292215677.gif)
```ts
@Entry
@Component
struct WrapCapabilitySample {
@State rate: number = 0.7;
const imageList: Resource [] = [
$r('app.media.flexWrap1'),
$r('app.media.flexWrap2'),
$r('app.media.flexWrap3'),
$r('app.media.flexWrap4'),
$r('app.media.flexWrap5'),
$r('app.media.flexWrap6')
];
// 底部滑块,可以通过拖拽滑块改变容器尺寸
@Builder slider() {
Slider({ value: this.rate * 100, min: 50, max: 70, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.onChange((value: number) => {
this.rate = value / 100;
})
.position({ x: '20%', y: '87%' })
}
build() {
Flex({ justifyContent: FlexAlign.Center, direction: FlexDirection.Column }) {
Column() {
// 通过Flex组件warp参数实现自适应折行
Flex({
direction: FlexDirection.Row,
alignItems: ItemAlign.Center,
justifyContent: FlexAlign.Center,
wrap: FlexWrap.Wrap
}) {
ForEach(this.imageList, (item) => {
Image(item).width(183).height(138).padding(10)
})
}
.backgroundColor('#FFFFFF')
.padding(20)
.width(this.rate * 100 + '%')
.borderRadius(16)
}
.width('100%')
this.slider()
}.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
}
}
```
# 自适应布局
针对常见的开发场景,方舟开发框架提炼了七种自适应布局能力,这些布局可以独立使用,也可多种布局叠加使用。
| 自适应布局类别 | 自适应布局能力 | 使用场景 | 实现方式 |
| -------- | -------- | -------- | -------- |
| 自适应拉伸 | [拉伸能力](#拉伸能力) | 容器组件尺寸发生变化时,增加或减小的空间**全部分配**给容器组件内**指定区域**。 | [Flex布局](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-universal-attributes-flex-layout.md)的flexGrow和flexShrink属性 |
| | [均分能力](#均分能力) | 容器组件尺寸发生变化时,增加或减小的空间**均匀分配**给容器组件内**所有空白区域**。 | [Row组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-row.md)[Column组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-column.md)[Flex组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-flex.md)的justifyContent属性设置为FlexAlign.SpaceEvenly |
| 自适应缩放 | [占比能力](#占比能力) | 子组件的宽或高**按照预设的比例**,随容器组件发生变化。 | 基于通用属性的两种实现方式:<br/>-&nbsp;将子组件的宽高设置为父组件宽高的百分比<br/>-&nbsp;layoutWeight属性 |
| | [缩放能力](#缩放能力) | 子组件的宽高**按照预设的比例**,随容器组件发生变化,且变化过程中子组件的**宽高比不变**。 | [布局约束](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-universal-attributes-layout-constraints.md)的aspectRatio属性 |
| 自适应延伸 | [延伸能力](#延伸能力) | 容器组件内的子组件,按照其**在列表中的先后顺序**,随容器组件尺寸变化显示或隐藏。 | 基于容器组件的两种实现方式:<br/>-&nbsp;通过[List组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-list.md)实现<br/>-&nbsp;通过[Scroll组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-scroll.md)配合[Row组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-row.md)[Column组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-column.md)实现 |
| | [隐藏能力](#隐藏能力) | 容器组件内的子组件,按照其**预设的显示优先级**,随容器组件尺寸变化显示或隐藏。**相同显示优先级的子组件同时显示或隐藏**。 | [布局约束](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-universal-attributes-layout-constraints.md)的displayPriority属性 |
| 自适应折行 | [折行能力](#折行能力) | 容器组件尺寸发生变化时,如果布局方向尺寸不足以显示完整内容,**自动换行**。 | [Flex组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-flex.md)的wrap属性设置为FlexWrap.Wrap |
下面我们依次介绍这几种自适应布局能力。
## 拉伸能力
拉伸能力是指容器组件尺寸发生变化时,增加或减小的空间全部分配给容器组件内指定区域。
拉伸能力通常通过[Flex布局](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-universal-attributes-flex-layout.md)中的flexGrow和flexShrink属性实现,flexGrow和flexShink属性常与flexBasis属性搭配使用,故将这三个属性放在一起介绍。
| 属性 | 类型 | 默认值 | 描述 |
| -------- | -------- | -------- | -------- |
| flexGrow | number | 0 | 仅当父容器宽度大于所有子组件宽度的总和时,该属性生效。配置了此属性的子组件,按照比例拉伸,分配父容器的多余空间。 |
| flexShrink | number | 1 | 仅当父容器宽度小于所有子组件宽度的总和时,该属性生效。配置了此属性的子组件,按照比例收缩,分配父容器的不足空间。 |
| flexBasis | 'auto'&nbsp;\|&nbsp;[Length](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-types.md#length) | 'auto' | 设置组件在Flex容器中主轴方向上基准尺寸。'auto'意味着使用组件原始的尺寸,不做修改。<br/>flexBasis属性不是必须的,通过width或height也可以达到同样的效果。当flexBasis属性与width或height发生冲突时,以flexBasis属性为准。 |
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> - 开发者期望将父容器的剩余空间全部分配给某空白区域时,也可以通过[Blank组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-basic-components-blank.md)实现。注意仅当父组件为Row\Column\Flex组件时,Blank组件才会生效。
>
> - 类Web开发范式也是通过flex-grow和flex-shrink实现拉伸能力,同时也支持配置flex-basis,详见[通用样式](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-js/js-components-common-styles.md)。
>
> - 类Web开发范式没有提供blank组件,但可以通过div组件模拟blank组件的行为,如“&lt;div style='flex-grow: 1; flex-shrink: 0; flex-basis: 0'&gt;&lt;/div&gt;”。
**示例1**
本示例中的页面由中间的内容区(包含一张图片)以及两侧的留白区组成,各区域的属性配置如下。
* 中间内容区的宽度设置为400vp,同时将flexGrow属性设置为1,flexShrink属性设置为0。
* 两侧留白区的宽度设置为150vp,同时将flexGrow属性设置为0,flexShrink属性设置为1。
由上可知,父容器的基准尺寸是700vp(150vp+400vp+150vp)。
可以通过拖动底部的滑动条改变父容器的尺寸,查看布局变化。
* 当父容器的尺寸大于700vp时,父容器中多余的空间全部分配给中间内容区。
* 当父容器的尺寸小于700vp时,左右两侧的留白区按照“1:1”的比例收缩(即平均分配父容器的不足空间)。
![zh-cn_image_0000001335796258](figures/zh-cn_image_0000001335796258.gif)
```
@Entry
@Component
struct FlexibleCapabilitySample1 {
@State containerWidth: number = 402
// 底部滑块,可以通过拖拽滑块改变容器尺寸。
@Builder slider() {
Slider({ value: this.containerWidth, min: 402, max: 1000, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.onChange((value: number) => {
this.containerWidth = value;
})
.position({ x: '20%', y: '80%' })
}
build() {
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center,
alignItems: ItemAlign.Center }) {
Column() {
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Center,
alignItems: ItemAlign.Center }) {
// 通过flexGrow和flexShink属性,将多余的空间全部分配给图片,将不足的控件全部分配给两侧空白区域。
Row().width(150).height(400).backgroundColor('#FFFFFF')
.flexGrow(0).flexShrink(1)
Image($r("app.media.illustrator")).width(400).height(400)
.objectFit(ImageFit.Contain)
.backgroundColor("#66F1CCB8")
.flexGrow(1).flexShrink(0)
Row().width(150).height(400).backgroundColor('#FFFFFF')
.flexGrow(0).flexShrink(1)
}.width(this.containerWidth)
}
this.slider()
}.width('100%').height('100%').backgroundColor('#F1F3F5')
}
}
```
**示例2**
文字和开关的尺寸固定,仅有中间空白区域(Blank组件)随父容器尺寸变化而伸缩。
![zh-cn_image_0000001335316714](figures/zh-cn_image_0000001335316714.gif)
```
@Entry
@Component
struct FlexibleCapabilitySample2 {
@State rate: number = 0.8
// 底部滑块,可以通过拖拽滑块改变容器尺寸
@Builder slider() {
Slider({ value: this.rate * 100, min: 30, max: 80, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.onChange((value: number) => {
this.rate = value / 100;
})
.position({ x: '20%', y: '80%' })
}
build() {
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center,
alignItems: ItemAlign.Center }) {
Column() {
Row() {
Text('飞行模式')
.fontSize(16)
.width(135)
.height(22)
.fontWeight(FontWeight.Medium)
.lineHeight(22)
Blank() // 通过Blank组件实现拉伸能力
Toggle({ type: ToggleType.Switch })
.width(36)
.height(20)
}
.height(55)
.borderRadius(12)
.padding({ left: 13, right: 13 })
.backgroundColor('#FFFFFF')
.width(this.rate * 100 + '%')
}
this.slider()
}.width('100%').height('100%').backgroundColor('#F1F3F5')
}
}
```
## 均分能力
均分能力是指容器组件尺寸发生变化时,增加或减小的空间均匀分配给容器组件内所有空白区域。它常用于内容数量固定、均分显示的场景,比如工具栏、底部菜单栏等。
均分能力可以通过将[Row组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-row.md)[Column组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-column.md)[Flex组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-flex.md)的justifyContent属性设置为FlexAlign.SpaceEvenly实现,即子元素在父容器主轴方向等间距布局,相邻元素之间的间距、第一个元素与行首的间距、最后一个元素到行尾的间距都完全一样。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> - 均分能力还可以通过其它方式实现,如使用[Grid网格组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-grid.md)或在每个组件间添加Blank组件等。
>
> - 类Web开发范式中,通过将[div组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-js/js-components-container-div.md)的justify-content属性设置为space-evenly来实现均分布局。
**示例:**
父容器尺寸变化过程中,图标及文字的尺寸不变,图标间的间距及图标离左右边缘的距离同时均等改变。
![zh-cn_image_0000001335477142](figures/zh-cn_image_0000001335477142.gif)
```
@Entry
@Component
struct EquipartitionCapabilitySample {
const list: number [] = [0, 1, 2, 3]
@State rate: number = 0.6
// 底部滑块,可以通过拖拽滑块改变容器尺寸
@Builder slider() {
Slider({ value: this.rate * 100, min: 30, max: 60, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.onChange((value: number) => {
this.rate = value / 100
})
.position({ x: '20%', y: '80%' })
}
build() {
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Column() {
// 均匀分配父容器主轴方向的剩余空间
Flex({ justifyContent: FlexAlign.SpaceEvenly }) {
ForEach(this.list, (item) => {
Column() {
Image($r("app.media.icon")).width(48).height(48).margin({ top: 8 })
Text('App name')
.width(64)
.height(30)
.lineHeight(15)
.fontSize(12)
.textAlign(TextAlign.Center)
.margin({ top: 8 })
.padding({ bottom: 15 })
}.width(80).height(102)
})
}
// 均匀分配父容器主轴方向的剩余空间
Flex({ justifyContent: FlexAlign.SpaceEvenly }) {
ForEach(this.list, (item) => {
Column() {
Image($r("app.media.icon")).width(48).height(48).margin({ top: 8 })
Text('App name')
.width(64)
.height(30)
.lineHeight(15)
.fontSize(12)
.textAlign(TextAlign.Center)
.margin({ top: 8 })
.padding({ bottom: 15 })
}.width(80).height(102)
})
}
}
.width(this.rate * 100 + '%')
.height(222)
.padding({ top: 16 })
.backgroundColor('#FFFFFF')
.borderRadius(16)
this.slider()
}
.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
}
}
```
## 占比能力
占比能力是指子组件的宽高按照预设的比例,随父容器组件发生变化。
占比能力通常有两种实现方式:
- 将子组件的宽高设置为父组件宽高的百分比,详见[尺寸设置](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-universal-attributes-size.md)[长度类型](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-types.md#length)
- 通过layoutWeight属性配置互为兄弟关系的组件在父容器主轴方向的布局权重,详见[尺寸设置](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-universal-attributes-size.md)
- 当父容器尺寸确定时,其子组件按照开发者配置的权重比例分配父容器中主轴方向的空间。
- 仅当父容器是Row、Colomn或者Flex时,layoutWeight属性才会生效。
- 设置layoutWeight属性后,组件本身的尺寸会失效。比如同时设置了.width('40%')和.layoutWeight(1),那么只有.layoutWeight(1)会生效。
layoutWeight存在使用限制,所以实际使用过程中大多通过将子组件宽高设置为父组件的百分比来实现占比能力。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> - 占比能力在实际开发中使用的非常广泛,可以通过很多不同的方式实现占比能力,如还可以通过[Grid组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-grid.md)的columnsTemplate属性设置网格容器中列的数量及其宽度比例,或通过配置子组件在栅格(本章后文将详细介绍栅格系统)中占据不同的列数来实现占比能力。本小节仅介绍最基础和常用的实现方式,局限性较大或比非常小众的实现方式,本文不做展开介绍。
>
> - 类Web开发范式同样支持以百分比的形式设置组件的宽高,详见[通用样式](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-js/js-components-common-styles.md)中关于width和height的介绍以及[长度类型介绍](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-js/js-appendix-types.md#%E9%95%BF%E5%BA%A6%E7%B1%BB%E5%9E%8B)。
>
> - 与声明式开发范式中的layoutWeight属性类似,类Web开发范式提供了[flex-weight样式](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-js/js-components-common-atomic-layout.md#%E5%8D%A0%E6%AF%94%E8%83%BD%E5%8A%9B)用于配置互为兄弟关系的组件在父容器主轴方向的布局权重。
**示例:**
简单的播放控制栏,其中“上一首”、“播放/暂停”、“下一首”的layoutWeight属性都设置为1,因此它们按照“1:1:1”的比例均分父容器主轴方向的空间。
将三个按钮的.layoutWeight(1)分别替换为.width('33%')、.width('34%')、.width('33%'),也可以实现与当前同样的显示效果。
![zh-cn_image_0000001385757965](figures/zh-cn_image_0000001385757965.gif)
```
@Entry
@Component
struct ProportionCapabilitySample {
@State rate: number = 0.5
// 底部滑块,可以通过拖拽滑块改变容器尺寸
@Builder slider() {
Slider({ value: 100, min: 25, max: 50, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.height(50)
.onChange((value: number) => {
this.rate = value / 100
})
.position({ x: '20%', y: '80%' })
}
build() {
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Column() {
Row() {
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Image($r("app.media.down"))
.width(48)
.height(48)
}
.height(96)
.layoutWeight(1) // 设置子组件在父容器主轴方向的布局权重
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Image($r("app.media.pause"))
.width(48)
.height(48)
}
.height(96)
.layoutWeight(1) // 设置子组件在父容器主轴方向的布局权重
.backgroundColor('#66F1CCB8')
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Image($r("app.media.next"))
.width(48)
.height(48)
}
.height(96)
.layoutWeight(1) // 设置子组件在父容器主轴方向的布局权重
}
.width(this.rate * 100 + '%')
.height(96)
.borderRadius(16)
.backgroundColor('#FFFFFF')
}
this.slider()
}
.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
}
}
```
## 缩放能力
缩放能力是指子组件的宽高按照预设的比例,随容器组件发生变化,且变化过程中子组件的宽高比不变。
缩放能力通过使用百分比布局配合**固定宽高比**(aspectRatio属性)实现当容器尺寸发生变化时,内容自适应调整。
可以访问[布局约束](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-universal-attributes-layout-constraints.md),了解aspectRatio属性的详细信息。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 类Web开发范式同样提供了[aspect-ratio样式](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-js/js-components-common-atomic-layout.md#%E5%9B%BA%E5%AE%9A%E6%AF%94%E4%BE%8B),用于固定组件的宽高比。
**示例:**
为方便查看效果,示例中特意给Column组件加了边框。可以看到Column组件随着其Flex父组件尺寸变化而缩放的过程中,始终保持预设的宽高比,其中的图片也始终正常显示。
![zh-cn_image_0000001335640862](figures/zh-cn_image_0000001335640862.gif)
```
@Entry
@Component
struct ScaleCapabilitySample {
@State sliderWidth: number = 400
@State sliderHeight: number = 400
// 底部滑块,可以通过拖拽滑块改变容器尺寸
@Builder slider() {
Slider({ value: this.sliderWidth, min: 100, max: 400, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.height(50)
.onChange((value: number) => {
this.sliderWidth = value;
})
.position({ x: '20%', y: '80%' })
Slider({ value: this.sliderHeight, min: 100, max: 400, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.height(50)
.onChange((value: number) => {
this.sliderHeight = value
})
.position({ x: '20%', y: '87%' })
}
build() {
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Column() {
Image($r("app.media.illustrator")).width('100%').height('100%')
}
.aspectRatio(1) // 固定宽高比
.border({ width: 2, color: "#66F1CCB8"}) // 边框,仅用于展示效果
}
.backgroundColor("#FFFFFF")
.height(this.sliderWidth)
.width(this.sliderHeight)
this.slider()
}.width('100%')
.height('100%')
.backgroundColor("#F1F3F5")
}
}
```
## 延伸能力
延伸能力是指容器组件内的子组件,按照其在列表中的先后顺序,随容器组件尺寸变化显示或隐藏。它可以根据显示区域的尺寸,显示不同数量的元素。
延伸能力通常有两种实现方式:
- 通过[List组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-list.md)实现。
- 通过[Scroll组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-scroll.md)配合[Row组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-row.md)[Column组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-column.md)实现。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> - List、Row或Column组件中子节点的在页面显示时就已经全部完成了布局计算及渲染,只不过受限于父容器尺寸,用户只能看到一部分。随着父容器尺寸增大,用户可以看到的子节点数目也相应的增加。用户还可以通过手指滑动触发列表滑动,查看被隐藏的子节点。
>
> - 类Web开发范式同样可以使用[list组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-js/js-components-container-list.md)实现延伸能力。
>
> - 类Web开发范式没有提供scroll组件,但可以将[div组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-js/js-components-container-div.md)的overflow样式设置为scroll(即div组件主轴方向上子元素的尺寸超过div组件本身的尺寸时进行滚动显示)来模拟scroll组件的行为。
**示例:**
当父容器的尺寸发生改变时,页面中显示的图标数量随之发生改变。
分别通过List组件实现及通过Scroll组件配合Row组件实现。
![zh-cn_image_0000001335641246](figures/zh-cn_image_0000001335641246.gif)
(1)通过List组件实现。
```
@Entry
@Component
struct ExtensionCapabilitySample1 {
@State rate: number = 0.60
const appList: number [] = [0, 1, 2, 3, 4, 5, 6, 7]
// 底部滑块,可以通过拖拽滑块改变容器尺寸
@Builder slider() {
Slider({ value: this.rate * 100, min: 8, max: 60, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.height(50)
.onChange((value: number) => {
this.rate = value / 100
})
.position({ x: '20%', y: '80%' })
}
build() {
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Row({ space: 10 }) {
// 通过List组件实现隐藏能力
List({ space: 10 }) {
ForEach(this.appList, (item) => {
ListItem() {
Column() {
Image($r("app.media.icon")).width(48).height(48).margin({ top: 8 })
Text('App name')
.width(64)
.height(30)
.lineHeight(15)
.fontSize(12)
.textAlign(TextAlign.Center)
.margin({ top: 8 })
.padding({ bottom: 15 })
}.width(80).height(102)
}.width(80).height(102)
})
}
.padding({ top: 16, left: 10 })
.listDirection(Axis.Horizontal)
.width('100%')
.height(118)
.borderRadius(16)
.backgroundColor(Color.White)
}
.width(this.rate * 100 + '%')
this.slider()
}
.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
}
}
```
(2)通过Scroll组件配合Row组件实现。
```
@Entry
@Component
struct ExtensionCapabilitySample2 {
private scroller: Scroller = new Scroller()
@State rate: number = 0.60
@State appList: number [] = [0, 1, 2, 3, 4, 5, 6, 7]
// 底部滑块,可以通过拖拽滑块改变容器尺寸
@Builder slider() {
Slider({ value: this.rate * 100, min: 8, max: 60, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.height(50)
.onChange((value: number) => {
this.rate = value / 100;
})
.position({ x: '20%', y: '80%' })
}
build() {
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
// 通过Scroll和Row组件实现隐藏能力
Scroll(this.scroller) {
Row({ space: 10 }) {
ForEach(this.appList, () => {
Column() {
Image($r("app.media.icon")).width(48).height(48).margin({ top: 8 })
Text('App name')
.width(64)
.height(30)
.lineHeight(15)
.fontSize(12)
.textAlign(TextAlign.Center)
.margin({ top: 8 })
.padding({ bottom: 15 })
}.width(80).height(102)
})
}
.padding({ top: 16, left: 10 })
.height(118)
.borderRadius(16)
.backgroundColor(Color.White)
}
.scrollable(ScrollDirection.Horizontal)
.width(this.rate * 100 + '%')
this.slider()
}
.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
}
}
```
## 隐藏能力
隐藏能力是指容器组件内的子组件,按照其预设的显示优先级,随容器组件尺寸变化显示或隐藏,其中相同显示优先级的子组件同时显示或隐藏。它是一种比较高级的布局方式,常用于分辨率变化较大,且不同分辨率下显示内容有所差异的场景。主要思想是通过增加或减少显示内容,来保持最佳的显示效果。
隐藏能力通过设置**布局优先级**(displayPriority属性)来控制显隐,当布局主轴方向剩余尺寸不足以满足全部元素时,按照布局优先级大小,从小到大依次隐藏,直到容器能够完整显示剩余元素。具有相同布局优先级的元素将同时显示或者隐藏。
可以访问[布局约束](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-universal-attributes-layout-constraints.md),了解displayPriority属性的详细信息。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 类Web开发范式同样支持[display-index样式](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-js/js-components-common-atomic-layout.md#%E9%9A%90%E8%97%8F%E8%83%BD%E5%8A%9B),用于设置布局优先级。
**示例:**
父容器尺寸发生变化时,其子元素按照预设的优先级显示或隐藏。
![zh-cn_image_0000001335485154](figures/zh-cn_image_0000001335485154.gif)
```
@Entry
@Component
struct HiddenCapabilitySample {
@State rate: number = 0.45
// 底部滑块,可以通过拖拽滑块改变容器尺寸
@Builder slider() {
Slider({ value: this.rate * 100, min: 10, max: 45, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.height(50)
.onChange((value: number) => {
this.rate = value / 100
})
.position({ x: '20%', y: '80%' })
}
build() {
Flex({ direction: FlexDirection.Column,
justifyContent: FlexAlign.Center,
alignItems: ItemAlign.Center }) {
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Image($r("app.media.favorite"))
.width(48)
.height(48)
.objectFit(ImageFit.Contain)
.margin({ left: 12, right: 12 })
.displayPriority(1) // 布局优先级
Image($r("app.media.down"))
.width(48)
.height(48)
.objectFit(ImageFit.Contain)
.margin({ left: 12, right: 12 })
.displayPriority(2) // 布局优先级
Image($r("app.media.pause"))
.width(48)
.height(48)
.objectFit(ImageFit.Contain)
.margin({ left: 12, right: 12 })
.displayPriority(3) // 布局优先级
Image($r("app.media.next"))
.width(48)
.height(48)
.objectFit(ImageFit.Contain)
.margin({ left: 12, right: 12 })
.displayPriority(2) // 布局优先级
Image($r("app.media.list"))
.width(48)
.height(48)
.objectFit(ImageFit.Contain)
.margin({ left: 12, right: 12 })
.displayPriority(1) // 布局优先级
}
.width(this.rate * 100 + '%')
.height(96)
.borderRadius(16)
.backgroundColor('#FFFFFF')
this.slider()
}
.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
}
}
```
## 折行能力
折行能力是指容器组件尺寸发生变化,当布局方向尺寸不足以显示完整内容时自动换行。它常用于横竖屏适配或默认设备向平板切换的场景。
折行能力通过使用 **Flex折行布局** (将wrap属性设置为FlexWrap.Wrap)实现,当横向布局尺寸不足以完整显示内容元素时,通过折行的方式,将元素显示在下方。
可以访问[Flex组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-flex.md),了解Flex组件的详细用法。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 类Web开发范式通过将[div组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-js/js-components-container-div.md)的flex-warp样式设置为wrap来使用折行能力。
**示例:**
父容器中的图片尺寸固定,当父容器尺寸发生变化,其中的内容做自适应换行。
![zh-cn_image_0000001385645821](figures/zh-cn_image_0000001385645821.gif)
```
@Entry
@Component
struct WrapCapabilitySample {
@State rate: number = 0.7
const imageList: Resource [] = [
$r('app.media.flexWrap1'),
$r('app.media.flexWrap2'),
$r('app.media.flexWrap3'),
$r('app.media.flexWrap4'),
$r('app.media.flexWrap5'),
$r('app.media.flexWrap6')
]
// 底部滑块,可以通过拖拽滑块改变容器尺寸
@Builder slider() {
Slider({ value: this.rate * 100, min: 50, max: 70, style: SliderStyle.OutSet })
.blockColor(Color.White)
.width('60%')
.onChange((value: number) => {
this.rate = value / 100
})
.position({ x: '20%', y: '87%' })
}
build() {
Flex({ justifyContent: FlexAlign.Center, direction: FlexDirection.Column }) {
Column() {
// 通过Flex组件warp参数实现自适应折行
Flex({
direction: FlexDirection.Row,
alignItems: ItemAlign.Center,
justifyContent: FlexAlign.Center,
wrap: FlexWrap.Wrap
}) {
ForEach(this.imageList, (item) => {
Image(item).width(183).height(138).padding(10)
})
}
.backgroundColor('#FFFFFF')
.padding(20)
.width(this.rate * 100 + '%')
.borderRadius(16)
}
.width('100%')
this.slider()
}.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
}
}
```
# 应用市场首页
本小节将以应用市场首页为例,介绍如何使用自适应布局能力和响应式布局能力适配不同尺寸窗口。本示例已经在[OpenHarmony应用示例](https://gitee.com/openharmony/applications_app_samples/tree/master/MultiDeviceAppDev/AppMarket)中开源,读者可以根据需要自行下载源码并运行及查看效果。
## 页面设计
一个典型的应用市场首页的UX设计如下所示。
| sm | md | lg |
| -------- | -------- | -------- |
| ![zh-cn_image_0000001328579522](figures/zh-cn_image_0000001328579522.png) | ![zh-cn_image_0000001328259918](figures/zh-cn_image_0000001328259918.png) | ![zh-cn_image_0000001379179861](figures/zh-cn_image_0000001379179861.png) |
观察应用市场首页的页面设计,不同断点下的页面设计有较多相似的地方。
据此,我们可以将页面分拆为多个组成部分。
1. 底部/侧边导航栏
2. 标题栏与搜索栏
3. 运营横幅
4. 快捷入口
5. 精品应用
| sm | md | lg |
| -------- | -------- | -------- |
| ![zh-cn_image_0000001379299533](figures/zh-cn_image_0000001379299533.png) | ![zh-cn_image_0000001328259922](figures/zh-cn_image_0000001328259922.png) | ![zh-cn_image_0000001379179865](figures/zh-cn_image_0000001379179865.png) |
接下来我们逐一分析各部分的实现。
## 底部/侧边导航栏
在sm和md断点下,导航栏在底部;在lg断点下,导航栏在左侧。可以通过[Tab组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-tabs.md)的barPosition和vertical属性控制TabBar的位置,同时还可以通过barWidth和barHeight属性控制TabBar的尺寸。
```
@Entry
@Component
struct Index {
...
build() {
// 设置TabBar在主轴方向起始或结尾位置
Tabs({ barPosition: this.currentBreakpoint === "lg" ? BarPosition.Start : BarPosition.End }) {
// 首页
TabContent() {
Home()
}.tabBar(this.tabItem1)
TabContent() {}.tabBar(this.tabItem2)
TabContent() {}.tabBar(this.tabItem3)
TabContent() {}.tabBar(this.tabItem4)
TabContent() {}.tabBar(this.tabItem5)
}
.backgroundColor('#F1F3F5')
.barMode(BarMode.Fixed)
.barWidth(this.currentBreakpoint === "lg" ? 96 : '100%')
.barHeight(this.currentBreakpoint === "lg" ? '60%' : 56)
// 设置TabBar放置在水平或垂直方向
.vertical(this.currentBreakpoint === "lg")
}
}
```
另外在sm及lg断点下,TabBar中各个Item的图标和文字是按照垂直方向排布的,在md断点下,TabBar中各个Item的图标和文字是按照水平方向排布的。
```
@Component
export default struct TabBarItem {
...
build() {
if (this.currentBreakpoint !== 'md' ) {
// sm及lg断点下,tabBarItem中的图标和文字垂直排布
Column() {
...
}.justifyContent(FlexAlign.Center).height('100%').width('100%')
} else {
// md断点下,tabBarItem中的图标和文字水平排布
Row() {
....
}.justifyContent(FlexAlign.Center).height('100%').width('100%')
}
}
}
```
## 标题栏与搜索栏
标题栏和搜索栏,在sm和md断点下分两行显示,在lg断点下单行显示,可以通过栅格实现。在sm和md断点下,标题栏和搜索栏占满12列,此时会自动换行显示。在lg断点下,标题栏占8列而搜索栏占4列,此时标题栏和搜索栏在同一行中显示。
| | sm/md | lg |
| -------- | -------- | -------- |
| 效果图 | ![zh-cn_image_0000001379385785](figures/zh-cn_image_0000001379385785.png) | ![zh-cn_image_0000001379464977](figures/zh-cn_image_0000001379464977.jpg) |
| 栅格布局图 | ![zh-cn_image_0000001379464981](figures/zh-cn_image_0000001379464981.png) | ![zh-cn_image_0000001328745102](figures/zh-cn_image_0000001328745102.png) |
```
@Component
export default struct IndexHeader {
...
build() {
// 借助栅格实现标题栏和搜索栏在不同断点下的不同布局效果。
GridRow() {
GridCol({ span: { xs: 12, lg: 8 } }) {
this.titleBar()
}
GridCol({ span: { xs: 12, lg: 4 } }) {
this.searchBar()
}
}
.width('100%')
}
}
```
## 运营横幅
不同断点下的运营横幅,sm断点下显示一张图片,md断点下显示两张图片,lg断点下显示三张图片。可以通过[Swiper组件的displayCount属性](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-swiper.md)实现目标效果。
```
@Component
export default struct IndexSwiper {
...
@Builder swiperItem(imageSrc) {
Image(imageSrc)
.width('100%')
.aspectRatio(2.5)
.objectFit(ImageFit.Fill)
}
build() {
Swiper() {
this.swiperItem($r('app.media.ic_public_swiper1'))
this.swiperItem($r('app.media.ic_public_swiper2'))
this.swiperItem($r('app.media.ic_public_swiper3'))
...
}
.autoPlay(true)
.indicator(false)
.itemSpace(10)
// 配置不同断点下运行横幅中展示的图片数量
.displayCount(this.currentBreakpoint === 'sm' ? 1 : (this.currentBreakpoint === 'md' ? 2 : 3))
.width('100%')
.padding({ left: 12, right: 12, bottom: 16, top: 16 })
}
}
```
## 快捷入口
在不同的断点下,快捷入口的5个图标始终均匀排布,这是典型的均分能力使用场景。
```
@Component
@Component
export default struct IndexEntrance {
build() {
// 将justifyContent参数配置为FlexAlign.SpaceEvenly实现均分布局
Row() {
ForEach(entranceIcons, (icon: AllIcons) => {
// 各快捷入口的图标及名称
Column() { ... }
})
}
.width('100%')
.height(64)
.justifyContent(FlexAlign.SpaceEvenly)
.padding({ left: 12, right: 12 })
}
}
```
## 精品应用
随着可用显示区域的增加,精品应用中显示的图标数量也不断增加,这是典型的延伸能力使用场景。精品游戏的实现与精品应用类似,不再展开分析。
```
@Component
@Component
export default struct IndexApps {
...
build() {
Column() {
this.appListHeader()
// 借助List组件能力,实现延伸能力场景
List({ space: this.currentBreakpoint === 'lg' ? 44 : 20}) {
LazyForEach(new MyAppSource(this.apps), app => {
ListItem() {
// 每个应用的图标、名称及安装按钮
this.appListItem(app)
}
}, app => app.id)
}
.width('100%')
.height(this.currentBreakpoint === 'lg' ? 140 : 120)
.listDirection(Axis.Horizontal)
}
.width('100%')
.height(this.currentBreakpoint === 'lg' ? 188 : 164)
.padding({ bottom: 8, left: 12, right: 12 })
}
}
```
## 运行效果
将上述各页面主要部分组合在一起后,即可完成整体页面开发。
```
@Component
struct IndexContent {
...
build() {
List() {
// 运营横幅
ListItem() {
IndexSwiper()
}
// 快捷入口
ListItem() {
IndexEntrance()
}
// 精品应用
ListItem() {
IndexApps({ title: $r('app.string.boutique_application'), apps: appList })
}
// 精品游戏
ListItem() {
IndexApps({ title: $r('app.string.boutique_game'), apps: gameList })
}
}
.width("100%")
}
}
@Component
export default struct Home {
...
build() {
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Start, alignItems: ItemAlign.Start }) {
// 标题栏和搜索栏
IndexHeader()
// 运营横幅、快捷入口、精品应用、精品游戏等
IndexContent()
}
.height('100%')
.backgroundColor("#F1F3F5")
}
}
```
本页面的实际运行效果如下图所示。
| sm | md | lg |
| -------- | -------- | -------- |
| ![zh-cn_image_0000001334345550](figures/zh-cn_image_0000001334345550.jpg) | ![zh-cn_image_0000001385105477](figures/zh-cn_image_0000001385105477.jpg) | ![zh-cn_image_0000001384985569](figures/zh-cn_image_0000001384985569.jpg) |
<!--no_check-->
\ No newline at end of file
# 应用架构设计
- **[应用导航结构设计要求](navigation-design.md)**
- **[应用页面结构设计](page-design.md)**
\ No newline at end of file
# 案例应用
本章从OpenHarmony预置的系统应用中,选择短信应用作为典型的案例,从页面开发和工程结构的角度,介绍"一多"的具体实践。OpenHarmony的产品形态在不断丰富中,当前主要有默认设备和平板两种产品形态,本章的具体实践也将围绕这两种产品形态展开。
## 概览
[短信](https://gitee.com/openharmony/applications_mms/tree/master)是OpenHarmony中预置的系统应用,主要包含信息查看、发送短信、接收短信、短信送达报告、删除短信等功能。在不同类型设备上,短信应用的功能完全相同,故短信应用适合使用[部署模型A](introduction.md#部署模型)(即:不同类型的设备上安装运行相同的HAP包或HAP包组合)。
本案例中,在会话详情页面利用[方舟开发框架](introduction.md#方舟开发框架)提供的“一多”能力,用一套代码同时适配默认设备和平板。
### 工程结构
短信应用的工程结构如下图所示,当前该应用的功能较少,所以直接使用了DevEco Studio创建出的默认工程结构。具体采用何种形式的工程结构,并不影响应用的开发。但是使用推荐的工程结构,目录结构更清晰,拓展性也更好。
短信应用UI相关的逻辑集中在views和pages两个目录,分别存放公共组件及页面。当前短信应用主要包含如下页面:
- 信息列表页面:首页,展示信息列表。
- 通知信息列表页面:将通知类信息集中在一起展示,与信息列表页面类似。
- 会话详情页面:展示与某联系人的所有信息往来。
- 报告详情页面:信息发送报告的详情页面。
- 设置页面:消息设置页面,如是否展示送达报告等。
```
/Mms/
├── doc # 资料
├── entry
│ └── src
│ └── main
│ ├── resources # 资源配置文件存放目录
│ ├── config.json # 全局配置文件
│ └── ets # ets代码目录
│ ├── ServiceAbility # 后台常驻服务
│ └── default # 业务代码目录
│ ├── data # 自定义数据类型
│ ├── model # 对接数据库
│ ├── pages # 所有页面
│ │ ├── conversation # 会话详情页面
│ │ ├── conversationlist # 信息列表页面
│ │ ├── index # 初始页面
│ │ ├── info_msg # 通知信息列表页面
│ │ ├── query_report # 报告详情页面
│ │ └── settings # 设置页面
│ ├── service # 业务逻辑
│ ├── utils # 工具类
│ ├── views # 自定义组件
│ └── app.ets # 应用生命周期
├── signs # 签名
└── LICENSE
```
短信应用在开发阶段,采用了一层工程结构。由于功能较为简单,所以并没有规划共用的feature和common目录,仅采用了一层product目录。
- 业务形态层(product)
该目录采用IDE工程默认创建的entry目录,开发者可根据需要在创建Module时自行更改该目录名。不同产品形态,编译出相同的短信HAP包。
## 会话详情页面
### 页面结构
| 默认设备 | 平板 |
| -------- | -------- |
| ![overview_phone](figures/overview_phone.png) | ![overview_tablet](figures/overview_tablet.png) |
会话详情页面在默认设备和平板上的样式如上图所示,会话详情页面可以划分为三个部分:
| 页面组成 | 介绍 |
| -------- | -------- |
| 顶部标题栏 | ![zh-cn_image_0000001335699774](figures/zh-cn_image_0000001335699774.jpg) |
| 信息列表 | ![zh-cn_image_0000001386060209](figures/zh-cn_image_0000001386060209.jpg) |
| 底部输入栏 | ![zh-cn_image_0000001386179873](figures/zh-cn_image_0000001386179873.jpg) |
接下来我们详细介绍各部分的实现。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 为了方便理解,我们对会话详情页面做了一定的精简,本小节仅介绍会话详情页面最基础的实现。
### 顶部标题栏
| 默认设备 | 平板 |
| -------- | -------- |
| ![zh-cn_image_0000001335539986](figures/zh-cn_image_0000001335539986.jpg) | ![top_title_tablet](figures/top_title_tablet.png) |
顶部标题栏是一个简单的行布局,包含返回图标、联系人头像、联系人姓名和号码、拨号图标、设置图标共5个元素。其中,联系人姓名和号码以列布局的形式放在一起。
在默认设备和平板上,顶部标题栏的组件结构是相同的,仅联系人姓名和号码与拨号图标的间距不同。回顾方舟开发框架一多能力介绍,这个场景可以借助Blank组件使用拉伸能力。
我们先实现联系人姓名和号码,用Flex组件作为父容器,其包含两个Text子组件,分别用于存放联系人姓名和号码。Flex组件的属性设置如下:
- direction: FlexDirection.Column:子组件在Flex容器上以列的方式排布,即主轴是垂直方向。
- justifyContent: FlexAlign.Center:子组件在Flex容器主轴(垂直方向)上居中对齐。
- alignItems: ItemAlign.Start:子组件在Flex容器交叉轴(水平方向)上首部对齐。
可以查看[Flex组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-flex.md)[Text组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-basic-components-text.md)了解这两个组件各个属性的含义及详细用法。
| 默认设备 | 平板 |
| -------- | -------- |
| ![contact_details_phone](figures/contact_details_phone.png) | ![contact_details_tablet](figures/contact_details_tablet.png) |
```
@Component
struct TopArea {
build() {
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center,
alignItems: ItemAlign.Start}) {
Text('张三').fontSize(16).fontColor("#182431")
Text('+123 4567 8901').fontSize(14).fontColor("#66182431")
}
}
}
```
接下来我们通过width属性和height属性设置四个图标的宽高(详见[尺寸设置](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-universal-attributes-size.md)),并将它们与联系人姓名和电话以及Blank组件一起放到Flex父容器中。为了便于查看效果,对顶部标题栏设置了淡蓝色的背景色。
| 默认设备 | 平板 |
| -------- | -------- |
| ![top_title_blank_phone](figures/top_title_blank_phone.png) | ![top_title_blank_tablet](figures/top_title_blank_tablet.png) |
```
@Component
struct TopArea {
build() {
Flex({ alignItems: ItemAlign.Center }) {
Image($r('app.media.back'))
.width(24)
.height(24)
Image($r('app.media.contact'))
.width(40)
.height(40)
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center,
alignItems: ItemAlign.Start}) {
Text('张三').fontSize(16).fontColor("#182431")
Text('+123 4567 8901').fontSize(14).fontColor("#66182431")
}
Blank() // 拉伸能力
Image($r("app.media.call"))
.width(24)
.height(24)
Image($r('app.media.dots'))
.width(24)
.height(24)
}
.width('100%')
.height(56)
.backgroundColor('#87CEFA') // 顶部标题栏背景色,仅用于开发测试
}
}
```
当前标题栏中子组件的布局同预期还有些差异,接下来通过margin属性,设置各个元素的左右间距(详见[尺寸设置](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-universal-attributes-size.md))。如下图所示,最终顶部工具栏在默认设备和平板上都可以达到预期显示效果。
| 默认设备 | 平板 |
| -------- | -------- |
| ![top_title_done_phone](figures/top_title_done_phone.png) | ![top_title_done_tablet](figures/top_title_done_tablet.png) |
```
@Component
struct TopArea {
build() {
Flex({ alignItems: ItemAlign.Center }) {
Image($r('app.media.back'))
.width(24)
.height(24)
.margin({ left:24 }) // 设置间距
Image($r('app.media.contact'))
.width(40)
.height(40)
.margin({ left:16, right:16 }) // 设置间距
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center,
alignItems: ItemAlign.Start}) {
Text('张三').fontSize(16).fontColor("#182431")
Text('+123 4567 8901').fontSize(14).fontColor("#66182431")
}
Blank()
Image($r("app.media.call"))
.width(24)
.height(24)
Image($r('app.media.dots'))
.width(24)
.height(24)
.margin({ left:16, right:24 }) // 设置间距
}
.width('100%')
.height(56)
.backgroundColor('#87CEFA') // 顶部标题栏背景色,仅用于开发测试
}
}
```
### 底部输入栏
有了顶部工具栏的开发经验,可以发现底部输入栏的结构更为简单,它同样以Flex组件作为父容器,同时包含文本输入框(请访问[文本输入组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-basic-components-textarea.md)查看详细介绍)和消息发送图标两个子节点。
![zh-cn_image_0000001335380378](figures/zh-cn_image_0000001335380378.jpg)
为了便于查看的效果,我们同样给底部输入栏设置了淡蓝色到背景色。注意这里有一个特殊的地方,我们给TextArea设置了flexGrow(1)属性。flexGrow属性仅在父组件是Flex组件时生效,表示Flex容器的剩余空间分配给此属性所在的组件的比例,flexGrow(1)表示父容器的剩余空间全部分配给此组件,详见[Flex布局](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-universal-attributes-flex-layout.md)
| 默认设备 | 平板 |
| -------- | -------- |
| ![bottom_input_phone](figures/bottom_input_phone.png) | ![bottom_input_tablet](figures/bottom_input_tablet.png) |
```
@Component
struct BottomArea {
build() {
Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
TextArea({ placeholder:'短信' })
.placeholderColor("#99000000")
.caretColor("#007DFF")
.backgroundColor("#F1F3F5")
.borderRadius(20)
.height(40)
.flexGrow(1) // 将父容器的剩余空间全部分配给此组件
Image($r("app.media.send"))
.height(36)
.width(36)
.opacity(0.4)
.margin({ left:12 })
}
.height(72)
.width('100%')
.padding({ left:24, right:24, bottom:8, top:8 })
.backgroundColor('#87CEFA') // 底部输入栏背景色,仅用于开发测试
}
}
```
### 信息列表
观察信息列表区域,可以发现它是由一个个消息气泡组成的,另外消息气泡在默认设备和平板上的布局有差异。本小节将围绕如下两个主题介绍如何实现消息列表。
- 如何实现自定义消息气泡组件。
- 如何在默认设备和平板上自适应布局。
| 默认设备 | 平板 |
| -------- | -------- |
| ![zh-cn_image_0000001386180233](figures/zh-cn_image_0000001386180233.jpg) | ![message_list_tablet](figures/message_list_tablet.png) |
**消息气泡**
先做一个最简单的消息气泡,通过borderRadius属性可以设置边框的圆角半径(详见[边框设置](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-universal-attributes-border.md))。
| 默认设备 | 平板 |
| -------- | -------- |
| ![message_bubble_basic_phone](figures/message_bubble_basic_phone.png) | ![message_bubble_basic_tablet](figures/message_bubble_basic_tablet.png) |
```
@Component
struct MessageBubble {
private content: string = "OpenHarmony"
build() {
Column() {
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.End }) {
Text(this.content)
.fontSize(16)
.lineHeight(21)
.padding({ left: 12, right: 12, top: 8, bottom: 8 })
.backgroundColor("#C0EBDF")
.borderRadius(24)
.fontColor("#182431")
}.width('100%')
}
.margin({left: 24, right: 24 })
.backgroundColor('#87CEFA') // 消息背景色,仅用于开发和测试
}
}
```
注意这个简单的消息气泡,左上角(或右上角)的样式,与实际期望不符。我们先修改发送消息右上角的样式,接收消息左上角的实现与之类似。
[Stack组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-stack.md)是一个堆叠容器,其子组件按照轴方向依次堆叠,后一个子组件覆盖前一个子组件。通过其alignContent接口,可以设置子组件在容器内的对齐方式,如alignContent: Alignment.TopStart代表子组件从左上角对齐。
| 默认设备 | 平板 |
| -------- | -------- |
| ![message_bubble_radius_phone](figures/message_bubble_radius_phone.png) | ![message_bubble_radius_tablet](figures/message_bubble_radius_tablet.png) |
```
@Component
struct MessageBubble {
private content: string = "OpenHarmony"
private time: string = "今天 上午 10:35"
build() {
Column() {
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.End }) {
Stack({ alignContent: Alignment.TopEnd }) { // 在左上角堆叠一个小色块
Column()
.backgroundColor("#C0EBDF")
.borderRadius(4)
.width(24)
.height(24)
Text(this.content)
.fontSize(16)
.lineHeight(21)
.padding({ left: 12, right: 12, top: 8, bottom: 8 })
.backgroundColor("#C0EBDF")
.borderRadius(24)
.fontColor("#182431")
}
}.width('100%')
}
.margin({left: 24, right: 24 })
.backgroundColor('#87CEFA') // 消息背景色,仅用于开发和测试
}
}
```
接下来我们在消息气泡下方加上时间显示,如下图所示,一个消息气泡自定义组件就基本完成了。
| 默认设备 | 平板 |
| -------- | -------- |
| ![message_bubble_recv_phone](figures/message_bubble_recv_phone.png) | ![message_bubble_recv_tablet](figures/message_bubble_recv_tablet.png) |
```
@Component
struct MessageBubble {
private content: string = "OpenHarmony"
private time: string = "上午 10:35"
build() {
Column() {
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.End }) {
Stack({ alignContent: Alignment.TopEnd }) {
Column()
.backgroundColor("#C0EBDF")
.borderRadius(4)
.width(24)
.height(24)
Text(this.content)
.fontSize(16)
.lineHeight(21)
.padding({ left: 12, right: 12, top: 8, bottom: 8 })
.backgroundColor("#C0EBDF")
.borderRadius(24)
.fontColor("#182431")
}
}.width('100%')
// 在消息气泡底部增加时间显示
Flex({ alignItems: ItemAlign.Center, direction: FlexDirection.Row,
justifyContent: FlexAlign.End}) {
Text(this.time)
.textAlign(TextAlign.Start)
.fontSize(10)
.lineHeight(13)
.fontColor("#99182431")
}.width('100%').margin({ left: 12, right: 0 })
}
.margin({left: 24, right: 24 })
.backgroundColor('#87CEFA') // 消息背景色,仅用于开发和测试
}
}
```
发送出的消息和接收到的消息的消息气泡结构基本一致,可以通过增加一个标志位,让两种消息共用MessageBubble这个自定义组件,代码如下所示。将这个标志位设置true,可以查看接收消息的效果。
| 默认设备 | 平板 |
| -------- | -------- |
| ![message_bubble_send_phone](figures/message_bubble_send_phone.png) | ![message_bubble_send_tablet](figures/message_bubble_send_tablet.png) |
```
@Component
struct MessageBubble {
private isReceived:boolean = false // 通过标志位,判断是发送or接收场景,进而使用不同的样式
private content:string = "OpenHarmony"
private time:string = "今天 10:00"
build() {
Column() {
Flex({ justifyContent:this.isReceived? FlexAlign.Start: FlexAlign.End,
alignItems: ItemAlign.Center }) {
Stack({ alignContent:this.isReceived? Alignment.TopStart: Alignment.TopEnd }) {
Column()
.backgroundColor(this.isReceived?"#FFFFFF":"#C0EBDF")
.borderRadius(4)
.width(24)
.height(24)
Text(this.content)
.fontSize(16)
.lineHeight(21)
.padding({ left:12, right:12, top:8, bottom:8 })
.backgroundColor(this.isReceived?"#FFFFFF":"#C0EBDF")
.borderRadius(24)
.fontColor("#182431")
}
}.width('100%')
Flex({ alignItems: ItemAlign.Center, direction: FlexDirection.Row,
justifyContent:this.isReceived? FlexAlign.Start: FlexAlign.End }) {
Text(this.time)
.textAlign(TextAlign.Start)
.fontSize(10)
.lineHeight(13)
.fontColor("#99182431")
}.width('100%')
.margin({ left:this.isReceived?12:0, right:this.isReceived?0:12 })
}
.margin({left:24, right:24 })
.backgroundColor('#87CEFA') // 消息背景色,仅用于开发和测试
}
}
```
**栅格布局**
回顾方舟开发框架一多能力,消息气泡在默认设备和平板上布局不同,可以借助栅格布局来解决。为了方便测试,我们预定义一个全局数组。
```
const globalMessageList:any[] = [
{
'time':'上午 10:20',
'content':'项目介绍',
'isReceived':false
},
{
'time':'上午 10:28',
'content':'OpenHarmony是由开放原子开源基金会(OpenAtom Foundation)孵化及运营的开源项目,目标是面向全场景、全连接、全智能时代,基于开源的方式,搭建一个智能终端设备操作系统的框架和平台,促进万物互联产业的繁荣发展。',
'isReceived':false
},{
'time':'上午 10:32',
'content':'技术架构',
'isReceived':true
},
{
'time':'上午 10:35',
'content':'OpenHarmony整体遵从分层设计,从下向上依次为:内核层、系统服务层、框架层和应用层。系统功能按照“系统 > 子系统 > 组件”逐级展开,在多设备部署场景下,支持根据实际需求裁剪某些非必要的组件。',
'isReceived':true
}
]
```
结合[栅格组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-gridcontainer.md)的定义,考虑我们当前的实际场景,GridRow的各参数设置如下。
- columns:栅格组件中的列数,当前场景默认12列即可。
- gutter:栅格布局列间距,当前场景未使用该参数,默认设置为0即可。
- margin: 栅格布局两侧间距,在开发消息气泡组件时,已经设置了左右间距,故该属性也默认配置为0。
栅格中仅包含我们自定义的消息气泡组件,该组件在各断点上的参数配置如下。
| 断点 | 窗口宽度(vp) | 栅格总列数 | 消息气泡占用的列数 | 接收场景偏移的列数 | 发送场景偏移的列数 |
| -------- | -------- | -------- | -------- | -------- | -------- |
| sm | [320,&nbsp;520) | 12 | 12 | 0 | 0 |
| md | [520,&nbsp;840) | 12 | 8 | 0 | 4 |
| lg | [840,&nbsp;+∞) | 12 | 8 | 0 | 4 |
| 默认设备 | 平板 |
| -------- | -------- |
| ![message_list_phone](figures/message_list_phone.png) | ![message_list_tablet](figures/message_list_tablet.png) |
```
@Component
export default struct MessageItem {
private isReceived: boolean
private content: string
private time: string
build() {
GridRow() {
GridCol({span: {sm: 12, md: 8, lg: 8},
offset: {sm: 0, md: this.isReceived? 0 : 4, lg: this.isReceived? 0 : 4}}) {
Flex({ justifyContent: FlexAlign.End, alignItems: ItemAlign.End }) {
MessageBubble({
isReceived: this.isReceived,
content: this.content,
time: this.time
})
}
}
}
}
}
@Entry
@Component
struct Conversation {
build() {
Column() { // 验证效果
MessageItem({
isReceived: globalMessageList[1].isReceived,
content: globalMessageList[1].content,
time: globalMessageList[1].time
})
MessageItem({
isReceived: globalMessageList[3].isReceived,
content: globalMessageList[3].content,
time: globalMessageList[3].time
})
}.backgroundColor('#87CEFA') // 消息背景色,仅用于开发和测试
}
}
```
### 组合成型
现在会话详情页面的顶部标题栏、信息列表及底部输入栏都已经准备完毕,将这三部分组合起来即可得到完整的页面。
- 通过[Flex组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-flex.md)将三个部分组合起来,注意justifyContent: FlexAlign.SpaceBetween配置项是将Flex组件中的元素按照主轴方向均匀分配,其中第一个元素与顶部对齐,最后一个元素与底部对齐。
- 通过[List组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-list.md)[ForEach语法](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/ui/ts-rending-control-syntax-foreach.md),显示整个消息列表。
| 默认设备 | 平板 |
| -------- | -------- |
| ![overview_phone](figures/overview_phone.png) | ![overview_tablet](figures/overview_tablet.png) |
```
@Entry
@Component
struct Conversation {
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Start,
justifyContent: FlexAlign.SpaceBetween }) {
Column() {
TopArea() // 顶部标题栏
List() { // 消息列表
ForEach(globalMessageList, (item, index) => {
ListItem() {
MessageItem({
isReceived: item.isReceived,
content: item.content,
time: item.time
})
})
}
.listDirection(Axis.Vertical)
.edgeEffect(EdgeEffect.Spring)
}
BottomArea() // 底部输入栏
}
.backgroundColor("#F1F3F5")
.width('100%')
.height('100%')
}
}
```
## 总结
短信应用在默认设备和平板上的功能完全相同,因此选择了部署模型A。借助方舟开发框架一多能力,短信应用实现了在默认设备和平板上共用同一份代码,同时自然也共用安装包。
在实际开发过程中,会话详情页面需要从底层做数据交互,同时还要支持信息选择、信息删除、信息发送状态、输入框与输入法联动等等功能,会比本小节中介绍的基础版本复杂很多。读者如果对这部分感兴趣,可以访问[短信应用开源代码仓](https://gitee.com/openharmony/applications_mms/tree/master),了解会话详情页面的详细实现。
# 桌面应用
[桌面](https://gitee.com/openharmony/applications_launcher)是OpenHarmony中预置的系统应用,它是系统人机交互的首要入口。
## 应用功能
桌面提供了如下功能:
- 万能卡片:包括卡片的呼出和关闭、卡片管理、卡片拖动、卡片移除等。
- 任务中心:展示后台运行任务的快照。
- 大文件夹:包括大文件基础展示、将应用拖入及移出大文件夹等。
- 应用中心:展示全量桌面应用列表。
- 图标管理:图标显示、长按图标显示菜单列表等。
- 布局管理:对不同的设备、横竖屏、分辨率等做布局自适应。
- 桌面设置:设置桌面布局方案。
- 手势导航:允许用户用手势导航替代传统的三键导航。
- 应用启动:启动应用、应用启动动效等。
- 键鼠适配:支持鼠标及键盘。
- 快捷栏(Smart Dock):底部快捷栏,显示常用应用列表、支持添加应用快捷方式及删除等。
部分功能场景设计图如下所示。
| 功能点 | 默认设备 | 平板 |
| -------- | -------- | -------- |
| 万能卡片 | ![zh-cn_image_0000001267293008](figures/zh-cn_image_0000001267293008.jpg) | ![zh-cn_image_0000001267932928](figures/zh-cn_image_0000001267932928.jpg) |
| 任务中心 | ![zh-cn_image_0000001315932969](figures/zh-cn_image_0000001315932969.jpg) | ![zh-cn_image_0000001316932917](figures/zh-cn_image_0000001316932917.jpg) |
| 快捷栏 | ![zh-cn_image_0000001315453085](figures/zh-cn_image_0000001315453085.jpg) | ![zh-cn_image_0000001315732933](figures/zh-cn_image_0000001315732933.jpg) |
| 大文件夹 | ![zh-cn_image_0000001267532988](figures/zh-cn_image_0000001267532988.jpg) | ![zh-cn_image_0000001266893124](figures/zh-cn_image_0000001266893124.jpg) |
| 应用中心 | NA | ![zh-cn_image_0000001267293016](figures/zh-cn_image_0000001267293016.jpg) |
| 键鼠适配 | NA | ![zh-cn_image_0000001267932936](figures/zh-cn_image_0000001267932936.jpg) |
| 桌面设置 | ![zh-cn_image_0000001315932977](figures/zh-cn_image_0000001315932977.jpg) | ![zh-cn_image_0000001316932929](figures/zh-cn_image_0000001316932929.jpg) |
## 部署模型
如前所述,为保证最佳的体验效果,桌面应用根据默认设备和平板的特性,做了深入定制。
- 不同设备桌面应用的特性规格有差异。
- 平板有应用中心功能,平板通过快捷栏任务中心图标进入任务中心,才能看到全量应用。默认设备直接在桌面上显示全量应用,没有应用中心功能。
- 默认设备不需要适配键鼠输入。
- 同一特性,在不同设备上页面跳转逻辑有差异。
默认设备和平板的万能卡片管理页面完全不同,默认设备上是跳转到新的页面进行卡片管理操作,而平板上是直接在原有页面上弹出Dailog窗口进行卡片管理操作。
因此,桌面最终选择了[部署模型B](introduction.md#部署模型),即默认设备和平板上部署不同的HAP包。
## 工程结构
```
/applications
├── common # 公共模块目录
│ └── src
│ └── main
│ └── ets
│ └── default
│ ├── base # 公共presenter层代码目录
│ ├── bean # 公共数据对象
│ ├── cache # 缓存目录
│ ├── configs # 应用配置对象目录
│ ├── constants # 应用常量对象目录
│ ├── manager # manager代码目录
│ ├── model # Model层代码目录
│ ├── settings # settings配置项
│ ├── uicomponents # 自定义组件目录
│ └── utils # 工具类代码目录
├── feature # feature层模块目录
│ ├── appcenter # 应用中心模块目录
│ ├── bigfolder # 大文件夹模块目录
│ ├── form # 卡片模块目录
│ ├── gesturenavigation # 桌面设置模块目录
│ ├── launcherlayout # 桌面布局计算模块目录
│ ├── pagedesktop # 桌面设置模块目录
│ ├── recents # 最近任务模块目录
│ ├── settings # 桌面设置模块目录
│ └── smartdock # dock栏模块目录
└── product # 产品层模块目录
├── pad # 平板目录
└── phone # 默认设备目录
```
桌面应用在开发阶段,采用了三层工程结构。默认设备和平板共用feature和common目录中的代码,仅在product目录中各自有部分独立代码。
- 业务形态层(product)
包含不同产品形态的桌面。含桌面窗口、个性化业务、组件的配置以及个性化资源包。不同产品形态,编译出不同的Launcher的HAP包,各产品按需部署。
- 公共特性层(feature)
抽象的公共特性组件集合,可以被product层不同形态的桌面所引用,并且在product层上做一些定制化的配置。
- 功能能力层(common)
基础能力集,每个桌面形态都必须要依赖的模块。该层不可被分割,方便后续架构拓展和演进。
## 总结
本小节主要仅从工程结构角度介绍桌面应用,读者如果对其具体实现感兴趣,可以访问[桌面应用开源代码仓](https://gitee.com/openharmony/applications_launcher)了解详情。桌面应用在默认设备和平板上功能有较大差异,因此最终选择了部署模型B。虽然在默认设备和平板上是不同的安装包,但二者依然公用了非常多的代码。经统计,**共用代码占比在90%左右**。在后续拓展其它设备类型时,依然可以使用此部分公共代码。这既可以提高多设备场景开发效率,同时也可以降低后期的维护成本。
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
**基于触控的交互** **基于触控的交互**
很多设备都拥有支持多点触控的屏幕,允许用户使用手指和/或手写笔进行交互。它们与屏幕的接触状态、数量以及运动行为被识别成触控手势和操作,可以支持多种交互功能和体验(例如点击、滑动、缩放、旋转)。在多数情况下,应将触控交互作为用户首要的交互方式。 很多设备的屏幕都支持多点触控,允许用户通过手指或手写笔进行交互。它们与屏幕的接触状态、数量以及运动行为被识别成触控手势和操作,可以支持多种交互功能和体验(例如点击、滑动、缩放、旋转)。在多数情况下,应将触控交互作为用户首要的交互方式。
以下是基础的手势操作: 以下是基础的手势操作:
......
...@@ -10,17 +10,17 @@ ...@@ -10,17 +10,17 @@
左右拉伸:例如,列表开关组合中,在窗口宽度变化时,开关控件固定宽度并相对列表的右边缘位置固定,整个组合与文本宽度均自适应变化。 左右拉伸:例如,列表开关组合中,在窗口宽度变化时,开关控件固定宽度并相对列表的右边缘位置固定,整个组合与文本宽度均自适应变化。
![zh-cn_image_0000001258762686](figures/zh-cn_image_0000001258762686.gif) ![拉伸能力](figures/拉伸能力.gif)
均分拉伸:例如,在图标型网格中,当窗口宽度变化时,入口图标间距与图标离左右边缘间距同时均等变化。 均分拉伸:例如,在图标型网格中,当窗口宽度变化时,入口图标间距与图标离左右边缘间距同时均等变化。
![zh-cn_image_0000001291556325](figures/zh-cn_image_0000001291556325.gif) ![均分能力](figures/均分能力.gif)
自适应拉伸适用于文字、普通按钮、间距等展示宽度灵活,对宽高比不敏感的内容和内容组合。 自适应拉伸适用于文字、普通按钮、间距等展示宽度灵活,对宽高比不敏感的内容和内容组合。
当可能出现的拉伸宽度不足以显示默认内容时,应根据场景选择优先保证内容完整或者优先保证其他内容的屏效,并进行截断或换行等组合适配。 当可能出现的拉伸宽度不足以显示默认内容时,应根据场景选择优先保证内容完整或者优先保证其他内容的屏效,并进行截断或换行等组合适配。
![zh-cn_image_0000001245276368](figures/zh-cn_image_0000001245276368.png) ![拉伸注意场景](figures/拉伸注意场景.png)
## 自适应缩放 ## 自适应缩放
...@@ -33,13 +33,13 @@ ...@@ -33,13 +33,13 @@
占比缩放:例如,带主体和背景的插画,画面内容根据宽度变化裁切,根据高度变化按50%比例缩放。 占比缩放:例如,带主体和背景的插画,画面内容根据宽度变化裁切,根据高度变化按50%比例缩放。
![zh-cn_image_0000001245116688](figures/zh-cn_image_0000001245116688.gif) ![缩放案例](figures/缩放案例.gif)
自适应缩放适用于图片、圆形按钮、banner、反应真实物体形状的图像等必须保证宽高比的内容。 自适应缩放适用于图片、圆形按钮、banner、反应真实物体形状的图像等必须保证宽高比的内容。
不推荐将所有元素同时缩放、或某内容放大过大超过屏幕50%。这将导致获取信息量不增反减,不符合用户预期。 不推荐将所有元素同时缩放、或某内容放大过大超过屏幕50%。这将导致获取信息量不增反减,不符合用户预期。
![zh-cn_image_0000001292842113](figures/zh-cn_image_0000001292842113.gif) ![4.3-2](figures/4.3-2.png)
## 自适应延伸 ## 自适应延伸
...@@ -48,11 +48,11 @@ ...@@ -48,11 +48,11 @@
同功能内容延伸:例如,子页签和可滑动宫格在默认宽度下通过露出最后内容,提示右方有更多入口,在宽度变化时,可在每个元素宽度不变、保持滑动交互时显示更多数量。 同功能内容延伸:例如,子页签和可滑动宫格在默认宽度下通过露出最后内容,提示右方有更多入口,在宽度变化时,可在每个元素宽度不变、保持滑动交互时显示更多数量。
![zh-cn_image_0000001291556369](figures/zh-cn_image_0000001291556369.gif) ![延长能力](figures/延长能力.gif)
不同功能内容延伸或隐藏:例如,默认处于同一排的不同音乐播放按钮优先级不同,在宽度变化时可延伸或隐藏低优先级的按钮,最大化适应不同窗口尺寸。 不同功能内容延伸或隐藏:例如,默认处于同一排的不同音乐播放按钮优先级不同,在宽度变化时可延伸或隐藏低优先级的按钮,最大化适应不同窗口尺寸。
![zh-cn_image_0000001245276416](figures/zh-cn_image_0000001245276416.gif) ![隐藏能力](figures/隐藏能力.gif)
自适应延伸/隐藏适用于页签、操作块、推荐栏目等具有相同交互层级且有更多数据可以填充的内容。 自适应延伸/隐藏适用于页签、操作块、推荐栏目等具有相同交互层级且有更多数据可以填充的内容。
...@@ -65,8 +65,8 @@ ...@@ -65,8 +65,8 @@
例如,在宽度足够时,操作块位于同一行,在宽度变小时,可根据内容能显示的宽度纵向排布。 例如,在宽度足够时,操作块位于同一行,在宽度变小时,可根据内容能显示的宽度纵向排布。
![zh-cn_image_0000001291675805](figures/zh-cn_image_0000001291675805.png) ![折行案例分镜](figures/折行案例分镜.png)
自适应折行适用于页签、操作块、内容流等具有相同交互层级,且希望保证类型和数量完整性的内容。 自适应折行适用于页签、操作块、内容流等具有相同交互层级,且希望保证类型和数量完整性的内容。
自适应布局对应OpenHarmony系统提供的自适应布局能力中的拉伸、均分、缩放、占比、延伸、隐藏、折行。自适应布局能力详见本文“[自适应布局](adaptive-layout-intro.md)”。 自适应布局对应OpenHarmony系统提供的自适应布局能力中的拉伸、均分、缩放、占比、延伸、隐藏、折行。自适应布局能力详见本文“[自适应布局](adaptive-layout.md)”相关介绍。
...@@ -6,19 +6,22 @@ ...@@ -6,19 +6,22 @@
自检表的要求范围分为“必选”与“推荐”两类。必选类一般为已总结出的较优解决方案或效果,表示相关设计需要按照此原则统一执行;推荐类指可能受应用品牌风格或业务特殊性影响,可适量做出修改。 自检表的要求范围分为“必选”与“推荐”两类。必选类一般为已总结出的较优解决方案或效果,表示相关设计需要按照此原则统一执行;推荐类指可能受应用品牌风格或业务特殊性影响,可适量做出修改。
请参考以下表格范围内提出的要求对应用进行检查。 请参考以下表格范围内提出的要求对应用进行检查。
| **类型** | **条目** | **说明** | | **类型** | **条目** | **说明** |
| -------- | ------------------ | ------------------------------------------------------------ | | -------- | ------------------ | ------------------------------------------------------------ |
| 应用架构 | 导航结构 | 保证同一应用/服务在各设备上导航结构一致。 | | 应用架构 | 导航结构 | 在各设备上页面导航结构保持一致(同时出多个设备的UX设计)。 |
| | 界面框架 | 尽量使用推荐的常用界面架构,以更好达到一多效果。 | | 布局 | 拉通设计 | 拉通各设备的布局设计,保证在不同尺寸和分辨率的设备上能够无错位/不截断/不变形/不过多空白(50%以上)/不过于拥挤(间距小于16vp,明显截断)/无大图大字体地正常显示。 |
| 布局 | 自适应布局 | 关注布局标注是否逻辑合理,是否具备自适应能力。保证在不同尺寸和分辨率的设备上能够无错位/不截断/不变形地正常显示。 | | | 响应式设计 | 栅格布局只能占N列以及N列内部的Gutter,不包含N列两侧的Gutter。 |
| | 响应式布局 | 关注布局是否更有效利用屏幕控件,是否具备响应式能力。保证在不同尺寸和分辨率的设备上不过多空白(50%以上)/不过于拥挤(间距小于16vp,明显截断)。 | | | 响应式设计 | 明确标注使用什么类型的栅格、给出在不同断点下栅格三要素取值。 |
| | 页面结构 | 尽量使用推荐的常用页面结构,以更好达到一多效果。 | | | 响应式设计 | 按容器去对齐栅格,而不是内部子元素对齐栅格。 |
| | 页面结构优化 | 在通用能力不适用业务诉求时,进行页面特殊优化,保证在具体设备上的使用体验。 | | | 响应式设计 | 栅格除了页面布局设计外,在做局部栅格设计时,需要通过明显方式如颜色等进行标注区分,避免混淆。 |
| | 响应式设计 | 禁止出现标注了栅格但实际没有通过栅格进行布局设计,避免混淆。 |
| | 自适应设计 | 非栅格设计场景下,明确标注自适应布局能力。自适应布局能力有:拉伸、均分、占比、缩放、延伸、隐藏、折行。 |
| 人机交互 | 输入方式 | 需保证在各设备上完整支持触摸、鼠标、触控、键盘、遥控器、摇杆等交互方式,并符合标准定义。 | | 人机交互 | 输入方式 | 需保证在各设备上完整支持触摸、鼠标、触控、键盘、遥控器、摇杆等交互方式,并符合标准定义。 |
| | 交互归一 | 应使用系统提供的控件以达到一致的交互体验。如有定制,需保证在各场景下,不同输入设备上的操作与指南要求一致。需特别注意鼠标行为。 | | | 交互归一 | 应使用系统提供的控件以达到一致的交互体验。如有定制,需保证在各场景下,不同输入设备上的操作与指南要求一致。需特别注意鼠标行为。 |
| 视觉风格 | 单位 | 用于界面布局的单位应全部使用vp。只针对严格控制元素尺寸的场景使用px。 | | 视觉风格 | 单位 | 用于界面布局的单位应全部使用vp。只针对严格控制元素尺寸的场景使用px。 |
| | 色彩 | 用于色彩的赋值应使用分层参数。推荐支持深色模式,需保证界面在系统切换色彩模式时没有识别性问题。 | | | 色彩 | 用于色彩的赋值应使用分层参数。推荐支持深色模式,需保证界面在系统切换色彩模式时没有识别性问题。 |
| | 字体 | 尽量使用fp为文字大小单位,需要响应系统大字体模式,确保系统调节字体大小后,界面字体能响应变化大小,并且界面布局没有出现布局错乱问题。 | | | 字体 | 使用fp为文字大小单位,需要响应系统大字体模式,确保系统调节字体大小后,界面字体能响应变化大小,并且界面布局没有出现布局错乱问题。 |
| 多态控件 | 支持常用的控件状态 | 确保控件不同状态下的视觉效果没有缺失。控件的常用状态有:正常态、不可用态、点击态、获焦态、激活态、悬停态。 | | 多态控件 | 支持常用的控件状态 | 确保控件不同状态下的视觉效果没有缺失。控件的常用状态有:正常态、不可用态、点击态、获焦态、激活态、悬停态。 |
\ No newline at end of file
# 设计交付
## 概述
为了将UX设计详尽准确地实现并传递给开发者,设计师需向开发者提供必要的设计交付件,包括但不限于:应用规格说明、交互流程、视觉效果、视觉标注、视觉规格说明、切图资源、动效参数资源、音效资源等内容。
面向多设备多尺寸的应用UX设计交付件,以完整说明界面布局与视觉设计为主要目的,至少应包含:效果图、标注图、规格说明和切图资源。
下面将分别介绍不同交付内容的用途与规范。
![4.8-交付4件套](figures/4.8-交付4件套.png)
## 效果图
效果图是表达UX设计的关键内容,用于直观呈现目标体验的静态效果,并作为开发者与测试工程师的实现与验收依据。效果图一般为jpg或png格式。
在优秀的设计交付件中,针对不同前置条件下,各交互流程所途经的每个界面都应匹配效果图。对于大量重复使用的组件元素,如弹窗和即时反馈,亦可配合规格统一说明,不做过多重复设计。
在多设备设计中,为了清晰表达设计效果,设计师仍需要针对不同尺寸的关键界面提供符合规范的效果图。关键界面包括且不限于:
- 应用的首页
- 可以从首页直接进入的各二级界面
- 核心使用流程途经的各界面
- 能体现多数界面的自适应与响应式规格的典型界面
不同尺寸的效果图,至少应包含小设备、中设备、大设备。根据业务涉及的设备特点,可补充超小尺寸效果,及不同设备的横竖屏效果,提前充分验证并优化UX设计体现的差异性、一致性、灵活性、兼容性。
对应不同设备效果图的设计画板尺寸推荐如下:
| 设备类型 | 屏幕宽度 | 画板尺寸(vp) |
| -------- | -------- | -------- |
| 超小设备 | [0,&nbsp;320) | 240\*320 |
| 小设备 | [320,&nbsp;520) | 360\*780 |
| 中设备 | [520,&nbsp;840) | 677\*763 |
| 大设备 | [840,&nbsp;+) | 1280\*800 |
多设备效果示意图
![4.8-效果示意图](figures/4.8-效果示意图.png)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> - 画板尺寸为不同宽度断点下的典型设备屏幕尺寸,可根据业务具体针对的设备选择其他画板尺寸。
>
> - 画板尺寸以vp为单位提供,根据实际设计所需精度,可统一把设计文档中所有画板设置为1倍或多倍的px尺寸。
>
> - 当效果图需要展示一屏以上的内容,如列表内容较长时,建议保持宽度不变并增加画板高度以容纳更多内容。
## 标注图
标注图是向开发者传递界面上每个元素详细属性,以指导代码完整实现UX设计的图像化文档。界面元素的属性包括:色彩、尺寸、字体、圆角、间距、阴影、模糊、缩放、所用的组件、自适应布局、响应式布局等。标注图一般为jpg或png格式。
在优秀的设计交付件中,不同类型属性的标注文本大小一致、色彩不一、互不重叠,并与效果图内容在视觉上良好区隔。标注图数量与关键界面效果图一一对应,如遇到较复杂的界面,为了更清晰说明规格,也有多张标注图对应一张效果图的情况。
随着设计与开发工具的演进,一些业界工具支持界面元素属性的自动识别并创建标注,也可导出支持动态展示标注的效果图文档,一般为html格式。OpenHarmony应用的设计交付同样可以借助这些工具作为标注图的补充内容,但必选的分层参数仍需设计师专门确认并手动完成标注。
除了尺寸、间距等强依赖于业务具体设计的参数,色彩、字体、圆角、阴影、模糊等属性应尽可能使用分层参数完成标注。
标注示意图
![4.8-标注图](figures/4.8-标注图.png)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> - 如遇到分层参数覆盖不到的属性值,可按照具体设计效果标注。此时必须详细考虑不同场景下该元素的效果。
## 规格说明
规格说明是一份专门编写的文档,用于更完整清晰地说明界面间的通用元素与变化规则,一般为pdf格式。规格说明不是UX设计交付件中的必选项,但它能帮助设计师节省很多重复性工作,帮助开发者快速理解UX设计规格,在面向多设备的应用设计交付中非常推荐。
在多设备设计中,规格说明常用于并排展示同一界面在不同尺寸下的效果,并说明栅格变化规则。
一多规范栅格图
![4.8-栅格标注](figures/4.8-栅格标注.png)
## 切图资源
包含在设计效果中的素材,如图标、图片、序列帧等,应根据实际需要输出为合适的格式提供开发者置入界面内。
为了更好在多设备上根据设备dpi展示清晰的图像,一分优秀的应用应含有多套同名的切图资源,它们分别存放在mdpi、ldpi、xldpi、xxldpi的文件夹目录下,最终会存放到应用资源包的同名路径中。
多套同名切图文件夹示意图
![4.8-切图资源对应文件夹](figures/4.8-切图资源对应文件夹.png)
图标资源可以是png、jpg、webp、svg等格式。推荐在多设备设计中使用svg图标资源,因为能充分利用矢量图片体积较小、可以自由缩放且不出现锯齿、可根据色彩参数实时赋色的特点,仅用一套资源即可满足复杂场景的UX规范。如使用其他位图格式如png、jpg、webp,则需分别交付各dpi下的切图资源,以达到边缘像素清晰的体验。
图片资源可以是png、jpg、webp等格式。与图标资源类似,一般应提供各dpi下的图片资源。在不同dpi下对边缘像素要求不高的图片如背景图,则推荐按照更高dpi提供一张资源复用到各dpi,以减少应用包大小。
在多设备设计中,图片也可根据设计效果,在不同宽度断点使用同一个资源,或分别交付:
- 当图片所在组件接口提供的缩放显示机制满足UX设计效果,可以用同一个资源
- 当图片所在组件接口提供的缩放显示机制不满足、且不可通过简单的自定义规则实现UX效果,则推荐使用不同资源
夜晚单张适配多设备宽度示意图
![4.8-夜晚单张适配多设备](figures/4.8-夜晚单张适配多设备.png)
晴天多张适配多设备宽度示意图
![4.8-晴天多张适配](figures/4.8-晴天多张适配.png)
...@@ -13,5 +13,8 @@ ...@@ -13,5 +13,8 @@
Margin、Gutter的大小、Column的数量均可自定义,界面内容跟据Column的边缘定位。通过采用不同数值调整内容信息量和紧密程度,一般推荐使用4或8的倍数。例如Margin 32vp、Gutter 16vp、Column数量为4,或Margin 40vp、Gutter 24vp、Column数量为8。 Margin、Gutter的大小、Column的数量均可自定义,界面内容跟据Column的边缘定位。通过采用不同数值调整内容信息量和紧密程度,一般推荐使用4或8的倍数。例如Margin 32vp、Gutter 16vp、Column数量为4,或Margin 40vp、Gutter 24vp、Column数量为8。
![zh-cn_image_0000001305734113](figures/zh-cn_image_0000001305734113.png) ![栅格系统例](figures/栅格系统例.png)
栅格系统对应OpenHarmony系统提供的布局能力中的栅格布局,详见本文 “[栅格布局](responsive-layout.md#栅格布局)”相关介绍。
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
例如,市场类应用作为典型的平级导航,其首页不同板块采用了不同布局能力。 例如,市场类应用作为典型的平级导航,其首页不同板块采用了不同布局能力。
![zh-cn_image_0000001317088945](figures/zh-cn_image_0000001317088945.png) ![一多-布局1](figures/一多-布局1.png)
- 标题栏与搜索栏:因元素单一、位置固定在顶部,因此适合采用自适应拉伸,并在大尺寸界面中从纵排变为横排,充分利用顶部区域。 - 标题栏与搜索栏:因元素单一、位置固定在顶部,因此适合采用自适应拉伸,并在大尺寸界面中从纵排变为横排,充分利用顶部区域。
...@@ -19,11 +19,11 @@ ...@@ -19,11 +19,11 @@
在横竖屏切换时,也保持了一致的布局能力,实际上完成了大尺寸和中尺寸的切换。 在横竖屏切换时,也保持了一致的布局能力,实际上完成了大尺寸和中尺寸的切换。
![zh-cn_image_0000001317328797](figures/zh-cn_image_0000001317328797.png) ![一多-布局2](figures/一多-布局2.png)
当界面出现在智慧屏上,虽然同是大尺寸界面,为了符合设备样式和遥控器交互规则,搜索栏转化为图标入口,导航栏挪移到页面上部。 当界面出现在智慧屏上,虽然同是大尺寸界面,为了符合设备样式和遥控器交互规则,搜索栏转化为图标入口,导航栏挪移到页面上部。
![zh-cn_image_0000001268448854](figures/zh-cn_image_0000001268448854.png) ![一多-布局3](figures/一多-布局3.png)
## 层级导航的列表视图 ## 层级导航的列表视图
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
例如,设置类应用作为典型的层级导航,其列表控件采用自适应拉伸。 例如,设置类应用作为典型的层级导航,其列表控件采用自适应拉伸。
![zh-cn_image_0000001268128998](figures/zh-cn_image_0000001268128998.png) ![布局基础案例-层级导航-设置](figures/布局基础案例-层级导航-设置.png)
在中尺寸设备中,为避免中间区域空白过大,采用缩进布局,大尺寸设备中,为充分利用横向空间,建议采用栅格系统形成分栏效果,并让列表元素在各自区域保持拉伸。 在中尺寸设备中,为避免中间区域空白过大,采用缩进布局,大尺寸设备中,为充分利用横向空间,建议采用栅格系统形成分栏效果,并让列表元素在各自区域保持拉伸。
...@@ -53,14 +53,14 @@ ...@@ -53,14 +53,14 @@
- 播放栏:固定在界面底部,保持左右拉伸即可。 - 播放栏:固定在界面底部,保持左右拉伸即可。
![zh-cn_image_0000001268288870](figures/zh-cn_image_0000001268288870.png) ![页面布局-布局基础案例-歌单详情页面布局能力360-800vp](figures/页面布局-布局基础案例-歌单详情页面布局能力360-800vp.png)
在横竖屏切换时,完成了中尺寸和大尺寸的切换。歌单列表板块进行挪移的同时,内部采用了重复布局。 在横竖屏切换时,完成了中尺寸和大尺寸的切换。歌单列表板块进行挪移的同时,内部采用了重复布局。
歌单信息和歌单操作板块因较小宽高比,挪移到上下排布。 歌单信息和歌单操作板块因较小宽高比,挪移到上下排布。
![zh-cn_image_0000001268608782](figures/zh-cn_image_0000001268608782.png) ![页面布局-布局基础案例-歌单详情页面布局能力800-1280vp](figures/页面布局-布局基础案例-歌单详情页面布局能力800-1280vp.png)
当界面出现在智慧屏上,为了符合沉浸简约的设备信息和遥控器交互规则,将部分歌单信息替代原来标题栏的位置,并取消播放栏。同时歌单列表居左,更方便遥控器选择。 当界面出现在智慧屏上,为了符合沉浸简约的设备信息和遥控器交互规则,将部分歌单信息替代原来标题栏的位置,并取消播放栏。同时歌单列表居左,更方便遥控器选择。
![zh-cn_image_0000001317208833](figures/zh-cn_image_0000001317208833.png) ![页面布局-布局基础案例-歌单详情页面布局能力1280-1920vp](figures/页面布局-布局基础案例-歌单详情页面布局能力1280-1920vp.png)
...@@ -25,7 +25,12 @@ ...@@ -25,7 +25,12 @@
常见的状态类型: 常见的状态类型:
| | | | | | | |
| -------- | -------- | -------- | | -------- | -------- | -------- |
| ![zh-cn_image_0000001268288974](figures/zh-cn_image_0000001268288974.gif)<br/>**正常态**<br/>表明当前控件可交互。 | ![zh-cn_image_0000001268608890](figures/zh-cn_image_0000001268608890.gif)<br/>**不可用态**<br/>表明当前控件不可交互。一般使用灰显的方式呈现。 | ![zh-cn_image_0000001317208945](figures/zh-cn_image_0000001317208945.gif)<br/>**点击态**<br/>表明当前控件当前处于点击状态。<br/>操作:手指或鼠标按下且未释放。 | | ![zh-cn_image_0000001268288974](figures/zh-cn_image_0000001268288974.gif)<br/>**正常态**<br/>表明当前控件可交互。 | ![zh-cn_image_0000001268608890](figures/zh-cn_image_0000001268608890.gif)<br/>**不可用态**<br/>表明当前控件不可交互。一般使用灰显的方式呈现。 | ![zh-cn_image_0000001317208945](figures/zh-cn_image_0000001317208945.gif)<br/>**点击态**<br/>表明当前控件当前处于点击状态。<br/>操作:手指或鼠标按下且未释放。 |
| ![zh-cn_image_0000001317488873](figures/zh-cn_image_0000001317488873.gif)<br/>**获焦态**<br/>表明当前控件处于焦点状态。操作:<br/>-&nbsp;键盘或遥控器通过方向键将焦点从一个控件移动到另外一个控件。<br/>-&nbsp;通过语音操作,使得控件获得焦点。 | ![zh-cn_image_0000001317089061](figures/zh-cn_image_0000001317089061.gif)<br/>**激活态**<br/>表明当前控件处于激活的状态。用于有多个元素可获焦的控件<br/>操作:焦点处在页签控件的某个页签上时,该页签获焦。点击此页签,该页签被激活。 | ![zh-cn_image_0000001317328893](figures/zh-cn_image_0000001317328893.gif)<br/>**悬停态**<br/>表明当前控件处于鼠标悬停的状态。<br/>操作:将鼠标悬停在控件之上。 | | ![zh-cn_image_0000001317488873](figures/zh-cn_image_0000001317488873.gif)<br/>**获焦态**<br/>表明当前控件处于焦点状态。操作:<br/>-&nbsp;键盘或遥控器通过方向键将焦点从一个控件移动到另外一个控件。<br/>-&nbsp;通过语音操作,使得控件获得焦点。 | ![zh-cn_image_0000001317089061](figures/zh-cn_image_0000001317089061.gif)<br/>**激活态**<br/>表明当前控件处于激活的状态。用于有多个元素可获焦的控件<br/>操作:焦点处在页签控件的某个页签上时,该页签获焦。点击此页签,该页签被激活。 | ![zh-cn_image_0000001317328893](figures/zh-cn_image_0000001317328893.gif)<br/>**悬停态**<br/>表明当前控件处于鼠标悬停的状态。<br/>操作:将鼠标悬停在控件之上。 |
## 弹出框
弹出框是一种模态窗口,在弹出框消失之前,用户无法操作其他界面内容,干扰性比较强。通常用来展示用户当前需要的或用户必须关注的信息或操作,其他情况不建议使用弹出框,可考虑通知等其他非模态窗口。 弹出框的内容通常是不同控件进行组合布局。例如文本(可带格式,如缩进、链接、粗体等)、列表、输入框、网格、图标或图片等,常用于选择或确认信息。
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
**兼容性** **兼容性**
在硬件能力、交互方式、使用场景差异较大的设备上,除了考虑布局位置、内容宽度、横向组件数量,还需支持不同的输入方式、考虑功能架构的调整,避免出现输入不识别、功能不可用、使用困难的问题。 在硬件能力、交互方式、使用场景差异较大的设备上,除了考虑布局位置、内容宽度、横向组件数量,还需支持不同的输入方式、考虑功能架构的调整,避免出现输入不识别、功能不可用、使用困难的问题。
## 设计要点 ## 设计要点
...@@ -35,9 +35,9 @@ ...@@ -35,9 +35,9 @@
例如:默认设备上的底Tab的结构,在Pad上一般使用侧边Tab来代替,在大屏上则是顶部Tab。 例如:默认设备上的底Tab的结构,在Pad上一般使用侧边Tab来代替,在大屏上则是顶部Tab。
![zh-cn_image_0000001317325609](figures/zh-cn_image_0000001317325609.png) ![一多-1-1](figures/一多-1-1.png)
更多应用架构的设计内容,详见:[应用导航结构设计](navigation-design.md)[应用页面结构设计](page-design.md) 更多应用架构的设计内容,详见:[应用架构](architecture-design.md)
### 响应式界面布局 ### 响应式界面布局
...@@ -48,31 +48,31 @@ OpenHarmony 提供了多种布局能力,开发者通过组合运用使内容 ...@@ -48,31 +48,31 @@ OpenHarmony 提供了多种布局能力,开发者通过组合运用使内容
例如:默认设备上的滚动banner,在其他设备上可进行延伸,平板上露出更多banner,大屏上完全显示两张。 例如:默认设备上的滚动banner,在其他设备上可进行延伸,平板上露出更多banner,大屏上完全显示两张。
![zh-cn_image_0000001317485573](figures/zh-cn_image_0000001317485573.png) ![一多-概述-界面布局-banner例图](figures/一多-概述-界面布局-banner例图.png)
在不同类型的设备上,界面的尺寸和比例更为多样,再加上使用上的差异,导致设计上更为复杂。为此,可以考虑使用分栏布局、重复布局、挪移布局、缩进布局,进一步解决内容的显示问题。 在不同类型的设备上,界面的尺寸和比例更为多样,再加上使用上的差异,导致设计上更为复杂。为此,可以考虑使用分栏布局、重复布局、挪移布局、缩进布局,进一步解决内容的显示问题。
例如:默认设备上上下排布的大图与列表,在长宽比例更大的设备上可挪移到左右展示。 例如:默认设备上上下排布的大图与列表,在长宽比例更大的设备上可挪移到左右展示。
![zh-cn_image_0000001268285678](figures/zh-cn_image_0000001268285678.png) ![概述-界面布局-歌单详情高保真](figures/概述-界面布局-歌单详情高保真.png)
更多界面布局的设计内容,详见:[界面布局](layout-design-intro.md) 更多界面布局的设计内容,详见:[界面布局](interface-layout-design.md)
### 交互归一 ### 交互归一
交互归一描述了在多种交互任务或场景下,应用在触屏上和其它常用的输入方式(例如鼠标、触摸板、键盘)上分别对应的正确的交互规则。设计师和开发者应保证在当前输入方式下应用能够以正确的、符合用户习惯的交互规则进行响应。通常情况下,系统已经做好了这些事情,开发者只需正确调用。如果您的操作比较特别,您需要考虑多端上的交互归一,以确保用户体验的一致。 交互归一描述了在多种交互任务或场景下,应用在触屏上和其它常用的输入方式(例如鼠标、触摸板、键盘)上分别对应的正确的交互规则。设计师和开发者应保证在当前输入方式下应用能够以正确的、符合用户习惯的交互规则进行响应。通常情况下,系统已经做好了这些事情,开发者只需正确调用。如果您的操作比较特别,您需要考虑多端上的交互归一,以确保用户体验的一致。
更多交互归一的设计内容,详见:[人机交互](interaction-basics.md) 更多交互归一的设计内容,详见:[人机交互](man-machine-interaction.md)
### 视觉参数化 ### 视觉参数化
通过参数,方便的调整各端的视觉,使得各端具备该设备特有的风格。在OpenHarmony中,边距、圆角、阴影、字体大小等,都可以通过参数来进行调整。 通过参数,方便的调整各端的视觉,使得各端具备该设备特有的风格。在OpenHarmony中,边距、圆角、阴影、字体大小等,都可以通过参数来进行调整。
![zh-cn_image_0000001317085757](figures/zh-cn_image_0000001317085757.png) ![画板copy](figures/画板copy.png)
更多视觉参数化的设计内容,详见:[视觉风格](visual-style-basics.md) 更多视觉参数化的设计内容,详见:[视觉风格](visual-style.md)
### 多态控件 ### 多态控件
...@@ -91,6 +91,6 @@ OpenHarmony默认提供支持多设备的控件,开发者可以直接使用并 ...@@ -91,6 +91,6 @@ OpenHarmony默认提供支持多设备的控件,开发者可以直接使用并
| **以触控为主** | **以键鼠操作为主** | | **以触控为主** | **以键鼠操作为主** |
| -------- | -------- | | -------- | -------- |
| 下拉刷新 | 界面上提供“刷新”图标或适配F5快捷键 | | 下拉刷新 | 界面上提供“刷新”图标或适配F5快捷键 |
| 滑动多选 | 鼠标&nbsp;框选 | | 滑动多选 | 鼠标框选 |
| 下拉关闭 | 界面上提供“关闭”图标 | | 下拉关闭 | 界面上提供“关闭”图标 |
| 长按浮起拖拽 | 鼠标直接拖拽 | | 长按浮起拖拽 | 鼠标直接拖拽 |
# 资源 # 资源
为方便UX设计师以及开发者使用[分层参数](visual-style-basics.md),本文特提供 [OpenHarmony_系统资源分层设计表_V1.0.xlsm](OpenHarmony_系统资源分层设计表_V1.0.xlsm)
为方便UX设计师以及开发者参考使用,本文特提供:
- [分层参数](visual-basics.md)的场景、id、参数详细对照表,[OpenHarmony_系统资源分层设计表_V1.0.xlsm](OpenHarmony_系统资源分层设计表_V1.0.xlsm)
- 符合规范的[设计交付件](design-delivery.md)样例,[OpenHarmony_天气应用UX设计交付件_V1.0.zip](OpenHarmony_天气应用UX设计交付件_V1.0.zip)
系统资源分层设计表列出了当前OpenHarmony中可用系统资源id及其在不同类型设备上的取值,它由六张子表组成,各个子表的含义如下所示。 系统资源分层设计表列出了当前OpenHarmony中可用系统资源id及其在不同类型设备上的取值,它由六张子表组成,各个子表的含义如下所示。
| 表格 | 简介 | | 表格 | 简介 |
| ------------------ | ------------------------------------------------------------ | | -------- | -------- |
| 应用色彩参数 | 在**应用开发**过程中可以使用的**色彩**相关的系统资源。 | | 应用色彩参数 | 在**应用开发**过程中可以使用的**色彩**相关的系统资源。 |
| 应用圆角参数 | 在**应用开发**过程中可以使用的**圆角**相关的系统资源。 | | 应用圆角参数 | 在**应用开发**过程中可以使用的**圆角**相关的系统资源。 |
| 应用字体参数 | 在**应用开发**过程中可以使用的**字体**相关的系统资源。 | | 应用字体参数 | 在**应用开发**过程中可以使用的**字体**相关的系统资源。 |
...@@ -13,8 +21,10 @@ ...@@ -13,8 +21,10 @@
| 服务卡片参数 | 在**服务卡片开发**过程中可以使用的系统资源。 | | 服务卡片参数 | 在**服务卡片开发**过程中可以使用的系统资源。 |
| 不透明度数值速查表 | 用于将不透明度在**百分比表示形式****十六进制表示形式**之间快速转换的速查表。 | | 不透明度数值速查表 | 用于将不透明度在**百分比表示形式****十六进制表示形式**之间快速转换的速查表。 |
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:** > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
>
> - 推荐应用相关系统参数仅在应用开发场景中使用,卡片相关系统参数仅在卡片开发场景中使用。 > - 推荐应用相关系统参数仅在应用开发场景中使用,卡片相关系统参数仅在卡片开发场景中使用。
>
> - 同一系统参数在不同类型的设备上有不同的取值,当前仅提供了系统参数在默认设备上的取值,后续会针对不同设备类型做补充。 > - 同一系统参数在不同类型的设备上有不同的取值,当前仅提供了系统参数在默认设备上的取值,后续会针对不同设备类型做补充。
> - 颜色可以用“RGB”或“ARGB”形式表示,采用“RGB”表示的颜色,完全不透明;采用“ARGB”表示的颜色,其不透明度由“A”(Alpha通道)确定。如“#7FFF0000”代表不透明度为50%的红色,“#FFFF0000”和“#FF0000”都表示不透明度为100%(即完全不透明)的红色。 >
\ No newline at end of file > - 颜色可以用“RGB”或“ARGB”形式表示,采用“RGB”表示的颜色,完全不透明;采用“ARGB”表示的颜色,其不透明度由“A”(Alpha通道)确定。如“\#7FFF0000”代表不透明度为50%的红色,“\#FFFF0000”和“\#FF0000”都表示不透明度为100%(即完全不透明)的红色。
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
根据设备的水平宽度,OpenHarmony提供了断点系统,覆盖超小、小、中、大 四种屏幕类型,并结合栅格系统默认提供了对应 Column 的数量关系。不同的设备根据自身屏幕水平宽度,在不同的断点范围,系统将自动匹配不同数量的栅格。应用也可针对具体界面自定义栅格。 根据设备的水平宽度,OpenHarmony提供了断点系统,覆盖超小、小、中、大 四种屏幕类型,并结合栅格系统默认提供了对应 Column 的数量关系。不同的设备根据自身屏幕水平宽度,在不同的断点范围,系统将自动匹配不同数量的栅格。应用也可针对具体界面自定义栅格。
![zh-cn_image_0000001305864477](figures/zh-cn_image_0000001305864477.png) ![删格](figures/删格.png)
栅格断点系统与日常使用的设备屏幕类型有一定的对应关系,例如:超小对应智能穿戴设备,小对应默认设备,中对应平板,大对应智慧屏与PC。设计师可面向希望运行的设备进行所属屏幕类型的适配。 栅格断点系统与日常使用的设备屏幕类型有一定的对应关系,例如:超小对应智能穿戴设备,小对应默认设备,中对应平板,大对应智慧屏与PC。设计师可面向希望运行的设备进行所属屏幕类型的适配。
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
为了在宽屏上内容显示有更好的效果,在不同宽度的设备上进行不同缩进效果。 为了在宽屏上内容显示有更好的效果,在不同宽度的设备上进行不同缩进效果。
![zh-cn_image_0000001264774952](figures/zh-cn_image_0000001264774952.gif) ![缩进布局](figures/缩进布局.gif)
缩进适用于,因宽度明显变大,内容拉伸以后导致屏幕空白内容超过50%,或文本内容过长(每行大于30字),但没有上下级界面可供同时展示或上下级界面不适合同时显示的场景。 缩进适用于,因宽度明显变大,内容拉伸以后导致屏幕空白内容超过50%,或文本内容过长(每行大于30字),但没有上下级界面可供同时展示或上下级界面不适合同时显示的场景。
...@@ -32,7 +32,7 @@ OpenHarmony提供的默认实现为,当栅格为8column或12column时可以响 ...@@ -32,7 +32,7 @@ OpenHarmony提供的默认实现为,当栅格为8column或12column时可以响
例如,上下排布的插画和文字,横屏后左右排布。 例如,上下排布的插画和文字,横屏后左右排布。
![zh-cn_image_0000001264296340](figures/zh-cn_image_0000001264296340.gif) ![挪移布局](figures/挪移布局.gif)
挪移布局适用于横竖屏切换,以及类似的宽高比明显变化(大于200%)同时希望保证内容完整的场景。 挪移布局适用于横竖屏切换,以及类似的宽高比明显变化(大于200%)同时希望保证内容完整的场景。
...@@ -41,11 +41,11 @@ OpenHarmony提供的默认实现为,当栅格为8column或12column时可以响 ...@@ -41,11 +41,11 @@ OpenHarmony提供的默认实现为,当栅格为8column或12column时可以响
利用屏幕的宽度优势,将相同属性的组件横向并列排布。 利用屏幕的宽度优势,将相同属性的组件横向并列排布。
![zh-cn_image_0000001306203573](figures/zh-cn_image_0000001306203573.gif) ![重复布局](figures/重复布局.gif)
重复布局适用于对宽高比敏感的图片和及组合内容,当内容缩放以后导致原图放大超过150%的场景。 重复布局适用于对宽高比敏感的图片和及组合内容,当内容缩放以后导致原图放大超过150%的场景。
OpenHarmony栅格系统提供的分栏实现为,当栅格为8column或12column时可以将默认4栅格的页面整体进行重复布局。 OpenHarmony栅格系统提供的分栏实现为,当栅格为8column或12column时可以将默认4栅格的页面整体进行重复布局。
响应式布局对应OpenHarmony系统提供的布局能力中的[栅格断点系统](grid-breakpoint.md)[媒体查询](media-query.md),详见本文 “响应式布局”。 响应式布局对应OpenHarmony系统提供的布局能力中的栅格断点系统和媒体查询,详见本文 “[响应式布局](responsive-layout.md)”相关介绍。
# 一多能力的功能开发介绍 # 功能开发的一多能力介绍
应用开发至少包含两部分工作:UI页面开发和底层功能开发(部分需要联网的应用还会涉及服务端开发)。如“打开设备NFC”功能,除了开发页面,还需要调用系统API开启NFC。前面章节主要介绍了如何解决页面适配的问题,本章节主要介绍应用如何解决设备系统能力差异的兼容问题。 应用开发至少包含两部分工作:UI页面开发和底层功能开发(部分需要联网的应用还会涉及服务端开发)。如“打开设备NFC”功能,除了开发页面,还需要调用系统API开启NFC。前面章节主要介绍了如何解决页面适配的问题,本章节主要介绍应用如何解决设备系统能力差异的兼容问题。
...@@ -57,7 +57,7 @@ OpenHarmony支持的设备类型分为两大类: ...@@ -57,7 +57,7 @@ OpenHarmony支持的设备类型分为两大类:
IDE中提供了API的联想功能,方便开发者使用系统能力。当开发者选择多个设备类型时,API的联想范围就是选择类型设备提供的API的并集,如同时支撑默认设备和平板,API的联想范围就是默认设备和平板支持的API的并集。API的联想效果如下: IDE中提供了API的联想功能,方便开发者使用系统能力。当开发者选择多个设备类型时,API的联想范围就是选择类型设备提供的API的并集,如同时支撑默认设备和平板,API的联想范围就是默认设备和平板支持的API的并集。API的联想效果如下:
![zh-cn_image_0000001267334018](figures/zh-cn_image_0000001267334018.gif) ![Video_20220408101413](figures/Video_20220408101413.gif)
## 动态逻辑判断 ## 动态逻辑判断
...@@ -67,15 +67,30 @@ IDE中提供了API的联想功能,方便开发者使用系统能力。当开 ...@@ -67,15 +67,30 @@ IDE中提供了API的联想功能,方便开发者使用系统能力。当开
``` ```
import geolocation from'@ohos.geolocation'; import geolocation from'@ohos.geolocation';
const isLocationAvailable =canIUse('SystemCapability.Location.Location');
if (isLocationAvailable) { @Entry
console.log('该设备支持位置信息'); @Component
geolocation.getCurrentLocation((location) => { struct Index {
console.log(location.latitude, location.longitude); @State message: string = 'unknown';
aboutToAppear() {
if (canIUse('SystemCapability.Location.Location')) {
geolocation.getCurrentLocation().then((location) => {
this.message = 'current location: ' + JSON.stringify(location)
}) })
} else { } else {
console.log('该设备不支持位置信息'); this.message = 'This device does not have the ability to get location.'
}
}
build() {
Row() {
Text(this.message).fontSize(24)
}
.justifyContent(FlexAlign.Center)
.width('100%')
.height('100%')
} }
}
``` ```
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:** > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
......
# 常见问题
## 如何查询设备类型
设备类型分为default(默认设备)、tablet、tv、wearable等,有多种查询设备类型的方式。
1. 通过命令行的方式查询设备类型。
通过命令行查询指定系统参数(const.build.characteristics)进而确定设备类型,详见[系统参数介绍](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/subsystems/subsys-boot-init-sysparam.md)
```shell
# 方法一
hdc shell param get "const.build.characteristics"
# 方法二
hdc shell cat /etc/param/ohos.para | grep const.build.characteristic
```
2. 在应用开发过程中查询设备类型。
- 通过js接口查询指定系统参数(const.build.characteristics)进而确定设备类型,详见[系统属性](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis/js-apis-system-parameter.md)
```typescript
import parameter from '@ohos.systemparameter'
@Entry
@Component
struct GetDeviceTypeSample {
@State deviceType: string = 'unknown'
aboutToAppear() {
try {
this.deviceType = parameter.getSync("const.build.characteristics")
} catch(e) {
console.log("getSync unexpected error: " + e)
}
}
build() {
Column() {
Text(this.deviceType).fontSize(24)
}
.width('100%')
.height('100%')
}
}
```
- 通过deviceInfo查询设备类型,deviceInfo中各个字段的含义请参考[设备信息](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis/js-apis-device-info.md)
```typescript
import deviceInfo from'@ohos.deviceInfo'
@Entry
@Component
struct GetDeviceTypeSample {
@State deviceType:string='unknown'
aboutToAppear() {
this.deviceType= deviceInfo.deviceType
}
build() {
Column() {
Text(this.deviceType).fontSize(24)
}
.width('100%')
.height('100%')
}
}
```
## 如何在不同设备上为Ability配置不同的启动模式
应用由一个或多个Ability组成,Ability支持单实例、多实例和指定实例3种[启动模式](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/ability/stage-ability.md#%E5%90%AF%E5%8A%A8%E6%A8%A1%E5%BC%8F),启动模式可以在[配置文件(module.json5)](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/quick-start/stage-structure.md)中通过launchType字段配置。启动模式对应Ability被启动时的行为,对启动模式的详细说明如下:
| 启动模式 | 描述 | 说明 |
| -------- | -------- | -------- |
| standard | 多实例 | 每次startAbility都会启动一个新的实例。 |
| singleton | 单实例 | 系统中最多只可以存在一个实例,startAbility时,如果系统中已存在相应的Ability实例,则复用该实例。 |
| specified | 指定实例 | 运行时由Ability内部业务决定是否创建多实例。 |
默认设备屏幕尺寸较小,采用standard启动模式不仅无法给用户提供便利,反而可能消耗更多系统资源,故通常采用singleton启动模式。平板屏幕尺寸较大且可能支持自由窗口,对于文档编辑、网页浏览等场景,使用standard启动模式可以提升用户体验。
本文中将默认设备和平板等归为同一泛类,推荐同一泛类的设备共用HAP包,同时本文也介绍了如何通过自适应布局能力和响应式布局能力开发出适配不同设备的页面。这里将补充介绍,如何实现Ability在不同设备上以不同的模式启动。
launchType字段配置为specified时,系统会根据AbilityStage的onAcceptWant的返回值确定是否创建新的实例。对于同一个应用,如果key已经存在,则复用该key对应的Ability,如果key不存在则新创建Ability。
可以将配置文件中的launchType字段配置为specified,同时在应用中加入如下代码以实现目标效果。
- 非平板设备,直接将设备类型作为key,保证每次启动的key相同,即以单实例模式运行。
- 平板设备,将设备类型与毫秒级时间戳叠加作为key,保证每次启动的key不同,即以多实例模式运行。
```typescript
// MyAbilityStage.ts
import AbilityStage from "@ohos.application.AbilityStage"
import deviceInfo from'@ohos.deviceInfo'
export default class MyAbilityStage extends AbilityStage {
...
private generateKey(): string {
// 如果是平板,则将设备类型和毫秒级时间戳叠加作为key,保证每次启动的key都不同
if (deviceInfo.deviceType === 'tablet') {
return deviceInfo.deviceType + (new Date()).valueOf()
}
// 如果不是平板,直接以设备类型作为key,每次启动的key相同
return deviceInfo.deviceType
}
onAcceptWant(want) {
return this.generateKey()
}
}
```
## 如何开启自由窗口
开发板上的自由窗口功能默认是关闭的,可以通过如下方式开启自由窗口功能。
```shell
# 取出开发板中的窗口配置文件,并将文件中的<decor enable="false"></decor>修改为<decor enable="true"></decor>
hdc file recv system/etc/window/resources/window_manager_config.xml ./
# 以可读写的模式重新挂载根目录,并更新开发板中的配置文件
hdc shell mount -o rw,remount /
hdc file send window_manager_config.xml system/etc/window/resources/window_manager_config.xml
# 重启开发板,配置生效
hdc shell reboot
```
开发板屏幕较小,通过手指操作窗口较为不便,建议外接鼠标进行操作。
- 鼠标在应用顶部悬停,即可召唤出窗口工具栏。
- 点击窗口工具栏中的缩放按钮(从左到右第二个),即可让应用以自由窗口的模式显示。
- 在自由窗口模式下,可以通过拖动应用窗口的边框或顶角,改变窗口尺寸同时触发应用显示刷新。
在调整窗口尺寸的过程中,窗口尺寸可能超出屏幕尺寸。此时应用显示正常,但受限于屏幕尺寸,在屏幕中只能看到应用部分区域的显示。可以通过移动窗口位置,查看应用其它区域的显示。
| 窗口操作按钮 | 悬浮窗口显示 | 调整窗口尺寸及位置查看不同的效果 |
| -------- | -------- | -------- |
| ![img2](figures/img2.png) | ![img3](figures/img3.png) | ![img4](figures/img4.png) |
## 如何限制自由窗口的尺寸调节范围
自适应布局可以保证窗口尺寸在一定范围内变化时,页面的显示是正常的。当窗口尺寸变化较大时,就需要额外借助响应式布局能力(如断点等)调整页面结构以保证显示正常。通常每个断点都需要开发者精心适配以获得最佳的显示效果,考虑到设计及开发成本等实际因素的限制,应用不可能适配从零到正无穷的所有窗口宽度。
不同设备或不同设备状态,系统默认的自由窗口尺寸的调节范围可能不同。开发者可以在[应用配置文件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/quick-start/stage-structure.md)中限制应用中各个Ability的自由窗口尺寸调节范围,配置文件中影响自由窗口尺寸调节范围的字段如下表所示。
| 配置文件字段 | 数据类型 | 描述 |
| -------- | -------- | -------- |
| minWindowWidth | 数值 | 标识该ability支持的最小的窗口宽度,&nbsp;宽度单位为vp。 |
| minWindowHeight | 数值 | 标识该ability支持的最小的窗口高度,&nbsp;高度单位为vp。 |
| maxWindowWidth | 数值 | 标识该ability支持的最大的窗口宽度,宽度单位为vp。 |
| maxWindowHeight | 数值 | 标识该ability支持的最大的窗口高度,&nbsp;高度单位为vp。 |
| minWindowRatio | 数值 | 标识该ability支持的最小的宽高比。 |
| maxWindowRatio | 数值 | 标识该ability支持的最大的宽高比。 |
如下所示,通过配置文件分别限制自由窗口的最大和最小尺寸。
```
{
"module": {
...
"abilities": [
{
...
"minWindowWidth": 320,
"minWindowHeight": 240,
"maxWindowWidth": 1440,
"maxWindowHeight": 900,
"minWindowRatio": 0.5,
"maxWindowRatio": 2,
}
]
}
}
```
# 常见问题
## 如何查询设备类型
设备类型分为default(默认设备)、tablet、tv、wearable等,有多种查询设备类型的方式。
1. 通过命令行的方式查询设备类型。
通过命令行查询指定系统参数(const.build.characteristics)进而确定设备类型,详见[系统参数介绍](../../../device-dev/subsystems/subsys-boot-init-sysparam.md)
```bash
# 方法一
hdc shell param get "const.build.characteristics"
# 方法二
hdc shell cat /etc/param/ohos.para | grep const.build.characteristic
```
2. 在应用开发过程中查询设备类型。
- 通过js接口查询指定系统参数(const.build.characteristics)进而确定设备类型,详见[系统属性](../../reference/apis/js-apis-system-parameter.md)
```ts
import parameter from '@ohos.systemParameter'
@Entry
@Component
struct GetDeviceTypeSample {
@State deviceType: string = 'unknown';
aboutToAppear() {
try {
this.deviceType = parameter.getSync("const.build.characteristics");
} catch(e) {
console.log("getSync unexpected error: " + e);
}
}
build() {
Column() {
Text(this.deviceType).fontSize(24)
}
.width('100%')
.height('100%')
}
}
```
- 通过deviceInfo查询设备类型,deviceInfo中各个字段的含义请参考[设备信息](../../reference/apis/js-apis-device-info.md)
```ts
import deviceInfo from'@ohos.deviceInfo'
@Entry
@Component
struct GetDeviceTypeSample {
@State deviceType:string='unknown';
aboutToAppear() {
this.deviceType= deviceInfo.deviceType;
}
build() {
Column() {
Text(this.deviceType).fontSize()
}
.width('100%')
.height('100%')
}
}
```
## 如何查询屏幕/窗口尺寸
在应用开发过程中,为了在不同的设备上取得更好的显示效果,开发者可能需要查询屏幕尺寸或应用显示窗口尺寸。
- 通过display查询显示设备的属性(包括屏幕宽、高和屏幕密度等),详见[屏幕属性](../../reference/apis/js-apis-display.md)
```ts
private aboutToAppear() {
display.getDefaultDisplay()
.then((displayInfo) => {
console.info('Display width: '+ displayInfo.width);
console.info('Display height: '+ displayInfo.height);
console.info('Display density: '+ displayInfo.densityDPI);
})
.catch((error) => {
console.error('Failed to obtain the default display size. Cause: '+JSON.stringify(error));
})
}
```
- 通过window.getTopWindow获取应用窗口,进而查询应用窗口的宽高等,详见[窗口](../../reference/apis/js-apis-window.md)
注意必须在应用创建窗口后才可以拿到窗口对象,window.getTopWindow依赖AbilityContext作为入参,可以在MainAbility中通过this.context拿到AbilityContext对象,详见[Ability开发指导](../../ability/stage-ability.md)。可以在MainAbility的onCreate生命周期中拿到窗口尺寸,如下所示。
```ts
onCreate(want, launchParam) {
window.getTopWindow(this.context)
.then((windowClass) => { return windowClass.getProperties(); })
.then((topWindow) => {
console.info('Window width: '+ topWindow.windowRect.width);
console.info('Window height: '+ topWindow.windowRect.height);
})
.catch((error)=>{
console.error('Failed to obtain the window size. Cause: '+JSON.stringify(error));
})
}
```
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 通过窗口尺寸调整显示更加“可靠”,因为顶部状态栏、三键导航等可能占据部分屏幕空间,另外部分设备上的应用可能以窗口形式显示。
# 前言
本文介绍了“一次开发,多端部署”(后文中简称为“一多”)的定义、目标等,同时从UX设计、工程管理、页面开发、功能开发等角度,端到端的给出了指导,帮助开发者快速开发出适配多种类型设备的应用。在应用开发前,开发者应尽可能全面考虑应用支持多设备的情况,避免在后期加入新的类型设备时对应用架构进行大幅调整。
## 本文面向的读者
本文适合开发OpenHarmony应用的UX设计师及开发人员。当然,也欢迎任何对“一多”感兴趣的读者阅读本文,相信读者们都可以从中获益。
推荐尽量按照章节顺序阅读本文。如果时间有限,按照角色区分,建议至少阅读如下章节:
- UX设计师:第2章、第3章、第4章。
- 开发人员:第2章、第3章、第5章、第6章、第7章。
## 章节概要
应用在需求明确后,开发过程大致分为:应用设计(包含界面UX设计、业务功能设计)-&gt;工程设计和创建-&gt;功能代码实现。本指导也是基于这个流程进行的内容编排。
本文档各章节简介如下:
- 第1章前言说明本文的目的以及本文面向的读者,指引读者更好地阅读。
- 第2章[简介](introduction.md)简短介绍了“一多”的背景、定义、目标、以及用于指导后续开发的一些基础知识。
- 第3章[从一个例子开始](start-with-a-example.md)通过示例介绍“一多”应用的开发过程,让读者对“一多”有个直观认识。
- 第4章[应用UX设计](design-principles.md)介绍了应用UX设计理念。主要阐述了应用设计之初UX设计的原则和要点。该章节主要面向应用的UX设计师。
UX设计原则应该考虑多设备的“差异性” 、“一致性”、“灵活性”和“兼容性”。
UX设计要点则从6个方面阐述如何进行多设备应用设计,分别是“自适应应用架构”、“响应式界面布局”、“交互归一”、“视觉参数化”、“多态控件”和“针对性优化”。
最后,给出设计自检表,用于检查应用UX设计是否合理 。
- 第5章[工程管理](ide-using.md)介绍了从工程角度如何开始开发应用,让读者可以直接上手创建多设备应用的工程,是后面学习“一多”能力的上手基础。
- 第6章[页面开发的一多能力介绍](page-development-intro.md)和第7章[功能开发的一多能力介绍](development-intro.md)介绍了OpenHarmony提供的“一多”能力,其中每个能力都提供了代码示例和UX效果,让读者可以快速学习“一多”能力。
- 第8章[案例应用](case.md)阐述了从应用设计到开发这一过程中如何实践前面章节介绍的设计思路或“一多”能力,让读者可以整体掌握“一多”在应用开发过程中的知识。
- 第9章[常见问题](faq.md)提供了常见的问题(FAQ),方便读者查阅。
本指导在介绍过程中还包括一些“说明”。这些“说明”,表示例外情况或者额外信息的补充。“说明”在文中如下所示:
> **说明:**<br/>此处承载说明内容。
# 栅格断点系统
栅格断点系统定义了不同水平宽度设备对应的Column数量关系,形成了一套断点规则定义。其以水平值作为断点依据,不同的设备根据自身当前水平宽度值在不同断点范围内的情况,显示不同数量的栅格数。
![zh-cn_image_0000001292375365](figures/zh-cn_image_0000001292375365.png)
| 水平宽度&nbsp;(vp) | SizeType类型 | Columns数量 | 默认Margin | 默认Gutter | 典型设备 |
| -------- | -------- | -------- | -------- | -------- | -------- |
| [0,&nbsp;320) | XS | 2 | 12vp | 12vp | 智能穿戴 |
| [320,&nbsp;600) | SM | 4 | 24vp | 24vp | 默认设备 |
| [600,&nbsp;840) | MD | 8 | 32vp | 24vp | 智慧屏、竖屏状态的平板等 |
| [840,&nbsp;+∞) | LG | 12 | 48vp | 24vp | 车机、横屏状态的平板等 |
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> - 请访问[栅格布局](../../ui/ui-ts-layout-grid-container.md),了解栅格布局更详细的介绍。
>
> - 声明式开发范式,请访问[GridContainer组件](../../reference/arkui-ts/ts-container-gridcontainer.md)及[栅格设置](../../reference/arkui-ts/ts-universal-attributes-grid.md),了解栅格布局的详细用法。
>
> - 类Web开发范式,通过[grid-container](../../reference/arkui-js/js-components-grid-container.md)、 [grid-row](../../reference/arkui-js/js-components-grid-row.md)、[grid-col](../../reference/arkui-js/js-components-grid-col.md)组件来实现栅格布局,请访问相应的链接了解其详细用法。
## 示例
- 默认设备屏幕宽度为360vp,属于SizeType.SM类型设备,Column数量为4。根据示例代码配置,在该设备上,Left占1列,Center占2列,Right占1列。
- 平板屏幕宽度为1024vp,属于SizeType.LG类型设备,Column数量为12。根据示例代码配置,在该设备上,Left占2列,Center占7列,Right占3列。
![zh-cn_image_0000001245456434](figures/zh-cn_image_0000001245456434.jpg)
```ts
@Entry
@Component
struct GridContainerExample1 {
build(){
GridContainer() {
Row({}) {
Row() {
Text('Left').fontSize(25)
}
.useSizeType({
xs: { span:1, offset:0 }, sm: { span:1, offset:0 },
md: { span:1, offset:0 }, lg: { span:2, offset:0 }
})
.height("100%")
.backgroundColor(0x66bbb2cb)
Row() {
Text('Center').fontSize(25)
}
.useSizeType({
xs: { span:1, offset:0 }, sm: { span:2, offset:1 },
md: { span:5, offset:1 }, lg: { span:7, offset:2 }
})
.height("100%")
.backgroundColor(0x66b6c5d1)
Row() {
Text('Right').fontSize(25)
}
.useSizeType({
xs: { span:1, offset:0 }, sm: { span:1, offset:3 },
md: { span:2, offset:6 }, lg: { span:3, offset:9 }
})
.height("100%")
.backgroundColor(0x66bbb2cb)
}
.height(200)
}
.backgroundColor(0xf1f3f5)
.margin({ top:10 })
}
}
```
## 相关实例
针对栅格断点系统开发,有以下相关实例可供参考:
- [`Weather`:一多天气(eTS)(API9)](https://gitee.com/openharmony/applications_app_samples/tree/master/MultiDeviceAppDev/Weather)
\ No newline at end of file
# IDE使用 # 工程管理
DevEco Studio的基本使用,请参考[DevEco Studio使用指南](../../quick-start/deveco-studio-user-guide-for-openharmony.md)。本章主要介绍如何使用DevEco Studio进行多设备应用开发。 DevEco Studio的基本使用,请参考[DevEco Studio使用指南](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/quick-start/deveco-studio-user-guide-for-openharmony.md)。本章主要介绍如何使用DevEco Studio进行多设备应用开发。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:** > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
...@@ -14,7 +14,21 @@ DevEco Studio的基本使用,请参考[DevEco Studio使用指南](../../quick- ...@@ -14,7 +14,21 @@ DevEco Studio的基本使用,请参考[DevEco Studio使用指南](../../quick-
![zh-cn_image_0000001267274204](figures/zh-cn_image_0000001267274204.jpg) ![zh-cn_image_0000001267274204](figures/zh-cn_image_0000001267274204.jpg)
而在本文“[工程结构](introduction.md#部署模型)”小节中,推荐开发者采用common、features、product三层工程结构,这样在一个应用工程内可以更容易、清晰地管理多个设备的模块。工程结构示例如下所示: 如果直接使用如下所示的平级目录进行模块管理,工程逻辑结构较混乱且模块间的依赖关系不够清晰,不利于开发及后期维护。
```
/application
├── common
├── feature1
├── feature2
├── featureN
├── wearable
├── default
└── productN
```
更推荐使用本文[部署模型](introduction.md#部署模型)小节中介绍的common、features、product三层工程结构。工程结构示例如下所示:
``` ```
...@@ -32,19 +46,7 @@ DevEco Studio的基本使用,请参考[DevEco Studio使用指南](../../quick- ...@@ -32,19 +46,7 @@ DevEco Studio的基本使用,请参考[DevEco Studio使用指南](../../quick-
└── ... └── ...
``` ```
当然开发者也可以使用平级的目录进行模块管理,只是看上去会显得有点“杂乱”,工程结构示例如下所示: 接下来将依次介绍如何新建Module、修改配置文件以及调整目录,以实现“一多”推荐的“三层工程结构”。
```
/application
├── common
├── feature1
├── feature2
├── featureN
├── wearable
├── default
└── productN
```
## 新建Module ## 新建Module
...@@ -73,7 +75,7 @@ DevEco Studio的基本使用,请参考[DevEco Studio使用指南](../../quick- ...@@ -73,7 +75,7 @@ DevEco Studio的基本使用,请参考[DevEco Studio使用指南](../../quick-
### 修改Module类型及其设备类型 ### 修改Module类型及其设备类型
通过修改每个模块中的配置文件(module.json5)对模块进行配置,配置文件中各字段含义详见[配置文件说明](../../quick-start/stage-structure.md) 通过修改每个模块中的配置文件(module.json5)对模块进行配置,配置文件中各字段含义详见[配置文件说明](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/quick-start/stage-structure.md)
- 将default模块的deviceTypes配置为["phone", "tablet"],同时将其type字段配置为entry。 - 将default模块的deviceTypes配置为["phone", "tablet"],同时将其type字段配置为entry。
即default模块编译出的hap包在默认设备和平板上安装和运行。 即default模块编译出的hap包在默认设备和平板上安装和运行。
......
...@@ -20,4 +20,4 @@ ...@@ -20,4 +20,4 @@
设计和开发应用时,设计师和开发者应考虑到多种距离下使用的可能性,保证界面元素的大小、展示信息的密度符合用户的预期。 设计和开发应用时,设计师和开发者应考虑到多种距离下使用的可能性,保证界面元素的大小、展示信息的密度符合用户的预期。
![zh-cn_image_0000001313720673](figures/zh-cn_image_0000001313720673.png) ![位图 21](figures/位图21.png)
...@@ -8,19 +8,19 @@ ...@@ -8,19 +8,19 @@
常见的基础输入方式及其在各输入设备上的表现如下图所示。 常见的基础输入方式及其在各输入设备上的表现如下图所示。
![zh-cn_image_0000001313602309](figures/zh-cn_image_0000001313602309.jpg) ![basic_guester](figures/basic_guester.jpg)
基础输入对应的开发接口,以及当前支持情况如下表所示。 基础输入对应的开发接口,以及当前支持情况如下表所示。
| 输入 | 开发接口 | 触控屏 | 鼠标 | 触控板 | | 输入 | 开发接口 | 触控屏 | 鼠标 | 触控板 |
| -------- | -------- | -------- | -------- | -------- | | -------- | -------- | -------- | -------- | -------- |
| 点击 | [onClick](../../reference/arkui-ts/ts-universal-events-click.md) | √ | √ | √ | | 点击 | [onClick](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-universal-events-click.md) | √ | √ | √ |
| 长按 | [LongPressGesture](../../reference/arkui-ts/ts-basic-gestures-longpressgesture.md) | √ | √ | × | | 长按 | [LongPressGesture](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-basic-gestures-longpressgesture.md) | √ | √ | × |
| 双击 | [TapGesture](../../reference/arkui-ts/ts-basic-gestures-tapgesture.md) | √ | √ | √ | | 双击 | [TapGesture](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-basic-gestures-tapgesture.md) | √ | √ | √ |
| 轻扫 | [SwipeGesture](../../reference/arkui-ts/ts-basic-gestures-swipegesture.md) | √ | √ | √ | | 轻扫 | [SwipeGesture](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-basic-gestures-swipegesture.md) | √ | √ | √ |
| 滚动及平移 | [PanGesture](../../reference/arkui-ts/ts-basic-gestures-pangesture.md) | √ | √ | √ | | 滚动及平移 | [PanGesture](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-basic-gestures-pangesture.md) | √ | √ | √ |
| 缩放 | [PinchGesture](../../reference/arkui-ts/ts-basic-gestures-pinchgesture.md) | √ | √ | √ | | 缩放 | [PinchGesture](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-basic-gestures-pinchgesture.md) | √ | √ | √ |
| 旋转 | [RotationGesture](../../reference/arkui-ts/ts-basic-gestures-rotationgesture.md) | √ | NA | √ | | 旋转 | [RotationGesture](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-basic-gestures-rotationgesture.md) | √ | NA | √ |
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:** > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> - 点击事件(onClick)其实是点击手势(TapGesture)的一个特殊场景(单指单次点击)。该场景使用的非常广泛,为了方便开发者使用及符合传统开发习惯,所以专门提供了开发接口。 > - 点击事件(onClick)其实是点击手势(TapGesture)的一个特殊场景(单指单次点击)。该场景使用的非常广泛,为了方便开发者使用及符合传统开发习惯,所以专门提供了开发接口。
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
一个完整的拖拽事件,包含多个拖拽子事件,如下表所示(请访问拖拽事件了解详细用法)。当前触控屏和鼠标的拖拽事件已经实现”交互归一”,对触控屏的支持正在开发中。 一个完整的拖拽事件,包含多个拖拽子事件,如下表所示(请访问拖拽事件了解详细用法)。当前触控屏和鼠标的拖拽事件已经实现”交互归一”,对触控屏的支持正在开发中。
| 名称 | 功能描述 | | 名称 | 功能描述 |
| -------- | -------- | | -------- | -------- |
| onDragStart | 绑定A组件,触控屏长按/鼠标左键按下后移动触发 | | onDragStart | 绑定A组件,触控屏长按/鼠标左键按下后移动触发 |
| onDragEnter | 绑定B组件,触控屏手指、鼠标移动进入B组件瞬间触发 | | onDragEnter | 绑定B组件,触控屏手指、鼠标移动进入B组件瞬间触发 |
......
# 概述 # 概述
布局不是静态固定的,当显示环境发生变化时,如横竖屏切换、调节字体大小、应用分屏,要及时调整内容的布局方式以适应变化。本章提供了布局基础的概念和介绍。详见[布局基础](layout-grid.md) 布局不是静态固定的,当显示环境发生变化时,如横竖屏切换、调节字体大小、应用分屏,要及时调整内容的布局方式以适应变化。本章提供了布局基础的概念和介绍。详见[布局基础](design-grid.md)
了解布局的基础概念后,通过调用栅格系统、自适应布局和响应式布局能力就可以让内容更好地适配显示环境的变化。综合运用布局基础能力,可实现常用页面结构的多设备适配。详见[布局基础运用案例](layout-design-cases.md) 了解布局的基础概念后,通过调用栅格系统、自适应布局和响应式布局能力就可以让内容更好地适配显示环境的变化。综合运用布局基础能力,可实现常用页面结构的多设备适配。详见[布局基础运用案例](design-layout-cases.md)
# 界面布局
- **[概述](interface-layout-design-intro.md)**
- **[布局基础](layout-design-basics.md)**
- **[布局基础运用案例](design-layout-cases.md)**
\ No newline at end of file
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
## 背景 ## 背景
随着终端设备形态日益多样化,分布式技术逐渐打破单一硬件边界,一个应用或服务,可以在不同的硬件设备之间随意调用、互助共享,让用户享受无缝的全场景体验。而作为应用开发者,广泛的设备类型也能为应用带来广大的潜在用户群体。但是如果一个应用需要在多个设备上提供同样的内容,则需要适配不同的屏幕尺寸和硬件,开发成本较高。OpenHarmony 系统面向多终端提供了“一多”的能力,让开发者可以基于一种设计,高效构建多端可运行的应用。 随着终端设备形态日益多样化,分布式技术逐渐打破单一硬件边界,一个应用或服务,可以在不同的硬件设备之间随意调用、互助共享,让用户享受无缝的全场景体验。而作为应用开发者,广泛的设备类型也能为应用带来广大的潜在用户群体。但是如果一个应用需要在多个设备上提供同样的内容,则需要适配不同的屏幕尺寸和硬件,开发成本较高。OpenHarmony 系统面向多终端提供了“一次开发,多端部署”(后文中简称为“一多”)的能力,让开发者可以基于一种设计,高效构建多端可运行的应用。
![zh-cn_image_0000001267340890](figures/zh-cn_image_0000001267340890.jpg) ![zh-cn_image_0000001267340890](figures/zh-cn_image_0000001267340890.jpg)
...@@ -12,17 +12,22 @@ ...@@ -12,17 +12,22 @@
**定义**:一套代码工程,一次开发上架,多端按需部署。 **定义**:一套代码工程,一次开发上架,多端按需部署。
**目标**:支撑开发者快速高效开发多设备(“多种终端设备形态”的简称)应用,实现对不同设备兼容的同时提供跨设备的流转、迁移和协同的分布式体验。 **目标**:支撑开发者快速高效的开发支持多种终端设备形态的应用,实现对不同设备兼容的同时,提供跨设备的流转、迁移和协同的分布式体验。
![zh-cn_image_0000001315500981](figures/zh-cn_image_0000001315500981.jpg) ![multi_device](figures/multi_device.jpg)
为了实现“已整改”的目标,需要解决两个基础问题: 为了实现“一多”的目标,需要解决两个基础问题:
- 不同设备间的屏幕尺寸、色彩风格等存在差异,页面如何适配。 - 不同设备间的屏幕尺寸、色彩风格等存在差异,页面如何适配。
- 不同设备的系统能力有差异,如智能穿戴设备是否具备GPS、智慧屏是否具备摄像头等,功能如何兼容。 - 不同设备的系统能力有差异,如智能穿戴设备是否具备定位能力、智慧屏是否具备摄像头等,功能如何兼容。
从第3章开始将从UX设计、系统能力等角度,详尽的解答上述问题。 从第4章开始将从UX设计、系统能力等角度,详尽的解答上述问题。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> - 应用开发不仅包含应用页面开发,还包括应用后端功能开发以及服务器端开发等。
>
> - 本文旨在指导开发者如何在OpenHarmony系统中开发“一多”应用,服务器端开发不在本文探讨范围内。
## 基础知识 ## 基础知识
...@@ -32,9 +37,9 @@ ...@@ -32,9 +37,9 @@
### 应用程序包结构 ### 应用程序包结构
OpenHarmony 的应用以APP Pack (Application Package) 形式发布,它是由一个或多个HAP包以及描述每个HAP属性的pack.info文件组成。 OpenHarmony 的应用以APP Pack (Application Package) 形式发布,它是由一个或多个HAP包以及描述每个HAP包属性的pack.info文件组成。
HAP是OpenHarmony的安装包,一个HAP在工程目录中对应一个Module,由Module编译而来,可分为entry和feature两种类型的HAP。 HAP包是OpenHarmony的安装包,一个HAP在工程目录中对应一个Module,由Module编译而来,可分为entry和feature两种类型的HAP。
- **entry**:应用的主模块包。一个APP中,对于同一设备类型,可以有一个或多个entry类型的HAP,来支持该设备类型中不同规格(如API版本、屏幕规格等)的具体设备。 - **entry**:应用的主模块包。一个APP中,对于同一设备类型,可以有一个或多个entry类型的HAP,来支持该设备类型中不同规格(如API版本、屏幕规格等)的具体设备。
...@@ -43,16 +48,16 @@ HAP是OpenHarmony的安装包,一个HAP在工程目录中对应一个Module, ...@@ -43,16 +48,16 @@ HAP是OpenHarmony的安装包,一个HAP在工程目录中对应一个Module,
![zh-cn_image_0000001266965046](figures/zh-cn_image_0000001266965046.png) ![zh-cn_image_0000001266965046](figures/zh-cn_image_0000001266965046.png)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:** > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> - Module是开发者开发的相对独立的功能模块,由代码、资源、第三方库及应用配置文件组成,属于IDE开发视图的概念。Module分为entry、feature及har三种类型,相应的可以编译生成entry类型的HAP包、feature类型的HAP包,以及HAR包。 > - Module是开发者开发的相对独立的功能模块,由代码、资源、第三方库及应用配置文件组成,属于IDE开发视图的概念。Module分为entry、feature及har三种类型,相应的可以编译生成entry类型的HAP包、feature类型的HAP包,以及har包。
> >
> - 如果需要了解应用程序包结构更多详情,可以查看[包结构说明](../..//quick-start/package-structure.md)。 > - 如果需要了解应用程序包结构更多详情,可以查看[包结构说明](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/quick-start/package-structure.md)。
### 方舟开发框架 ### 方舟开发框架
OpenHarmony提供了方舟开发框架(简称:ArkUI),提供开发者进行应用UI开发时所必须的能力。 OpenHarmony提供了方舟开发框架(简称:ArkUI),提供开发者进行应用UI开发时所必须的能力。
方舟开发框架提供了两种开发范式,分别是基于JS扩展的类Web开发范式(简称为“类Web开发范式”)和基于TS扩展的声明式开发范式(简称为“声明式开发范式”)。 方舟开发框架提供了两种开发范式,分别是基于JS扩展的类Web开发范式(后文中简称为“类Web开发范式”)和基于TS扩展的声明式开发范式(后文中简称为“声明式开发范式”)。
- **类Web开发范式**:采用经典的HML、CSS、JavaScript三段式开发方式。使用HML标签文件进行布局搭建,使用CSS文件进行样式描述,使用JavaScript文件进行逻辑处理。UI组件与数据之间通过单向数据绑定的方式建立关联,当数据发生变化时,UI界面自动触发更新。此种开发方式,更接近Web前端开发者的使用习惯,快速将已有的Web应用改造成方舟开发框架应用。主要适用于界面较为简单的中小型应用开发。 - **类Web开发范式**:采用经典的HML、CSS、JavaScript三段式开发方式。使用HML标签文件进行布局搭建,使用CSS文件进行样式描述,使用JavaScript文件进行逻辑处理。UI组件与数据之间通过单向数据绑定的方式建立关联,当数据发生变化时,UI界面自动触发更新。此种开发方式,更接近Web前端开发者的使用习惯,快速将已有的Web应用改造成方舟开发框架应用。主要适用于界面较为简单的中小型应用开发。
...@@ -68,13 +73,11 @@ OpenHarmony提供了方舟开发框架(简称:ArkUI),提供开发者进 ...@@ -68,13 +73,11 @@ OpenHarmony提供了方舟开发框架(简称:ArkUI),提供开发者进
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:** > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> - 声明式开发范式占用内存更少,**更推荐开发者选用声明式开发范式来搭建应用UI界面**。 > - 声明式开发范式占用内存更少,**更推荐开发者选用声明式开发范式来搭建应用UI界面**。
> >
> - 可以查看[方舟开发框架概述](../../ui/arkui-overview.md),了解方舟开发框架更多详情。 > - 可以查看[方舟开发框架概述](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/ui/arkui-overview.md),了解方舟开发框架更多详情。
### 部署模型 ### 部署模型
我们既可以在不同类型的设备上部署相同的HAP包,也可以在不同类型的设备上部署不同的HAP包。
“一多”有两种部署模型: “一多”有两种部署模型:
- **部署模型A**:不同类型的设备上按照一定的工程结构组织方式,通过一次编译生成**相同**的HAP包(或HAP包组合)。 - **部署模型A**:不同类型的设备上按照一定的工程结构组织方式,通过一次编译生成**相同**的HAP包(或HAP包组合)。
...@@ -92,23 +95,39 @@ OpenHarmony提供了方舟开发框架(简称:ArkUI),提供开发者进 ...@@ -92,23 +95,39 @@ OpenHarmony提供了方舟开发框架(简称:ArkUI),提供开发者进
- 智能穿戴 - 智能穿戴
对于相同泛类的设备,优先选择部署模型A,对于不同泛类设备,优先选择部署B。 对于相同泛类的设备,优先选择部署模型A,对于不同泛类设备,优先选择部署模型B。
**应用功能** **应用功能**
- 方舟开发框架提供了丰富的多设备适配能力,相同泛类的设备通常总是可以使用部署模型。部署模型A需要的开发和维护工作量更小,而且可以保证不同类型设备上体验的一致性。 - 方舟开发框架提供了丰富的多设备适配能力,相同泛类的设备通常总是可以使用部署模型A。部署模型A需要的开发和维护工作量更小,而且可以保证不同类型设备上体验的一致性。
- 仅当同一泛类不同类型设备上规划的功能差异非常大时,才推荐使用部署模型B,如默认设备和平板分别交给两个团队设计、开发和维护等。 - 仅当同一泛类不同类型设备上规划的功能差异非常大时,才推荐使用部署模型B,如默认设备和平板分别交给两个团队设计、开发和维护等。
一般应用在不同设备上选择部署模型的思路如下: 一般应用在不同设备上选择部署模型的思路如下:
![zh-cn_image_0000001317848113](figures/zh-cn_image_0000001317848113.png) ![zh-cn_image_0000001400300617](figures/zh-cn_image_0000001400300617.png)
(备注:页面导航是用于应用内页面之间的跳转。例如默认设备设备上PageA跳转到PageB,Pad设备上也是PageA跳转到PageB,两种设备因为屏幕大小不同,默认设备上PageB是覆盖显示在PageA上的,Pad设备上PageB是在PageA的右边并且同时显示,但因为都是PageA跳转到PageB,那么我们认为它们的页面导航逻辑相同。) > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 页面导航逻辑是指应用内页面之间的跳转关系。假设默认设备上页面A跳转到页面B,平板设备上也是页面A跳转到页面B。因为两种设备屏幕大小不同,默认设备上页面B是覆盖显示在页面A上的,平板设备上页面B是在页面A的右边并且同时显示,但因为都是页面A跳转到页面B,那么我们认为它们的页面导航逻辑相同。
**工程结构** **工程结构**
部署模型不同,往往代码工程结构也不同。 “一多”推荐在应用开发过程中使用如下的“三层工程结构”。
- common:公共特性目录,如工具类、公共配置等。
- features:功能模块目录,存放应用中相对独立的各个功能的实现(包括该功能相关的UI代码及业务逻辑代码),如帐户管理等。
- product:产品层目录,通过引用common和feature目录中代码的方式做功能和特性的集成,同时也作为主入口。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> features层可横向调用和依赖common层能力;product层不可横向调用,可依赖features层和common层,且不能有反向依赖。
部署模型不同,相应的代码工程结构也有差异。部署模型A和部署模型B的主要差异点集中在product层:
- 部署模型A可以直接在product目录中做功能和特性集成。
- 部署模型B需要在product目录下再建一级子目录,在不同的子目录中对不同的产品做差异化的功能和特性集成。
部署模型A对应的代码工程结构抽象后一般如下所示: 部署模型A对应的代码工程结构抽象后一般如下所示:
...@@ -139,12 +158,5 @@ OpenHarmony提供了方舟开发框架(简称:ArkUI),提供开发者进 ...@@ -139,12 +158,5 @@ OpenHarmony提供了方舟开发框架(简称:ArkUI),提供开发者进
└── ... └── ...
``` ```
- common:公共特性目录,如工具类、公共配置等。
- features:功能模块目录,存放应用中相对独立的各个功能的实现(包括该功能相关的UI代码及业务逻辑代码),如账户管理等。
- product:产品层目录,通过引用common和feature目录中代码的方式做功能和特性的集成,同时也作为主入口。**这一层是两个部署模型主要差异点**,部署模型A可以直接在product目录中做功能和特性集成,部署模型B则需要在product目录下再建一级子目录,在不同的子目录中对不同的产品做差异化的功能和特性集成。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:** > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 无论是用部署模型A还是部署模型B,在开发阶段,都应考虑**不同类型设备间最大程度的复用代码**,以减少开发及后续维护的工作量。 > 无论是用部署模型A还是部署模型B,在开发阶段,都应考虑**不同类型设备间最大程度的复用代码**,以减少开发及后续维护的工作量。
# 布局基础
- **[栅格系统](design-grid.md)**
- **[自适应布局](design-adaptive-layout.md)**
- **[响应式布局](design-responsive-layout.md)**
\ No newline at end of file
# 布局能力简介 # 布局简介
开发多设备上同一页面时,建议开发者优先使用一套布局,尽可能的实现代码复用 布局可以分为自适应布局和响应式布局,二者的介绍如下表所示
一般情况下,可以通过页面的组件结构(组件个数、组件的父子/兄弟关系、组件类型)来判断是否可以使用一套布局: | 名称 | 简介 |
| -------- | -------- |
| 自适应布局 | 当外部容器大小发生变化时,元素可以**根据相对关系自动变化**以适应外部容器变化的布局能力。相对关系如占比、固定宽高比、显示优先级等。当前自适应布局能力有7种:[拉伸能力](adaptive-layout.md#拉伸能力)[均分能力](adaptive-layout.md#均分能力)[占比能力](adaptive-layout.md#占比能力)[缩放能力](adaptive-layout.md#缩放能力)[延伸能力](adaptive-layout.md#延伸能力)[隐藏能力](adaptive-layout.md#隐藏能力)[折行能力](adaptive-layout.md#折行能力)。自适应布局能力可以实现界面显示随外部容器大小连续变化。 |
| 响应式布局 | 当外部容器大小发生变化时,元素可以**根据断点、栅格或特定的特征(如屏幕方向、窗口宽高等)自动变化**以适应外部容器变化的布局能力。当前响应式布局能力有3种:[断点](responsive-layout.md#断点)[媒体查询](responsive-layout.md#媒体查询)[栅格布局](responsive-layout.md#栅格布局)。响应式布局可以实现界面随外部容器大小有级不连续变化,通常不同特征下的界面显示会有较大的差异。 |
- 对于页面组件结构相同的场景,在开发过程中可以灵活使用[自适应布局](adaptive-layout-intro.md)[栅格布局](grid-breakpoint.md)能力来达到预期效果 自适应布局和响应式布局常常需要借助容器类组件实现,或与容器类组件搭配使用
- 对于页面组件结构不同的场景,可以借助[媒体查询](media-query.md)能力动态加载多套布局。
- 自适应布局常常需要借助[Row组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-row.md)[Column组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-column.md)[Flex组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-flex.md)实现。
![image-20220922185907892](figures/image-20220922185907892.png)
- 响应式布局常常与[GridRow组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-gridrow.md)[Grid组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-grid.md)[List组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-list.md)[Swiper组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-swiper.md)[Tabs组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-tabs.md)搭配使用。
![image-20220922190217247](figures/image-20220922190217247.png)
接下来将依次介绍自适应布局和响应式布局,同时结合实际,通过典型布局场景以及典型页面场景详细介绍两种布局能力的用法。
下面将详细介绍这些布局能力。
## 相关实例 ## 相关实例
针对一次开发,多端部署,有以下相关实例可供参考: 针对一次开发,多端部署,有以下相关实例可供参考:
...@@ -22,3 +34,4 @@ ...@@ -22,3 +34,4 @@
- [`AdaptiveCapabilities`:多设备自适应能力(eTS)(API9)](https://gitee.com/openharmony/applications_app_samples/tree/master/MultiDeviceAppDev/AdaptiveCapabilities) - [`AdaptiveCapabilities`:多设备自适应能力(eTS)(API9)](https://gitee.com/openharmony/applications_app_samples/tree/master/MultiDeviceAppDev/AdaptiveCapabilities)
- [`JsAdaptiveCapabilities`:多设备自适应能力(JS)(API8)](https://gitee.com/openharmony/applications_app_samples/tree/master/MultiDeviceAppDev/JsAdaptiveCapabilities) - [`JsAdaptiveCapabilities`:多设备自适应能力(JS)(API8)](https://gitee.com/openharmony/applications_app_samples/tree/master/MultiDeviceAppDev/JsAdaptiveCapabilities)
- [一次开发多端部署(eTS)(API8)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/MultiDeploymentEts) - [一次开发多端部署(eTS)(API8)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/MultiDeploymentEts)
# 布局能力
- **[布局简介](layout-intro.md)**
- **[自适应布局](adaptive-layout.md)**
- **[响应式布局](responsive-layout.md)**
- **[典型布局场景](typical-layout-scenario.md)**
- **[典型页面场景](typical-page-layout.md)**
\ No newline at end of file
# 人机交互
- **[交互基础](interaction-basics.md)**
- **[常见输入方式](common-input-modes.md)**
- **[交互事件归一](design-interaction-event-normalization.md)**
\ No newline at end of file
# 媒体查询
媒体查询是一种常用的功能,在移动设备上应用非常广泛。开发者经常需要根据设备的大致类型或特定的特征和设备参数(例如屏幕分辨率)来修改应用的样式。为此媒体查询提供了如下功能:
- 针对设备和应用的属性信息,可以设计出相匹配的布局样式。
- 当屏幕发生动态改变时(比如横竖屏切换、屏幕宽高比),页面布局同步更新。
当前支持对如下媒体特征做媒体查询。
| 类型 | 说明 |
| -------- | -------- |
| height | 应用页面显示区域的高度。 |
| min-height | 应用页面显示区域的最小高度。 |
| max-height | 应用页面显示区域的最大高度。 |
| width | 应用页面显示区域的宽度。 |
| min-width | 应用页面显示区域的最小宽度。 |
| max-width | 应用页面显示区域的最大宽度。 |
| resolution | 设备的分辨率,支持dpi,dppx和dpcm单位。 |
| min-resolution | 设备的最小分辨率。 |
| max-resolution | 设备的最大分辨率。 |
| orientation | 屏幕的方向,支持portrait(竖屏)和landscape(横屏)。 |
| device-type | 设备类型,支持tablet、tv、wearable等。 |
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> - 声明式开发范式,请查看[媒体查询](../../ui/ui-ts-layout-mediaquery.md)了解详细用法。
>
> - 类Web开发范式,支持在js文件和css文件中使用媒体查询,请查看[js媒体查询](../../reference/apis/js-apis-mediaquery.md)和[css媒体查询](../../reference/arkui-js/js-components-common-mediaquery.md)了解详细用法。
>
> - 媒体查询能力最为强大,它可以改变页面组件结构,而自适应布局和栅格布局均无此能力。
>
> - 相对于其它”一多”能力,媒体查询可能增加较多代码量。在可以满足需求的前提下,建议优先使用其它”一多”能力。
## 示例:
如下图,媒体查询不仅可以改变页面中的元素属性(文字内容、文字颜色),还可以改变页面结构(是否显示图片)。
![zh-cn_image_0000001226091264](figures/zh-cn_image_0000001226091264.jpg)
```ts
import mediaquery from '@ohos.mediaquery'
@Entry
@Component
struct MediaQuerySample {
@State color: string = '#DB7093';
@State text: string = 'Portrait';
@State flag: boolean = false;
private listener: mediaquery.MediaQueryListener = mediaquery.matchMediaSync('(orientation: landscape)');
onOrientationChange = (mediaQueryResult) => {
if (mediaQueryResult.matches) {
this.color = '#FFD700';
this.text = 'Landscape';
this.flag = true;
} else {
this.color = '#DB7093';
this.text = 'Portrait';
this.flag = false;
}
}
private aboutToAppear(): void {
this.listener.on('change', this.onOrientationChange);
}
private aboutToDisappear(): void {
this.listener.off('change', this.onOrientationChange);
}
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
if (this.flag) { // 通过媒体查询,改变布局
Image($r("app.media.my_image"))
.width(100)
.height(100)
}
Text(this.text).fontSize(24).fontColor(this.color)
}
.width('100%').height('100%')
}
}
```
# 音乐专辑页
本小节将以音乐专辑页为例,介绍如何使用自适应布局能力和响应式布局能力适配不同尺寸窗口。本示例已经在[OpenHarmony应用示例](https://gitee.com/openharmony/applications_app_samples/tree/master/MultiDeviceAppDev/MusicAlbum)中开源,读者可以根据需要自行下载源码并运行及查看效果。
## 页面设计
音乐专辑页的页面设计如下。
| sm | md | lg |
| -------- | -------- | -------- |
| ![zh-cn_image_0000001381013985](figures/zh-cn_image_0000001381013985.png) | ![zh-cn_image_0000001381133197](figures/zh-cn_image_0000001381133197.png) | ![zh-cn_image_0000001329813432](figures/zh-cn_image_0000001329813432.png) |
同样观察音乐专辑的页面设计,不同断点下的页面设计有较多相似的地方。
据此,我们可以将页面分拆为多个组成部分。
1. 标题栏
2. 歌单封面
3. 歌单列表
4. 播放控制栏
| sm | md | lg |
| -------- | -------- | -------- |
| ![zh-cn_image_0000001380933349](figures/zh-cn_image_0000001380933349.jpg) | ![zh-cn_image_0000001330133330](figures/zh-cn_image_0000001330133330.jpg) | ![zh-cn_image_0000001381013989](figures/zh-cn_image_0000001381013989.jpg) |
## 标题栏
不同断点下,标题栏始终只显示“返回按钮”、“歌单”以及“更多按钮”,但“歌单”与“更多按钮”之间的间距不同。由于不同断点下标题栏的背景色也有较大差异,因此无法使用拉伸能力实现,此场景更适合使用栅格实现。我们可以将标题栏划分为“返回按钮及歌单”和“更多按钮”两部分,这两部分在不同断点下占据的列数如下图所示。另外,还可以借助OnBreakpointChange事件,调整不同断点下这两部分的背景色。
| | sm | md | lg |
| -------- | -------- | -------- | -------- |
| 效果图 | ![zh-cn_image_0000001329817776](figures/zh-cn_image_0000001329817776.png) | ![zh-cn_image_0000001381018337](figures/zh-cn_image_0000001381018337.png) | ![zh-cn_image_0000001381137517](figures/zh-cn_image_0000001381137517.jpg) |
| 栅格布局图 | ![zh-cn_image_0000001330137692](figures/zh-cn_image_0000001330137692.png) | ![zh-cn_image_0000001329977740](figures/zh-cn_image_0000001329977740.png) | ![zh-cn_image_0000001329658136](figures/zh-cn_image_0000001329658136.png) |
```
@Component
export struct PlayListHeader {
@State moreBackgroundColor: Resource = $r('app.color.play_list_cover_background_color');
build() {
GridRow() {
GridCol({span: {sm:6, md: 6, lg:4}}) {
Row() {
Image($r('app.media.ic_back')).height('24vp').width('24vp')
}
.width('100%')
.height('50vp')
.justifyContent(FlexAlign.Start)
.alignItems(VerticalAlign.Center)
.padding({left:$r('app.float.default_margin')})
.backgroundColor($r('app.color.play_list_cover_background_color'))
}
GridCol({span: {sm:6, md: 6, lg:8}}) {
Row() {
Image($r('app.media.ic_add')).height('24vp').width('24vp')
}
.width('100%')
.height('50vp')
.justifyContent(FlexAlign.End)
.alignItems(VerticalAlign.Center)
.padding({right:$r('app.float.default_margin')})
.backgroundColor(this.moreBackgroundColor)
}
}.onBreakpointChange((currentBreakpoint) => {
// 调整不同断点下返回按钮及歌单的背景色
if (currentBreakpoint === 'sm') {
this.moreBackgroundColor = $r('app.color.play_list_cover_background_color');
} else {
this.moreBackgroundColor = $r('app.color.play_list_songs_background_color');
}
}).height('100%').width('100%')
}
}
```
## 歌单封面
歌单封面由封面图片、歌单介绍及常用操作三部分组成,这三部分的布局在md和lg断点下完全相同,但在sm断点下有较大差异。此场景同样可以用栅格实现。
| | sm | md/lg |
| -------- | -------- | -------- |
| 效果图 | ![zh-cn_image_0000001329660244](figures/zh-cn_image_0000001329660244.jpg) | ![zh-cn_image_0000001381379829](figures/zh-cn_image_0000001381379829.png) |
| 栅格布局图 | ![zh-cn_image_0000001381220165](figures/zh-cn_image_0000001381220165.png) | ![zh-cn_image_0000001381220169](figures/zh-cn_image_0000001381220169.png) |
```
@Component
export default struct PlayListCover {
...
build() {
Column() {
// 借助栅格组件实现总体布局
GridRow() {
// 歌单图片
GridCol({ span: { sm: 4, md: 10 }, offset: { sm: 0, md: 1, lg: 1 } }) {
this.CoverImage()
}
// 歌单介绍
GridCol({ span: { sm: 8, md: 10 }, offset: { sm: 0, md: 2, lg: 2 } }) {
this.CoverIntroduction()
}
// 歌单操作
GridCol({ span: { sm: 12, md: 10 }, offset: { sm: 0, md: 2, lg: 2 } }) {
this.CoverOptions()
}.margin({
top: this.currentBreakpoint === 'sm' ? 15 : 0,
bottom: this.currentBreakpoint === 'sm' ? 15 : 0
})
}
.margin({ left: this.coverMargin, right: this.coverMargin })
}
.height(this.currentBreakpoint === 'sm' ? this.coverHeight : '100%')
.padding({ top: this.currentBreakpoint === 'sm' ? 50 : 70 })
}
}
```
## 歌单列表
不同断点下,歌单列表的样式基本一致,但sm和md断点下是歌单列表是单列显示,lg断点下是双列显示。可以通过[List组件](https://gitee.com/fanzhaonan/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-list.md)的lanes属性实现这一效果。
```
@Component
export default struct PlayList {
...
build() {
Column() {
this.PlayAll()
Scroll() {
List() {
LazyForEach(new MyDataSource(songList), item => {
ListItem() {
this.SongItem(item.title, item.label, item.singer)
}
}, item => item.id)
}
.width('100%')
.height('100%')
// 配置不同断点下歌单列表的列数
.lanes(this.currentBreakpoint === 'lg' ? 2 : 1)
}
.backgroundColor('#fff')
.margin({ top: 50, bottom: this.currentBreakpoint === 'sm' ? this.coverHeight : 0 })
}
.padding({top: 50,bottom: 48})
}
}
```
## 播放控制栏
在不同断点下,播放控制栏显示的内容完全一致,唯一的区别是歌曲信息与播放控制按钮之间的间距有差异,这是典型的拉伸能力的使用场景。
```
@Component
export struct MusicBar {
build() {
Row() {
Image($r('app.media.pic_album')).height(32).width(32).margin({right: 12})
SongTitle()
// 通过Blank组件实现拉伸能力
Blank()
Image($r('app.media.icon_play')).height(26).width(26).margin({right: 16})
Image($r('app.media.ic_next')).height(24).width(24).margin({right: 16})
Image($r('app.media.ic_Music_list')).height(24).width(24)
}
.width('100%')
.height(48)
.backgroundColor('#D8D8D8')
.alignItems(VerticalAlign.Center)
.padding({left: 16, right: 16})
}
}
```
## 运行效果
将页面中的四部分组合在一起,即可显示完整的页面。
其中歌单封面和歌单列表这两部分的相对位置,在sm断点下是上下排布,在md和lg断点下是左右排布,也可以用栅格来实现目标效果。
| | sm | md | lg |
| -------- | -------- | -------- | -------- |
| 效果图 | ![zh-cn_image_0000001381026609](figures/zh-cn_image_0000001381026609.jpg) | ![zh-cn_image_0000001381145789](figures/zh-cn_image_0000001381145789.jpg) | ![zh-cn_image_0000001329666380](figures/zh-cn_image_0000001329666380.jpg) |
| 栅格布局图 | ![zh-cn_image_0000001330145976](figures/zh-cn_image_0000001330145976.png) | ![zh-cn_image_0000001381385985](figures/zh-cn_image_0000001381385985.png) | ![zh-cn_image_0000001381226321](figures/zh-cn_image_0000001381226321.png) |
```
@Component
export default struct MusicContent {
...
build() {
GridRow() {
// 歌单封面
GridCol({ span: { xs: 12, sm: 12, md: 6, lg: 4 } }) {
PlayListCover()
}
// 歌单列表
GridCol({ span: { xs: 12, sm: 12, md: 6, lg: 8 } }) {
PlayList()
}
}
.height('100%')
}
}
```
最后将页面各部分组合在一起即可。
```
@Entry
@Component
struct Index {
build() {
Column() {
// 标题栏
PlayListHeader()
// 歌单
MusicContent()
// 播放控制栏
MusicBar()
}.width('100%').height('100%')
}
}
```
音乐专辑页面的运行效果如下所示。
| sm | md | lg |
| -------- | -------- | -------- |
| ![MusicAlbum_sm_running](figures/MusicAlbum_sm_running.png) | ![MusicAlbum_md_running](figures/MusicAlbum_md_running.png) | ![MusicAlbum_lg_running](figures/MusicAlbum_lg_running.png) |
...@@ -8,15 +8,15 @@ ...@@ -8,15 +8,15 @@
导航需要遵循以下原则: 导航需要遵循以下原则:
- **一致**:导航操作的结果应该与用户的期望保持一致。相同或类似的场景使用用户熟悉的界面布局和控件,在多设备上确保一致的应用架构和导航行为,让用户无论在什么页面,都知道如何导航。例如二级界面使用左上角的返回按钮来返回界面的上一个层级。 - **一致:**导航操作的结果应该与用户的期望保持一致。相同或类似的场景使用用户熟悉的界面布局和控件,在多设备上确保一致的应用架构和导航行为,让用户无论在什么页面,都知道如何导航。例如二级界面使用左上角的返回按钮来返回界面的上一个层级。
- **清晰**:导航应该提供清晰的路径。用户使用的时候,逻辑关系简单且容易理解,能够知道当前处在界面的什么位置,操作后将会跳转到什么位置,不会迷失方向。例如使用底部页签,让用户在平级页面之间进行切换。 - **清晰:**导航应该提供清晰的路径。用户使用的时候,逻辑关系简单且容易理解,能够知道当前处在界面的什么位置,操作后将会跳转到什么位置,不会迷失方向。例如使用底部页签,让用户在平级页面之间进行切换。
导航要避免以下设计: 导航要避免以下设计:
- **层级过深**:导航层级建议在三层以内。对于太深的层次,会带来操作效率的问题。如果确实需要深层级设计,建议使用面包屑设计或增加一键回到首页的功能。 - **层级过深:**导航层级建议在三层以内。对于太深的层次,会带来操作效率的问题。如果确实需要深层级设计,建议使用面包屑设计或增加一键回到首页的功能。
- **导航复杂**:在侧边导航中,使用底部页签,会让操作变得复杂,建议仅使用侧边导航。 - **导航复杂:**在侧边导航中,使用底部页签,会让操作变得复杂,建议仅使用侧边导航。
## 导航的分类 ## 导航的分类
...@@ -33,11 +33,11 @@ ...@@ -33,11 +33,11 @@
例如:以Tab方式组成的页面。图中照片、相册、发现为一级界面,从视频相册进入二级内容界面。 例如:以Tab方式组成的页面。图中照片、相册、发现为一级界面,从视频相册进入二级内容界面。
![zh-cn_image_0000001226444718](figures/zh-cn_image_0000001226444718.png) ![一多-2-2](figures/一多-2-2.png)
多设备设计:可转化导航类控件到符合设备体验的位置上。默认设备上使用Tab导航,PAD和PC使用侧边Tab导航,智慧屏使用顶部Tab导航。 多设备设计:可转化导航类控件到符合设备体验的位置上。默认设备上使用Tab导航,PAD和PC使用侧边Tab导航,智慧屏使用顶部Tab导航。
![zh-cn_image_0000001270924709](figures/zh-cn_image_0000001270924709.png) ![一多-2-3](figures/一多-2-3.png)
**层级导航** **层级导航**
...@@ -51,11 +51,11 @@ ...@@ -51,11 +51,11 @@
例如:通过从内容进入后经返回键返回之前的页面。 例如:通过从内容进入后经返回键返回之前的页面。
![zh-cn_image_0000001270804909](figures/zh-cn_image_0000001270804909.png) ![一多-2-5](figures/一多-2-5.png)
多设备设计:可以考虑将上下层级的界面在同一界面展示。默认设备和智慧屏上使用上下层级关系。平板和PC使用上分栏的方式展示内容。 多设备设计:可以考虑将上下层级的界面在同一界面展示。默认设备和智慧屏上使用上下层级关系。平板和PC使用上分栏的方式展示内容。
![zh-cn_image_0000001226124842](figures/zh-cn_image_0000001226124842.png) ![一多-2-6](figures/一多-2-6.png)
**混合导航** **混合导航**
...@@ -67,4 +67,4 @@ ...@@ -67,4 +67,4 @@
多设备设计:可以根据平级导航、层级导航自身的设计规则综合运用,一般平级导航优先级比层级高。 多设备设计:可以根据平级导航、层级导航自身的设计规则综合运用,一般平级导航优先级比层级高。
![zh-cn_image_0000001263489242](figures/zh-cn_image_0000001263489242.png) ![混合导航](figures/混合导航.png)
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
针对内容型应用,应用首页内容的获取需要花费一定的时间,此时可以使用启动页缓解页面加载内容的等待感。启动页可以展示应用的品牌形象或者广告,避免让用户等待过长时间。没有网络加载内容的应用,不需要使用启动页。 针对内容型应用,应用首页内容的获取需要花费一定的时间,此时可以使用启动页缓解页面加载内容的等待感。启动页可以展示应用的品牌形象或者广告,避免让用户等待过长时间。没有网络加载内容的应用,不需要使用启动页。
![zh-cn_image_0000001317205637](figures/zh-cn_image_0000001317205637.png) ![一多-应用架构-启动页](figures/一多-应用架构-启动页.png)
用户总是希望第一时间看到应用内容,因此在页面加载完成后,需要及时呈现内容。 用户总是希望第一时间看到应用内容,因此在页面加载完成后,需要及时呈现内容。
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
列表内容页面通常用于文字和数据的展示,利于提升使用效率。 列表内容页面通常用于文字和数据的展示,利于提升使用效率。
![zh-cn_image_0000001268605598](figures/zh-cn_image_0000001268605598.png) ![一多-应用架构-列表视觉图](figures/一多-应用架构-列表视觉图.png)
列表应该按照一定的逻辑排序,便于用户浏览和操作。例如:按字母顺序排序、按时间排序。 列表应该按照一定的逻辑排序,便于用户浏览和操作。例如:按字母顺序排序、按时间排序。
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
网格内容页面通常用于图片和视频的展示,利于沉浸浏览内容。 网格内容页面通常用于图片和视频的展示,利于沉浸浏览内容。
![zh-cn_image_0000001268445650](figures/zh-cn_image_0000001268445650.png) ![一多-应用架构-网格视图](figures/一多-应用架构-网格视图.png)
网格视图显示同等重要的项目,具有统一的布局。 网格视图显示同等重要的项目,具有统一的布局。
...@@ -45,51 +45,53 @@ ...@@ -45,51 +45,53 @@
多选页面是对页面内的数据多项选择,然后进行批量处理。常见的是针对列表的多项选择或宫格的多项选择。 多选页面是对页面内的数据多项选择,然后进行批量处理。常见的是针对列表的多项选择或宫格的多项选择。
![zh-cn_image_0000001268125810](figures/zh-cn_image_0000001268125810.png) ![应用架构-常用页面结构-通用页面结构-多选页面低保真](figures/应用架构-常用页面结构-通用页面结构-多选页面低保真.png)
**详情页面** **详情页面**
详情页用于展示应用的详细描述和操作。 详情页用于展示应用的详细描述和操作。
![zh-cn_image_0000001317325613](figures/zh-cn_image_0000001317325613.png) ![应用架构-常用页面结构-通用页面结构-详情页面低保真](figures/应用架构-常用页面结构-通用页面结构-详情页面低保真.png)
**空页面** **空页面**
在页面内没有数据的时候,使用空页面。 在页面内没有数据的时候,使用空页面。
![zh-cn_image_0000001317485577](figures/zh-cn_image_0000001317485577.png) ![一多-应用架构-空页面](figures/一多-应用架构-空页面.png)
**设置页面** **设置页面**
设置页面通常是一个模块所有设置项的聚合。 设置页面通常是一个模块所有设置项的聚合。
![zh-cn_image_0000001268285682](figures/zh-cn_image_0000001268285682.png) ![一多-应用架构-设置](figures/一多-应用架构-设置.png)
**我的页面** **我的页面**
针对内容型应用,可以提供我的页面,用于承载用户的信息和资产内容。 针对内容型应用,可以提供我的页面,用于承载用户的信息和资产内容。
![zh-cn_image_0000001317085761](figures/zh-cn_image_0000001317085761.png) ![一多-应用架构-我的页面](figures/一多-应用架构-我的页面.png)
**关于页面** **关于页面**
关于页面用于呈现应用的基本情况,包括联系方式,法律条款等内容。 关于页面用于呈现应用的基本情况,包括联系方式,法律条款等内容。
![zh-cn_image_0000001317205645](figures/zh-cn_image_0000001317205645.png) ![一多-应用架构-关于](figures/一多-应用架构-关于.png)
## 垂类页面结构 ## 垂类页面结构
垂类是指垂直领域,为特定的人群提供特定的服务,属于应用的细分类别。例如:华为音乐、网易云音乐等属于音乐类,华为视频、优酷等属于视频类,快手、抖音等属于直播类。垂类页面结构是在特定领域长期使用的过程中,形成的广受用户接受和理解的页面结构。 垂类是指垂直领域,为特定的人群提供特定的服务,属于应用的细分类别。例如:华为音乐、网易云音乐等属于音乐类,华为视频、优酷等属于视频类,快手、抖音等属于直播类。垂类页面结构是在特定领域长期使用的过程中,形成的广泛被用户接受和理解的页面结构。
例如: 例如:
音乐类应用都有音乐歌单、音乐专辑、音乐播放界面。 音乐类应用都有音乐歌单,音乐专辑,音乐播放界面。
视频类应用都有视频详情和视频播放界面。 视频类应用都有视频详情和视频播放界面。
直播类应用,都有瀑布流推荐和直播界面。 直播类应用,都有瀑布流推荐和直播界面。
...
常见的垂类页面结构有: 常见的垂类页面结构有:
- 音乐播放页面 - 音乐播放页面
...@@ -104,25 +106,25 @@ ...@@ -104,25 +106,25 @@
音乐类应用中的播放器界面,该界面通常有音乐操控(播放,暂停,上一首,下一首)、歌词显示等功能。 音乐类应用中的播放器界面,该界面通常有音乐操控(播放,暂停,上一首,下一首)、歌词显示等功能。
![zh-cn_image_0000001268605602](figures/zh-cn_image_0000001268605602.png) ![应用架构-常用页面结构-垂类页面结构-音乐播放低保真](figures/应用架构-常用页面结构-垂类页面结构-音乐播放低保真.png)
**专辑详情页** **专辑详情页**
音乐类应用中的音乐专辑详情界面,该界面通常有专辑介绍、专辑包含的歌曲列表等功能。 音乐类应用中的音乐专辑详情界面,该界面通常有专辑介绍、专辑包含的歌曲列表等功能。
![zh-cn_image_0000001268445654](figures/zh-cn_image_0000001268445654.png) ![应用架构-常用页面结构-垂类页面结构-歌单详情低保真](figures/应用架构-常用页面结构-垂类页面结构-歌单详情低保真.png)
**视频详情页面** **视频详情页面**
视频类应用的视频详情界面,该界面通常有视频播放器、视频剧集显示、视频简介等功能。 视频类应用的视频详情界面,该界面通常有视频播放器、视频剧集显示、视频简介等功能。
![zh-cn_image_0000001268125814](figures/zh-cn_image_0000001268125814.png) ![一多-应用架构-视频详情](figures/一多-应用架构-视频详情.png)
**视频播放界面** **视频播放界面**
视频类应用的视频播放界面,该界面通常有视频画面预览、播放控制等功能。 视频类应用的视频播放界面,该界面通常有视频画面预览、播放控制等功能。
![zh-cn_image_0000001317325617](figures/zh-cn_image_0000001317325617.png) ![一多-应用架构-视频播放](figures/一多-应用架构-视频播放.png)
## 特殊页面结构 ## 特殊页面结构
...@@ -131,4 +133,4 @@ ...@@ -131,4 +133,4 @@
例如,同时具有底部Tab和子页签的页面,在大屏上应考虑将底部入口置于顶部工具栏或与子页签融合。 例如,同时具有底部Tab和子页签的页面,在大屏上应考虑将底部入口置于顶部工具栏或与子页签融合。
![zh-cn_image_0000001317485581](figures/zh-cn_image_0000001317485581.png) ![特殊页面结构](figures/特殊页面结构.png)
# 简介
本章介绍如何使用方舟开发框架“一多”能力,开发出在多设备上正常显示的页面。方舟开发框架推荐开发者使用声明式开发范式开发应用,故本章的内容和示例都主要基于声明式开发范式。本章主要包含如下内容:
- [布局能力](adaptive-layout.md)
布局决定了页面中的元素按照何种方式排布及显示,是页面设计及开发过程中首先需要考虑的问题。一般情况下,可以通过页面(或自定义组件)内的组件结构(组件个数、组件的父子/兄弟关系、组件类型、组件的相对位置)来判断使用何种布局能力。
- 对于随尺寸变化组件结构相同的场景,可以在开发过程中灵活使用[自适应布局能力](adaptive-layout.md)来达到目标效果。
- 对于随尺寸变化组件结构不同的场景,更适合使用[响应式布局能力](responsive-layout.md)来实现不同尺寸下的不同显示的效果。
- [交互归一](interaction-event-normalization.md)
- [多态组件](polymorphic-controls.md)
- [资源使用](resource-usage.md)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> - 开发多设备上同一页面时,建议开发者多使用[自定义组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/ui/ts-component-based-component.md),既可以增加代码的可读性和可维护性,同时也可以尽可能的实现代码复用。
>
> - 本章中涉及的示例代码均可以在[OpenHarmony应用示例](https://gitee.com/openharmony/applications_app_samples/tree/master/MultiDeviceAppDev)中获取,感兴趣的开发者可以自行下载、运行及查看效果。
# 页面开发的一多能力介绍
- **[简介](page-development-intro.md)**
- **[布局能力](layout.md)**
- **[交互归一](interaction-event-normalization.md)**
- **[多态组件](polymorphic-controls.md)**
- **[资源使用](resource-usage.md)**
\ No newline at end of file
# 多态组件
方舟开发框架不仅提供了多种基础组件,如文本显示、图片显示、按键交互等。并且针对不同类型设备进行了组件设计,提供了组件在不同平台上的样式适配能力,此种组件称为“多态组件”。开发者在使用多态组件时,无需考虑设备差异,只需关注功能实现即可。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> OpenHarmony上的多态组件能力正在逐步补齐中。
# 多态组件
方舟开发框架不仅提供了多种基础组件(如文本显示、图片显示、按键交互等),并且针对不同类型设备分别进行了适配。同一组件在不同的设备上会呈现出不同的形态(即视觉、交互、动效等可能有差异),称为“多态组件”。开发者在使用多态组件时,无需考虑设备差异,只需关注功能实现即可。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> OpenHarmony上的多态组件能力正在逐步补齐中。
...@@ -4,114 +4,95 @@ ...@@ -4,114 +4,95 @@
在页面开发过程中,经常需要用到颜色、字体、间距、图片等资源,在不同的设备或配置中,这些资源的值可能不同。有两种方式处理: 在页面开发过程中,经常需要用到颜色、字体、间距、图片等资源,在不同的设备或配置中,这些资源的值可能不同。有两种方式处理:
- [自定义资源](#自定义资源):借助资源文件能力,开发者在应用中自定义资源,自行管理这些资源在不同的设备或配置中的表现。 - [应用资源](#应用资源):借助资源文件能力,开发者在应用中自定义资源,自行管理这些资源在不同的设备或配置中的表现。
- [系统资源](#系统资源):开发者直接使用系统预置的资源定义(即分层参数)。 - [系统资源](#系统资源):开发者直接使用系统预置的资源定义(即分层参数)。
## 自定义资源 ## 应用资源
### 资源文件介绍 ### 资源文件介绍
开发者可以在项目工程的resources目录中创建指定类型的资源文件,通过”key-value”的形式定义资源,同时可以借助资源限定词能力,定义同一资源在不同设备(默认设备、智慧屏等)或同一设备不同配置(横竖屏、深浅色等)下的表现。工程内的resources目录按照两级目录的形式来组织: 应用开发中使用的各类自定义资源文件,需要统一存放于应用的resources目录下,便于使用和维护。resources目录包括两大类目录,一类为base目录与限定词目录,另一类为rawfile目录,其基础目录结构如下所示。
- 一级目录为base目录、限定词目录以及rawfile目录
- base目录是默认存在的目录。当应用的resources资源目录中没有与设备状态匹配的限定词目录时,会自动引用该目录中的资源文件。
- 限定词目录需要开发者自行创建,其可以由一个或多个表征应用场景或设备特征的限定词组合而成,包括移动国家码和移动网络码、语言、文字、国家或地区、横竖屏、设备类型、颜色模式和屏幕密度等维度,限定词之间通过下划线(_)或者中划线(-)连接。
- 在引用rawfile中的资源时,不会根据系统的状态去匹配,rawfile目录中可以直接存放资源文件。
- 二级目录为资源目录 ```
- 用于存放字符串、颜色、浮点数等基础元素,以及媒体等资源文件。 resources
- 当前支持的文件和资源类型如下: |---base // 默认存在的目录
| 文件名 | 资源类型 | | |---element
| -------- | -------- | | | |---string.json
| color.json | 颜色资源。 | | |---media
| float.json | 间距、圆角、字体等资源。 | | | |---icon.png
| string.json | 字符串资源。 | |---en_GB-vertical-car-mdpi // 限定词目录示例,需要开发者自行创建
| plural.json | 字符串资源。 | | |---element
| media目录 | 图片资源 | | | |---string.json
| |---media
| | |---icon.png
### 资源文件使用 |---rawfile // rawfile目录
```
在工程中,通过 "$r('app.type.name')" 的形式引用应用资源。app代表是应用内resources目录中定义的资源;type 代表资源类型(或资源的存放位置),可以取 color、float、string、plural和media,name代表资源命名,由开发者定义资源时确定。 base目录默认存在,而限定词目录需要开发者自行创建,其名称可以由一个或多个表征应用场景或设备特征的限定词组合而成。应用使用某资源时,系统会根据当前设备状态优先从相匹配的限定词目录中寻找该资源。只有当resources目录中没有与设备状态匹配的限定词目录,或者在限定词目录中找不到该资源时,才会去base目录中查找。rawfile是原始文件目录,它不会根据设备状态去匹配不同的资源,故不在本文的讨论范文内。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:** > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> - 可以查看[声明式开发范式资源访问](../../ui/ts-resource-access.md),了解资源访问的更多细节。 > - 请访问[声明式开发范式资源文件分类](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/ui/ui-ts-basic-resource-file-categories.md),了解限定词目录的命名规则、创建流程、匹配规则等,本文不展开介绍。
> >
> - 类Web开发范式的资源文件路径及资源限定词的使用与声明式范式不同,详情请参考[类Web开发范式资源限定与访问](../../ui/js-framework-resource-restriction.md)及[类Web开发范式文件组织](../../ui/js-framework-file.md)。 > - 没有设备状态匹配的限定词目录,或者在限定词目录中找不到目标资源时,会继续在base目录中查找。**强烈建议对于所有应用自定义资源都在base目录中定义默认值**,防止出现找不到资源值的异常场景。
>
> - 类Web开发范式的资源文件路径及资源限定词的使用与声明式范式不同,详情请参考[类Web开发范式资源限定与访问](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/ui/js-framework-resource-restriction.md)及[类Web开发范式文件组织](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/ui/js-framework-file.md)。
### 示例 base目录与限定词目录下面可以创建资源组目录(包括element、media等),用于存放特定类型的资源文件。
按照如下说明,配置工程resources目录中的json资源文件(如文件不存在,需手工创建)。另外,准备两张不同的图片,分别放置于resources/base/media/my_image.jpg及resources/tablet/media/my_image.jpg路径下。 | 资源组目录 | 目录说明 | 资源文件 |
| -------- | -------- | -------- |
| element | 表示元素资源,以下每一类数据都采用相应的JSON文件来表征。<br/>-&nbsp;boolean,布尔型<br/>-&nbsp;color,颜色<br/>-&nbsp;float,浮点型<br/>-&nbsp;intarray,整型数组<br/>-&nbsp;integer,整型<br/>-&nbsp;pattern,样式<br/>-&nbsp;plural,复数形式<br/>-&nbsp;strarray,字符串数组<br/>-&nbsp;string,字符串 | element目录中的文件名称建议与下面的文件名保持一致。每个文件中只能包含同一类型的数据。<br/>-&nbsp;boolean.json<br/>-&nbsp;color.json<br/>-&nbsp;float.json<br/>-&nbsp;intarray.json<br/>-&nbsp;integer.json<br/>-&nbsp;pattern.json<br/>-&nbsp;plural.json<br/>-&nbsp;strarray.json<br/>-&nbsp;string.json |
| media | 表示媒体资源,包括图片、音频、视频等非文本格式的文件。 | 文件名可自定义,例如:icon.png。 |
在element目录的各个资源文件中,以“name-value”的形式定义资源,如下所示。而在media目录中,直接以文件名作为name,故开发者将文件放入media目录即可,无需再额外定义name。
```ts
//resources/base/element/string.json
{
"string":[
{
"name":"my_string",
"value":"default"
}
]
}
//resources/base/element/color.json
{
"color":[
{
"name":"my_color",
"value":"#ff0000"
}
]
}
//resources/base/element/float.json
{
"float":[
{
"name":"my_float",
"value":"60vp"
}
]
}
//resources/tablet/element/string.json ```
{ // color.json
"string":[ {
{ "color": [
"name":"my_string",
"value":"tablet"
}
]
}
//resources/tablet/element/color.json
{
"color":[
{
"name":"my_color",
"value":"#0000ff"
}
]
}
//resources/tablet/element/float.json
{ {
"float":[ "name": "color_red",
"value": "#ffff0000"
},
{ {
"name":"my_float", "name": "color_blue",
"value":"80vp" "value": "#ff0000ff"
} }
] ]
} }
``` ```
![zh-cn_image_0000001267577314](figures/zh-cn_image_0000001267577314.png)
页面源码如下: ### 访问应用资源
在工程中,通过 "$r('app.type.name')" 的形式引用应用资源。app代表是应用内resources目录中定义的资源;type 代表资源类型(或资源的存放位置),可以取 color、float、string、plural和media,name代表资源命名,由开发者添加资源时确定。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 可以查看[声明式范式访问应用资源](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/ui/ts-resource-access.md),了解资源访问的更多细节。
### 示例
在应用的resources目录下,创建名为tablet的限定词子目录,并按照下表所示,在base目录和tablet限定词目录中添加相应的资源。
| 资源名称 | 资源类型 | base目录中资源值 | 限定词目录(tablet)中资源值 |
| -------- | -------- | -------- | -------- |
| my_string | string | default | tablet |
| my_color | color | \#ff0000 | \#0000ff |
| my_float | float | 60vp | 80vp |
| my_image | media | my_image.png(太阳图标) | my_image.png(月亮图标) |
```ts 在代码中通过 "$r('app.type.name')" 的形式使用应用资源,并分别在默认设备和平板上查看代码的运行效果,可以发现同一资源在不同设备上的取值不同。
![zh-cn_image_0000001325731389](figures/zh-cn_image_0000001325731389.png)
```
@Entry @Entry
@Component @Component
struct Index { struct Index {
...@@ -133,11 +114,15 @@ struct Index { ...@@ -133,11 +114,15 @@ struct Index {
## 系统资源 ## 系统资源
除了自定义资源,开发者也可以使用系统中预定义的资源(即[分层参数](visual-style-basics.md),同一资源ID在设备类型、深浅色等不同配置下有不同的取值)。 除了自定义资源,开发者也可以使用系统中预定义的资源(即[分层参数](visual-basics.md),同一资源ID在设备类型、深浅色等不同配置下有不同的取值)。
在开发过程中,分层参数的用法与资源限定词基本一致。开发者可以通过"$r('sys.type.resource_id')"的形式引用系统资源。sys代表是系统资源;type代表资源类型,值可以取color、float、string和media;resource_id代表资源id。 在开发过程中,分层参数的用法与资源限定词基本一致。开发者可以通过"$r('sys.type.resource_id')"的形式引用系统资源。sys代表是系统资源;type代表资源类型,值可以取color、float、string和media;resource_id代表资源id。
可以访问本文"[资源](resource.md)",获取OHOS上支持的系统资源ID及其在不同配置下的取值。也可以访问[OpenHarmony/resources代码仓](https://gitee.com/openharmony/resources/tree/master/systemres/main/resources),了解OpenHarmony系统中预置的分层参数资源,可以发现这里目录结构与工程中的resources目录类似,也是通过资源限定词匹配不同的设备或设备状态。 可以查看本文[应用UX设计中关于资源的介绍](design-resources.md),获取OpenHarmony支持的系统资源ID及其在不同配置下的取值。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:** > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 仅声明式开发范式支持使用分层参数,类Web开发范式不支持。 > - 仅声明式开发范式支持使用分层参数,类Web开发范式不支持。
>
> - 系统资源可以保证不同团队开发出的应用有较为一致的视觉风格。对于系统预置应用,强烈建议使用系统资源;对于三方应用,可以根据需要选择使用系统资源或自定义应用资源。
>
> - 可以查看[OpenHarmony/resources代码仓](https://gitee.com/openharmony/resources/tree/master/systemres/main/resources)了解系统预置资源的实现,这里的目录结构与工程中的resources目录类似,也是通过资源限定词匹配不同的设备或设备状态。
# 典型场景
当基本的自适应布局无法满足多终端上屏幕的体验要求时,我们可以针对不同终端的屏幕特点,借助栅格系统和媒体查询能力,实现更复杂的布局效果。
## 缩进布局
在上一小节“栅格系统”中,我们介绍了四种不同的设备宽度类型,在不同宽度类型的设备中,默认的Margin、Gutter及Column数量不同。借助栅格系统,我们可以很方便的在不同宽度的设备上实现不同的缩进效果。
### 示例
![zh-cn_image_0000001317091985](figures/zh-cn_image_0000001317091985.png)
```ts
@Entry
@Component
struct IndentationSample {
build() {
Row() {
GridContainer() {
Column() {
ForEach([0, 1, 2, 4], () => {
Column() {
ItemContent()
}.useSizeType({ xs: 2, sm: 4, md: 6, lg: 8 })
})
}
}
}
.alignItems(VerticalAlign.Center)
.height('100%')
.backgroundColor('#F1F3F5')
}
}
@Component
struct ItemContent {
build() {
Column() {
Row() {
Row() {}
.width(28).height(28).borderRadius(14).margin({ right: 15 })
.backgroundColor('#E4E6E8')
Row() {}
.width('30%').height(20).borderRadius(4)
.backgroundColor('#E4E6E8')
}.width('100%').height(28)
Row() {}
.width('100%').height(68).borderRadius(16).margin({ top: 12 })
.backgroundColor('#E4E6E8')
}
.height(128)
.borderRadius(24)
.backgroundColor('#FFFFFF')
.padding({ top: 12, bottom: 12, left: 18, right: 18 })
.margin({ bottom: 12 })
}
}
```
## 挪移布局
挪移布局是栅格和折行能力的结合。当屏幕宽度发生改变,达到预设的断点时,栅格中子元素占据的列数会随着开发者的配置发生改变。当一行中的列数超过栅格组件在该断点的指定值时,可以自动换行。借助挪移布局,我们可以很方便的实现内容尺寸及相对位置的调整。
### 示例
当屏幕宽度发生变化时,图片和文字由”上下布局”切换为”左右布局”。
![zh-cn_image_0000001317211889](figures/zh-cn_image_0000001317211889.png)
```ts
@Entry
@Component
struct DiversionSample {
build() {
Row() {
GridContainer() {
Flex({
direction: FlexDirection.Row,
alignItems: ItemAlign.Center,
justifyContent: FlexAlign.Center,
wrap: FlexWrap.Wrap
}) {
Row() {
Image($r('app.media.illustrator'))
}
.aspectRatio(1)
.useSizeType({ xs: 2, sm: 4, md: 4, lg: 6 })
Flex({
direction: FlexDirection.Column,
alignItems: ItemAlign.Center,
justifyContent: FlexAlign.Center
}) {
Text('用户体验改进计划')
.textAlign(TextAlign.Center)
.fontSize(20)
.fontWeight(FontWeight.Medium)
Text('用户体验改进计划通过系统、应用分析、故障诊断,提升华为的产品和服务质量')
.textAlign(TextAlign.Center)
.fontSize(14)
.fontWeight(FontWeight.Medium)
}
.height(100)
.useSizeType({ xs: 2, sm: 4, md: 4, lg: 6 })
}
}
}.height('100%')
.alignItems(VerticalAlign.Center)
.backgroundColor('#F1F3F5')
}
}
```
## 重复布局
充分利用屏幕尺寸优势,当屏幕变宽时,增加显示元素的数量。注意这种场景下,组件的数量发生了变化。可以使用媒体查询改变元素数量,同时借助栅格系统设置这些元素在不同宽度设备下的尺寸和位置。
### 示例
![zh-cn_image_0000001268611858](figures/zh-cn_image_0000001268611858.png)
```ts
import mediaquery from '@ohos.mediaquery'
@Entry
@Component
struct RepeatSample {
@State isWidescreen: boolean = false;
private listener: mediaquery.MediaQueryListener = mediaquery.matchMediaSync('(width > 1200)');
onWidthChange = (mediaQueryResult) => {
if (mediaQueryResult.matches) {
this.isWidescreen = true;
} else {
this.isWidescreen = false;
}
}
private aboutToAppear(): void {
this.listener.on('change', this.onWidthChange);
}
private aboutToDisappear(): void {
this.listener.off('change', this.onWidthChange);
}
build() {
Row() {
GridContainer() {
ForEach([0, 1, 2, 3], () => {
Row() {
Column() { RepeatItemContent() }
.useSizeType({
xs: { span: 2, offset: 0 }, sm: { span: 4, offset: 0 },
md: { span: 4, offset: 0 }, lg: { span: 6, offset: 0 }
})
if (this.isWidescreen) {
Column() { RepeatItemContent() }
.useSizeType({
md: { span: 4, offset: 4 }, lg: { span: 6, offset: 6 }
})
}
}
})
}
}
.height('100%')
.backgroundColor('#F1F3F5')
}
}
@Component
struct RepeatItemContent {
build() {
Flex() {
Row() {}.width(43).height(43).borderRadius(12).backgroundColor('#E4E6E8')
Flex({ alignItems: ItemAlign.Start, direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceAround }) {
Row() {}.height(10).width('100%').backgroundColor('#E4E6E8')
Row() {}.height(10).backgroundColor('#E4E6E8').width('50%')
}.flexGrow(1).margin({ left: 13 })
}
.padding({ top: 13, bottom: 13, left: 13, right: 37 })
.height(69)
.backgroundColor('#FFFFFF')
.margin({ bottom: 12 })
.borderRadius(24)
}
}
```
# 响应式布局
自适应布局可以保证窗口尺寸在一定范围内变化时,页面的显示是正常的。但是将窗口尺寸变化较大时(如窗口宽度从400vp变化为1000vp),仅仅依靠自适应布局可能出现图片异常放大或页面内容稀疏、留白过多等问题,此时就需要借助响应式布局能力调整页面结构。
响应式布局是指页面内的元素可以根据特定的特征(如窗口宽度、屏幕方向等)自动变化以适应外部容器变化的布局能力。响应式布局中最常使用的特征是窗口宽度,可以将窗口宽度划分为不同的范围(下文中称为断点)。当窗口宽度从一个断点变化到另一个断点时,改变页面布局(如将页面内容从单列排布调整为双列排布甚至三列排布等)以获得更好的显示效果。
当前OpenHarmony提供了如下三种响应式布局能力,后文中我们将依次展开介绍。
| 响应式布局能力 | 简介 |
| -------- | -------- |
| [断点](#断点) | 将窗口宽度划分为不同的范围(即断点),监听窗口尺寸变化,当断点改变时同步调整页面布局。 |
| [媒体查询](#媒体查询) | 媒体查询支持监听窗口宽度、横竖屏、深浅色、设备类型等多种媒体特征,当媒体特征发生改变时同步调整页面布局。 |
| [栅格布局](#栅格布局) | 栅格组件将其所在的区域划分为有规律的多列,通过调整不同断点下的栅格组件的参数以及其子组件占据的列数等,实现不同的布局效果。 |
## 断点
断点以应用窗口宽度为切入点,将应用窗口在宽度维度上分成了几个不同的区间即不同的断点,在不同的区间下,开发者可根据需要实现不同的页面布局效果。具体的断点如下所示。
| 断点名称 | 取值范围(vp) |
| -------- | -------- |
| xs | [0,&nbsp;320) |
| sm | [320,&nbsp;520) |
| md | [520,&nbsp;840) |
| lg | [840,&nbsp;+∞) |
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> - 以设备屏幕宽度作为参照物,也可以实现类似的效果。考虑到应用可能以非全屏窗口的形式显示,以应用窗口宽度为参照物更为通用。
>
> - 开发者可以根据实际使用场景决定适配哪些断点。如xs断点对应的一般是智能穿戴类设备,如果确定某页面不会在智能穿戴设备上显示,则可以不适配xs断点。
OpenHarmony提供了多种方法,判断应用当前处于何种断点,进而可以调整应用的布局。常见的监听断点变化的方法如下所示:
- 获取窗口对象并监听窗口尺寸变化
- 通过媒体查询监听应用窗口尺寸变化
- 借助栅格组件能力监听不同断点的变化
本小节中,先介绍如何通过窗口对象监听断点变化,后续的媒体查询及栅格章节中,将进一步展开介绍另外两种方法。
通过窗口对象监听断点变化的核心是获取窗口对象及注册窗口尺寸变化的回调函数。
1. 在Ability的[onWindowStageCreate](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/ability/stage-ability.md)生命周期回调中,获取并记录[窗口](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis/js-apis-window.md)对象。
```
// MainAbility.ts
import window from '@ohos.window'
export default class MainAbility extends Ability {
...
onWindowStageCreate(windowStage) {
window.getTopWindow(this.context).then((windowObj) => {
AppStorage.SetOrCreate('windowObj', windowObj)
})
}
...
}
```
2. 在页面中,通过窗口对象获取启动时的应用窗口宽度,同时注册回调函数监听窗口尺寸变化。
```
@Entry
@Component
struct Index {
@State private currentBreakpoint: string = 'sm'
private updateBreakpoint(windowWidth) {
if (windowWidth < 320) {
this.currentBreakpoint = 'xs'
return
}
if (windowWidth < 520) {
this.currentBreakpoint = 'sm'
return
}
if (windowWidth < 840) {
this.currentBreakpoint = 'md'
return
}
this.currentBreakpoint = 'lg'
}
aboutToAppear() {
let windowObj: window.Window = AppStorage.Get('windowObj')
// 获取应用启动时的窗口尺寸
windowObj.getProperties().then((windowProperties) => {
this.updateBreakpoint(px2vp(windowProperties.windowRect.width))
})
// 注册回调函数,监听窗口尺寸变化
windowObj.on('windowSizeChange', (data) => {
this.updateBreakpoint(px2vp(data.width))
});
}
aboutToDisappear() {
let windowObj: window.Window = AppStorage.Get('windowObj')
windowObj.off('windowSizeChange')
}
build() {
Flex({justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center}) {
Text(this.currentBreakpoint).fontSize(50).fontWeight(FontWeight.Medium)
}
.width('100%')
.height('100%')
}
}
```
3. 运行及验证效果。
| | | |
| -------- | -------- | -------- |
| ![zh-cn_image_0000001336485520](figures/zh-cn_image_0000001336485520.jpg) | ![zh-cn_image_0000001386645965](figures/zh-cn_image_0000001386645965.jpg) | ![zh-cn_image_0000001386325621](figures/zh-cn_image_0000001386325621.jpg) |
## 媒体查询
在实际应用开发过程中,开发者常常需要针对不同类型设备或同一类型设备的不同状态来修改应用的样式。媒体查询提供了丰富的媒体特征监听能力,可以监听应用显示区域变化、横竖屏、深浅色、设备类型等等,因此在应用开发过程中使用的非常广泛。
本小节仅介绍**媒体查询跟断点的结合**,即如何借助媒体查询能力,监听断点的变化,读者可以自行查阅官网中关于[媒体查询](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/ui/ui-ts-layout-mediaquery.md)的相关介绍了解更详细的用法。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 类Web开发范式,支持在js文件和css文件中使用媒体查询,请查看[js媒体查询](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis/js-apis-mediaquery.md)和[css媒体查询](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-js/js-components-common-mediaquery.md)了解详细用法。
**示例:**
通过媒体查询,监听应用窗口宽度变化,获取当前应用所处的断点值。
| | | |
| -------- | -------- | -------- |
| ![zh-cn_image_0000001336165712](figures/zh-cn_image_0000001336165712.jpg) | ![zh-cn_image_0000001386485617](figures/zh-cn_image_0000001386485617.jpg) | ![zh-cn_image_0000001386805569](figures/zh-cn_image_0000001386805569.jpg) |
```
// common/breakpointsystem.ets
// 对通过媒体查询监听断点的功能做简单的封装,方便后续使用
import mediaquery from '@ohos.mediaquery';
export class BreakpointType<T> {
sm: T
md: T
lg: T
constructor(sm: T, md: T, lg: T) {
this.sm = sm
this.md = md
this.lg = lg
}
GetValue(currentBreakpoint: string) {
if (currentBreakpoint === 'sm') {
return this.sm
}
if (currentBreakpoint === 'md') {
return this.md
}
if (currentBreakpoint === 'lg') {
return this.lg
}
return undefined
}
}
export class BreakpointSystem {
private currentBreakpoint: string = 'md'
private smListener: mediaquery.MediaQueryListener
private mdListener: mediaquery.MediaQueryListener
private lgListener: mediaquery.MediaQueryListener
private updateCurrentBreakpoint(breakpoint: string) {
if (this.currentBreakpoint !== breakpoint) {
this.currentBreakpoint = breakpoint
AppStorage.Set<string>('currentBreakpoint', this.currentBreakpoint)
}
}
private isBreakpointSM = (mediaQueryResult) => {
if (mediaQueryResult.matches) {
this.updateCurrentBreakpoint('sm')
}
}
private isBreakpointMD = (mediaQueryResult) => {
if (mediaQueryResult.matches) {
this.updateCurrentBreakpoint('md')
}
}
private isBreakpointLG = (mediaQueryResult) => {
if (mediaQueryResult.matches) {
this.updateCurrentBreakpoint('lg')
}
}
public register() {
this.smListener = mediaquery.matchMediaSync("(320vp<width<520vp)")
this.smListener.on("change", this.isBreakpointSM)
this.mdListener = mediaquery.matchMediaSync("(520vp<width<840vp)")
this.mdListener.on("change", this.isBreakpointMD)
this.lgListener = mediaquery.matchMediaSync("(840vp<width)")
this.lgListener.on("change", this.isBreakpointLG)
}
public unregister() {
this.smListener.off("change", this.isBreakpointSM)
this.mdListener.off("change", this.isBreakpointMD)
this.lgListener.off("change", this.isBreakpointLG)
}
}
// MediaQuerySample.ets
import { BreakpointSystem, BreakpointType } from '../common/breakpointsystem'
@Entry
@Component
struct MediaQuerySample {
@StorageLink('currentBreakpoint') private currentBreakpoint: string = "md";
@State private icon: Resource = $r('app.media.md')
private breakpointSystem: BreakpointSystem = new BreakpointSystem()
aboutToAppear() {
this.breakpointSystem.register()
}
aboutToDisappear() {
this.breakpointSystem.unregister()
}
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Image(new BreakpointType($r('app.media.sm'), $r('app.media.md'), $r('app.media.lg')).GetValue(this.currentBreakpoint))
.height(100)
.width(100)
.objectFit(ImageFit.Contain)
Text(this.currentBreakpoint)
.fontSize(24)
.margin(10)
}
.width('100%')
.height('100%')
}
}
```
## 栅格布局
## <sub>栅格</sub>
栅格是多设备场景下通用的辅助定位工具,通过将空间分割为有规律的栅格。栅格可以显著降低适配不同屏幕尺寸的设计及开发成本,使得整体设计和开发流程更有秩序和节奏感,同时也保证多设备上应用显示的协调性和一致性,提升用户体验。
![zh-cn_image_0000001224173302](figures/zh-cn_image_0000001224173302.png)
栅格的样式由Margin、Gutter、Columns三个属性决定。
- Margin是相对应用窗口、父容器的左右边缘的距离,决定了内容可展示的整体宽度。
- Gutter是相邻的两个Column之间的距离,决定内容间的紧密程度。
- Columns是栅格中的列数,其数值决定了内容的布局复杂度。
单个Column的宽度是系统结合Margin、Gutter和Columns自动计算的,不需要也不允许开发者手动配置。
### 栅格布局
栅格布局就是栅格结合了断点,实现栅格布局能力的组件叫栅格组件。在实际使用场景中,可以根据需要配置不同断点下栅格组件中元素占据的列数,同时也可以调整Margin、Gutter、Columns的取值,从而实现不同的布局效果。
| sm断点 | md断点 |
| -------- | -------- |
| ![zh-cn_image_0000001336486244](figures/zh-cn_image_0000001336486244.jpg) | ![zh-cn_image_0000001386646685](figures/zh-cn_image_0000001386646685.jpg) |
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> - ArkUI在API 9对栅格组件做了重构,推出了新的栅格组件[GridRow](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-gridrow.md)和[GridCol](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-gridcol.md),同时原有的[GridContainer组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-gridcontainer.md)及[栅格设置](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-universal-attributes-grid.md)已经废弃。
>
> - 本文中提到的栅格组件,如无特别说明,都是指GridRow和GridCol组件。
### 栅格组件的断点
栅格组件提供了丰富的断点定制能力。
**(一)开发者可以修改断点的取值范围,支持启用最多6个断点。**
- 基于本文断点小节介绍的推荐值,栅格组件默认提供xs、sm、md、lg四个断点。
- 栅格组件支持开发者修改断点的取值范围,除了默认的四个断点,还支持开发者启用xl和xxl两个额外的断点。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 断点并非越多越好,通常每个断点都需要开发者“精心适配”以达到最佳显示效果。
**示例1:**
修改默认的断点范围,同时启用xl和xxl断点。
图片右下角显示了当前设备屏幕的尺寸(即应用窗口尺寸),可以看到随着窗口尺寸发生变化,栅格的断点也相应发生了改变。(为了便于理解,下图中将设备的DPI设置为160,此时1vp=1px)
![window3](figures/window3.gif)
```
@Entry
@Component
struct GridRowSample1 {
@State private currentBreakpoint: string = 'unknown'
build() {
// 修改断点的取值范围同时启用更多断点,注意,修改的断点值后面必须加上vp单位。
GridRow(breakpoints: {value: ['600vp', '700vp', '800vp', '900vp', '1000vp'],
reference: BreakpointsReference.WindowSize}}) {
GridCol({span:{xs: 12, sm: 12, md: 12, lg:12, xl: 12, xxl:12}}) {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Text(this.currentBreakpoint).fontSize(50).fontWeight(FontWeight.Medium)
}
}
}.onBreakpointChange((currentBreakpoint: string) => {
this.currentBreakpoint = currentBreakpoint
})
}
}
```
**(二)栅格断点默认以窗口宽度为参照物,同时还允许开发者配置为以栅格组件本身的宽度为参照物。**
栅格既可以用于页面整体布局的场景,也可以用于页面局部布局的场景。考虑到在实际场景中,存在应用窗口尺寸不变但是局部区域尺寸发生了变化的情况,栅格组件支持以自身宽度为参照物响应断点变化具有更大的灵活性。
**示例2:**
以栅格组件宽度为参考物响应断点变化。满足窗口尺寸不变,而部分内容区需要做响应式变化的场景。
为了便于理解,下图中自定义预览器的设备屏幕宽度设置为650vp。示例代码中将侧边栏的变化范围控制在[100vp, 600vp],那么右侧的栅格组件宽度相对应在[550vp, 50vp]之间变化。根据代码中对栅格断点的配置,栅格组件宽度发生变化时,其断点相应的发生改变。
![component](figures/component.gif)
```
@Entry
@Component
struct GridRowSample2 {
@State private currentBreakpoint: string = 'unknown';
build() {
// 用户可以通过拖拽侧边栏组件中的分隔线,调整侧边栏和内容区的宽度。
SideBarContainer(SideBarContainerType.Embed)
{
// 侧边栏,尺寸变化范围 [100vp, 600vp]
Column(){}.width('100%').backgroundColor('#19000000')
// 内容区,尺寸变化范围 [窗口宽度550vp, 窗口宽度50vp]
GridRow({breakpoints: {value: ['100vp', '200vp', '300vp', '400vp', '500vp'],
reference: BreakpointsReference.ComponentSize}}) {
GridCol({span:{xs: 12, sm: 12, md: 12, lg:12, xl: 12, xxl:12}}) {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Text(this.currentBreakpoint).fontSize(50).fontWeight(FontWeight.Medium)
}
}
}.onBreakpointChange((currentBreakpoint: string) => {
this.currentBreakpoint = currentBreakpoint;
}).width('100%')
}
// 侧边栏拖拽到最小宽度时,不自动隐藏
.autoHide(false)
.sideBarWidth(100)
// 侧边栏的最小宽度
.minSideBarWidth(100)
// 侧边栏的最大宽度
.maxSideBarWidth(600)
}
}
```
**(三)栅格组件的断点发生变化时,会通过onBreakPointChange事件通知开发者。**
在之前的两个例子中,已经演示了onBreakpointChange事件的用法,此处不再赘述。
### 栅格组件的columns、gutter和margin
栅格组件columns默认为12列,gutter默认为0,同时支持开发者根据实际需要定义不同断点下的columns数量以及gutter长度。特别的,在栅格组件实际使用过程中,常常会发生多个元素占据的列数相加超过总列数而折行的场景。栅格组件还允许开发者分别定义水平方向的gutter(相邻两列之间的间距)和垂直方向的gutter(折行时相邻两行之间的间距)。
考虑到[组件通用属性](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-universal-attributes-size.md)中已经有margin和padding,栅格组件不再单独提供额外的margin属性,直接使用通用属性即可。借助margin或者padding属性,均可以控制栅格组件与父容器左右边缘的距离,但是二者也存在一些差异:
- margin区域在栅格组件的边界外,padding区域在栅格组件的边界内。
- 栅格组件的backgroundColor会影响padding区域,但不会影响margin区域。
总的来讲,margin在组件外而padding在组件内,开发者可以根据实际需要进行选择及实现目标效果。
**示例3:**
不同断点下,定义不同的columns和gutter。
| sm | md | lg |
| -------- | -------- | -------- |
| ![zh-cn_image_0000001386807405](figures/zh-cn_image_0000001386807405.jpg) | ![zh-cn_image_0000001336167540](figures/zh-cn_image_0000001336167540.jpg) | ![zh-cn_image_0000001386487457](figures/zh-cn_image_0000001386487457.jpg) |
```
@Entry
@Component
struct GridRowSample3 {
private bgColors: ResourceColor[] = [
$r('sys.color.ohos_id_color_palette_aux1'),
$r('sys.color.ohos_id_color_palette_aux2'),
$r('sys.color.ohos_id_color_palette_aux3'),
$r('sys.color.ohos_id_color_palette_aux4'),
$r('sys.color.ohos_id_color_palette_aux5'),
$r('sys.color.ohos_id_color_palette_aux6')
]
build() {
// 配置不同断点下columns和gutter的取值
GridRow({columns: {sm: 4, md: 8, lg: 12},
gutter: {x: {sm: 8, md: 16, lg: 24}, y: {sm: 8, md: 16, lg: 24}}}) {
ForEach(this.bgColors, (bgColor)=>{
GridCol({span: {sm: 2, md: 2, lg: 2}}) {
Row().backgroundColor(bgColor).height(30)
}
})
}
}
}
```
**示例4:**
通过通用属性margin或者padding,均可以控制栅格组件与其父容器左右两侧的距离,但padding区域计算在栅格组件内而margin区域计算在栅格组件外。此外,借助onBreakpointChange事件,还可以改变不同断点下margin或padding值。
![zh-cn_image_0000001336327452](figures/zh-cn_image_0000001336327452.png)
```
@Entry
@Component
struct GridRowSample4 {
@State private gridMargin: number = 0
build() {
Column() {
Row().width('100%').height(30)
// 使用padding控制栅格左右间距
GridRow() {
GridCol({span:{xs: 12, sm: 12, md: 12, lg:12}}) {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Text("padding").fontSize(24).fontWeight(FontWeight.Medium)
}.backgroundColor('#19000000')
}
}
.height(50)
.borderWidth(2)
.borderColor('#F1CCB8')
.padding({left: this.gridMargin, right: this.gridMargin})
// 借助断点变化事件配置不同断点下栅格组件的左右间距值
.onBreakpointChange((currentBreakpoint: string) => {
if (currentBreakpoint === 'lg' || currentBreakpoint === 'md') {
this.gridMargin = 24
} else {
this.gridMargin = 12
}
})
Row().width('100%').height(30)
// 使用margin控制栅格左右间距
GridRow() {
GridCol({span:{xs: 12, sm: 12, md: 12, lg:12}}) {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Text("margin").fontSize(24).fontWeight(FontWeight.Medium)
}.backgroundColor('#19000000')
}
}
.height(50)
.borderWidth(2)
.borderColor('#F1CCB8')
.margin({left: this.gridMargin, right: this.gridMargin})
}
}
}
```
### 栅格组件的span、offset和order
栅格组件(GridRow)的直接孩子节点只可以是栅格子组件(GridCol),GridCol组件支持配置span、offset和order三个参数。这三个参数的取值按照"xs -&gt; sm -&gt; md -&gt; lg -&gt; xl -&gt; xxl"的向后方向具有继承性(不支持向前方向的继承性),例如将sm断点下span的值配置为3,不配置md断点下span的值,则md断点下span的取值也是3。
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
| -------- | -------- | -------- | -------- | -------- |
| span | {xs?:&nbsp;number,&nbsp;sm?:&nbsp;number,&nbsp;md?:&nbsp;number,&nbsp;lg?:&nbsp;number,&nbsp;xl?:&nbsp;number,&nbsp;xxl?:number} | 是 | - | 在栅格中占据的列数。span为0,意味着该元素既不参与布局计算,也不会被渲染。 |
| offset | {xs?:&nbsp;number,&nbsp;sm?:&nbsp;number,&nbsp;md?:&nbsp;number,&nbsp;lg?:&nbsp;number,&nbsp;xl?:&nbsp;number,&nbsp;xxl?:number} | 否 | 0 | 相对于前一个栅格子组件偏移的列数。 |
| order | {xs?:&nbsp;number,&nbsp;sm?:&nbsp;number,&nbsp;md?:&nbsp;number,&nbsp;lg?:&nbsp;number,&nbsp;xl?:&nbsp;number,&nbsp;xxl?:number} | 否 | 0 | 元素的序号,根据栅格子组件的序号,从小到大对栅格子组件做排序。 |
**示例5:**
通过span参数配置GridCol在不同断点下占据不同的列数。特别的,将md断点下和6的span配置为0,这样在md断点下3和6不会渲染和显示。
| sm | md | lg |
| -------- | -------- | -------- |
| ![zh-cn_image_0000001336487884](figures/zh-cn_image_0000001336487884.jpg) | ![zh-cn_image_0000001386648317](figures/zh-cn_image_0000001386648317.jpg) | ![zh-cn_image_0000001386327997](figures/zh-cn_image_0000001386327997.jpg) |
```
@Entry
@Component
struct GridRowSample5 {
private elements: Object[] = [
{'index': 1, 'color': $r('sys.color.ohos_id_color_palette_aux1')},
{'index': 2, 'color': $r('sys.color.ohos_id_color_palette_aux2')},
{'index': 3, 'color': $r('sys.color.ohos_id_color_palette_aux3')},
{'index': 4, 'color': $r('sys.color.ohos_id_color_palette_aux4')},
{'index': 5, 'color': $r('sys.color.ohos_id_color_palette_aux5')},
{'index': 6, 'color': $r('sys.color.ohos_id_color_palette_aux6')},
]
build() {
GridRow() {
ForEach(this.elements, (item)=>{
GridCol({span: {sm: 6, md: (item.index % 3 === 0) ? 0 : 4, lg: 3}}) {
Row() {
Text('' + item.index).fontSize(24)
}
.justifyContent(FlexAlign.Center)
.backgroundColor(item.color).height(30)
}
})
}
}
}
```
**示例6:**
通过offset参数,配置GridCol相对其前一个兄弟间隔的列数。
| sm | md | lg |
| -------- | -------- | -------- |
| ![zh-cn_image_0000001386807873](figures/zh-cn_image_0000001386807873.jpg) | ![zh-cn_image_0000001336168020](figures/zh-cn_image_0000001336168020.jpg) | ![zh-cn_image_0000001386487913](figures/zh-cn_image_0000001386487913.jpg) |
```
@Entry
@Component
struct GridRowSample6 {
private elements: Object[] = [
{'index': 1, 'color': $r('sys.color.ohos_id_color_palette_aux1')},
{'index': 2, 'color': $r('sys.color.ohos_id_color_palette_aux2')},
{'index': 3, 'color': $r('sys.color.ohos_id_color_palette_aux3')},
{'index': 4, 'color': $r('sys.color.ohos_id_color_palette_aux4')},
{'index': 5, 'color': $r('sys.color.ohos_id_color_palette_aux5')},
{'index': 6, 'color': $r('sys.color.ohos_id_color_palette_aux6')},
]
build() {
GridRow() {
ForEach(this.elements, (item)=>{
GridCol({span: {sm: 6, md: 4, lg: 3}, offset: {sm: 0, md: 2, lg: 1} }) {
Row() {
Text('' + item.index).fontSize(24)
}
.justifyContent(FlexAlign.Center)
.backgroundColor(item.color).height(30)
}
})
}
}
}
```
**示例7:**
通过order属性,控制GridCol的顺序。在sm和md断点下,按照至6的顺序排列显示;在lg断点下,按照6至1的顺序排列显示。
| sm | md | lg |
| -------- | -------- | -------- |
| ![zh-cn_image_0000001336327916](figures/zh-cn_image_0000001336327916.jpg) | ![zh-cn_image_0000001336008356](figures/zh-cn_image_0000001336008356.jpg) | ![zh-cn_image_0000001336487888](figures/zh-cn_image_0000001336487888.jpg) |
```
@Entry
@Component
struct GridRowSample7 {
private elements: Object[] = [
{'index': 1, 'color': $r('sys.color.ohos_id_color_palette_aux1')},
{'index': 2, 'color': $r('sys.color.ohos_id_color_palette_aux2')},
{'index': 3, 'color': $r('sys.color.ohos_id_color_palette_aux3')},
{'index': 4, 'color': $r('sys.color.ohos_id_color_palette_aux4')},
{'index': 5, 'color': $r('sys.color.ohos_id_color_palette_aux5')},
{'index': 6, 'color': $r('sys.color.ohos_id_color_palette_aux6')},
]
build() {
GridRow() {
ForEach(this.elements, (item)=>{
GridCol({span: {sm: 6, md: 4, lg: 3}, order: {lg: (6-item.index)}}) {
Row() {
Text('' + item.index).fontSize(24)
}
.justifyContent(FlexAlign.Center)
.backgroundColor(item.color).height(30)
}
})
}
}
}
```
**示例8:**
仅配置sm和lg断点下span、offset和order参数的值,则md断点下这三个参数的取值与sm断点相同(按照“sm-&gt;md-&gt;lg”的向后方向继承)。
| sm | md | lg |
| -------- | -------- | -------- |
| ![zh-cn_image_0000001386648321](figures/zh-cn_image_0000001386648321.jpg) | ![zh-cn_image_0000001386328001](figures/zh-cn_image_0000001386328001.jpg) | ![zh-cn_image_0000001386807877](figures/zh-cn_image_0000001386807877.jpg) |
```
@Entry
@Component
struct GridRowSample8 {
private elements: Object[] = [
{'index': 1, 'color': $r('sys.color.ohos_id_color_palette_aux1')},
{'index': 2, 'color': $r('sys.color.ohos_id_color_palette_aux2')},
{'index': 3, 'color': $r('sys.color.ohos_id_color_palette_aux3')},
{'index': 4, 'color': $r('sys.color.ohos_id_color_palette_aux4')},
{'index': 5, 'color': $r('sys.color.ohos_id_color_palette_aux5')},
{'index': 6, 'color': $r('sys.color.ohos_id_color_palette_aux6')},
]
build() {
GridRow() {
ForEach(this.elements, (item)=>{
// 不配置md断点下三个参数的值,则其取值与sm断点相同
GridCol({span: {sm:4, lg: 3}, offset: {sm: 2, lg: 1},
order: {sm: (6-item.index), lg: item.index}}) {
Row() {
Text('' + item.index).fontSize(24)
}
.justifyContent(FlexAlign.Center)
.backgroundColor(item.color).height(30)
}
})
}
}
}
```
### 栅格组件的嵌套使用
栅格组件可以嵌套使用以满足复杂场景的需要。
**示例9:**
| sm | md | lg |
| -------- | -------- | -------- |
| ![zh-cn_image_0000001336338670](figures/zh-cn_image_0000001336338670.jpg) | ![zh-cn_image_0000001336019094](figures/zh-cn_image_0000001336019094.jpg) | ![zh-cn_image_0000001336498646](figures/zh-cn_image_0000001336498646.jpg) |
```
@Entry
@Component
struct GridRowSample9 {
private elements: Object[] = [
{'index': 1, 'color': $r('sys.color.ohos_id_color_palette_aux1')},
{'index': 2, 'color': $r('sys.color.ohos_id_color_palette_aux2')},
{'index': 3, 'color': $r('sys.color.ohos_id_color_palette_aux3')},
{'index': 4, 'color': $r('sys.color.ohos_id_color_palette_aux4')},
{'index': 5, 'color': $r('sys.color.ohos_id_color_palette_aux5')},
{'index': 6, 'color': $r('sys.color.ohos_id_color_palette_aux6')},
]
build() {
GridRow() {
GridCol({span: {sm: 12, md: 10, lg: 8}, offset: {sm: 0, md: 1, lg: 2}}) {
GridRow() {
ForEach(this.elements, (item)=>{
GridCol({span: {sm: 6, md: 4, lg: 3}}) {
Row() {
Text('' + item.index).fontSize(24)
}
.justifyContent(FlexAlign.Center)
.backgroundColor(item.color).height(30)
}
})
}
.backgroundColor('#19000000')
.height('100%')
}
}
}
}
```
### 总结
如前所述,栅格组件提供了丰富的自定义能力,功能异常灵活和强大。只需要明确栅格在不同断点下的Columns、Margin、Gutter及span等参数,即可确定最终布局,无需关心具体的设备类型及设备状态(如横竖屏)等。栅格可以节约设计团队与开发团队的沟通成本,提升整体开发效率。
## 相关实例
针对栅格断点系统开发,有以下相关实例可供参考:
- [`Weather`:一多天气(eTS)(API9)](https://gitee.com/openharmony/applications_app_samples/tree/master/MultiDeviceAppDev/Weather)
# 概览
[短信](https://gitee.com/openharmony/applications_mms/tree/master)是OpenHarmony中预置的系统应用,主要包含信息查看、发送短信、接收短信、短信送达报告、删除短信等功能。在不同类型设备上,短信应用的功能完全相同,故短信应用适合使用[部署模型A](introduction.md#部署模型)(即:不同类型的设备上安装运行相同的HAP包或HAP包组合)。
本案例中,在会话详情页面利用[方舟开发框架](introduction.md#方舟开发框架)提供的“一多”能力,用一套代码同时适配默认设备和平板。
## 工程结构
短信应用的工程结构如下图所示,当前该应用的功能较少,所以直接使用了DevEco Studio创建出的默认工程结构。具体采用何种形式的工程结构,并不影响应用的开发。但是使用推荐的工程结构,目录结构更清晰,拓展性也更好。
短信应用UI相关的逻辑集中在views和pages两个目录,分别存放公共组件及页面。当前短信应用主要包含如下页面:
- 信息列表页面:首页,展示信息列表。
- 通知信息列表页面:将通知类信息集中在一起展示,与信息列表页面类似。
- 会话详情页面:展示与某联系人的所有信息往来。
- 报告详情页面:信息发送报告的详情页面。
- 设置页面:消息设置页面,如是否展示送达报告等。
```
/Mms/
├── doc # 资料
├── entry
│ └── src
│ └── main
│ ├── resources # 资源配置文件存放目录
│ ├── config.json # 全局配置文件
│ └── ets # ets代码目录
│ ├── ServiceAbility # 后台常驻服务
│ └── default # 业务代码目录
│ ├── data # 自定义数据类型
│ ├── model # 对接数据库
│ ├── pages # 所有页面
│ │ ├── conversation # 会话详情页面
│ │ ├── conversationlist # 信息列表页面
│ │ ├── index # 初始页面
│ │ ├── info_msg # 通知信息列表页面
│ │ ├── query_report # 报告详情页面
│ │ └── settings # 设置页面
│ ├── service # 业务逻辑
│ ├── utils # 工具类
│ ├── views # 自定义组件
│ └── app.ets # 应用生命周期
├── signs # 签名
└── LICENSE
```
短信应用在开发阶段,采用了一层工程结构。由于功能较为简单,所以并没有规划共用的feature和common目录,仅采用了一层product目录。
- 业务形态层(product)
该目录采用IDE工程默认创建的entry目录,开发者可根据需要在创建Module时自行更改该目录名。不同产品形态,编译出相同的短信HAP包。
# 组合成型
现在会话详情页面的顶部标题栏、信息列表及底部输入栏都已经准备完毕,将这三部分组合起来即可得到完整的页面。
- 通过[Flex组件](../../reference/arkui-ts/ts-container-flex.md)将三个部分组合起来,注意justifyContent: FlexAlign.SpaceBetween配置项是将Flex组件中的元素按照主轴方向均匀分配,其中第一个元素与顶部对齐,最后一个元素与底部对齐。
- 通过[List组件](../../reference/arkui-ts/ts-container-list.md)[ForEach语法](../../ui/ts-rending-control-syntax-foreach.md),显示整个消息列表。
| 默认设备 | 平板 |
| -------- | -------- |
| ![zh-cn_image_0000001225812214](figures/zh-cn_image_0000001225812214.png) | ![zh-cn_image_0000001269812713](figures/zh-cn_image_0000001269812713.png) |
```ts
@Entry
@Component
struct Conversation {
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Start,
justifyContent: FlexAlign.SpaceBetween }) {
Column() {
TopArea() // 顶部标题栏
List() { // 消息列表
ForEach(globalMessageList, (item, index) => {
ListItem() {
MessageItem({
isReceived: item.isReceived,
content: item.content,
time: item.time
})
})
}
.listDirection(Axis.Vertical)
.edgeEffect(EdgeEffect.Spring)
}
ButtomArea() // 底部输入栏
}
.backgroundColor("#F1F3F5")
.width('100%')
.height('100%')
}
}
```
# 底部输入栏
有了顶部工具栏的开发经验,可以发现底部输入栏的结构更为简单,它同样以Flex组件作为父容器,同时包含文本输入框(请访问[文本输入组件](../../reference/arkui-ts/ts-basic-components-textarea.md)查看详细介绍)和消息发送图标两个子节点。
![zh-cn_image_0000001285597989](figures/zh-cn_image_0000001285597989.jpg)
为了便于查看效果,我们同样给底部输入栏设置了淡蓝色的背景色。注意这里有一个特殊的地方,我们给TextArea设置了flexGrow(1)属性。flexGrow属性仅在父组件是Flex组件时生效,表示Flex容器的剩余空间分配给此属性所在的组件的比例,flexGrow(1)表示父容器的剩余空间全部分配给此组件,详见[Flex布局](../../reference/arkui-ts/ts-universal-attributes-flex-layout.md)
| 默认设备 | 平板 |
| -------- | -------- |
| ![zh-cn_image_0000001292656257](figures/zh-cn_image_0000001292656257.png) | ![zh-cn_image_0000001246337342](figures/zh-cn_image_0000001246337342.png) |
```
@Component
struct ButtomArea {
build() {
Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
TextArea({ placeholder:'短信' })
.placeholderColor("#99000000")
.caretColor("#007DFF")
.backgroundColor("#F1F3F5")
.borderRadius(20)
.height(40)
.flexGrow(1) // 将父容器的剩余空间全部分配给此组件
Image($r("app.media.send"))
.height(36)
.width(36)
.opacity(0.4)
.margin({ left:12 })
}
.height(72)
.width('100%')
.padding({ left:24, right:24, bottom:8, top:8 })
.backgroundColor('#87CEFA') // 底部输入栏背景色,仅用于开发测试
}
}
```
# 信息列表
观察信息列表区域,可以发现它是由一个个消息气泡组成的,另外消息气泡在默认设备和平板上的布局有差异。本小节将围绕如下两个主题介绍如何实现消息列表。
- 如何实现自定义消息气泡组件。
- 如何在默认设备和平板上自适应布局。
| 默认设备 | 平板 |
| -------- | -------- |
| ![zh-cn_image_0000001224250574](figures/zh-cn_image_0000001224250574.jpg) | ![zh-cn_image_0000001225809618](figures/zh-cn_image_0000001225809618.png) |
## 消息气泡
先做一个最简单的消息气泡,通过borderRadius属性可以设置边框的圆角半径(详见[边框设置](../../reference/arkui-ts/ts-universal-attributes-border.md))。
| 默认设备 | 平板 |
| -------- | -------- |
| ![zh-cn_image_0000001313844881](figures/zh-cn_image_0000001313844881.png) | ![zh-cn_image_0000001313724825](figures/zh-cn_image_0000001313724825.png) |
```
@Component
struct MessageBubble {
private content: string = "OpenHarmony";
build() {
Column() {
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.End }) {
Text(this.content)
.fontSize(16)
.lineHeight(21)
.padding({ left: 12, right: 12, top: 8, bottom: 8 })
.backgroundColor("#C0EBDF")
.borderRadius(24)
.fontColor("#182431")
}.width('100%')
}
.margin({left: 24, right: 24 })
.backgroundColor('#87CEFA') // 消息背景色,仅用于开发和测试
}
}
```
注意这个简单的消息气泡,左上角(或右上角)的样式,与实际期望不符。我们先修改发送消息右上角的样式,接收消息左上角的实现与之类似。
[Stack组件](../../reference/arkui-ts/ts-container-stack.md)是一个堆叠容器,其子组件按照轴方向依次堆叠,后一个子组件覆盖前一个子组件。通过其alignContent接口,可以设置子组件在容器内的对齐方式,如alignContent: Alignment.TopStart代表子组件从左上角对齐。
| 默认设备 | 平板 |
| -------- | -------- |
| ![zh-cn_image_0000001313604829](figures/zh-cn_image_0000001313604829.png) | ![zh-cn_image_0000001313523845](figures/zh-cn_image_0000001313523845.png) |
```
@Component
struct MessageBubble {
private content: string = "OpenHarmony";
private time: string = "今天 上午 10:35";
build() {
Column() {
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.End }) {
Stack({ alignContent: Alignment.TopEnd }) { // 在左上角堆叠一个小色块
Column()
.backgroundColor("#C0EBDF")
.borderRadius(4)
.width(24)
.height(24)
Text(this.content)
.fontSize(16)
.lineHeight(21)
.padding({ left: 12, right: 12, top: 8, bottom: 8 })
.backgroundColor("#C0EBDF")
.borderRadius(24)
.fontColor("#182431")
}
}.width('100%')
}
.margin({left: 24, right: 24 })
.backgroundColor('#87CEFA') // 消息背景色,仅用于开发和测试
}
}
```
接下来我们在消息气泡下方加上时间显示,如下图所示,一个消息气泡自定义组件就基本完成了。
| 默认设备 | 平板 |
| -------- | -------- |
| ![zh-cn_image_0000001266163748](figures/zh-cn_image_0000001266163748.png) | ![zh-cn_image_0000001265685020](figures/zh-cn_image_0000001265685020.png) |
```
@Component
struct MessageBubble {
private content: string = "OpenHarmony";
private time: string = "上午 10:35";
build() {
Column() {
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.End }) {
Stack({ alignContent: Alignment.TopEnd }) {
Column()
.backgroundColor("#C0EBDF")
.borderRadius(4)
.width(24)
.height(24)
Text(this.content)
.fontSize(16)
.lineHeight(21)
.padding({ left: 12, right: 12, top: 8, bottom: 8 })
.backgroundColor("#C0EBDF")
.borderRadius(24)
.fontColor("#182431")
}
}.width('100%')
// 在消息气泡底部增加时间显示
Flex({ alignItems: ItemAlign.Center, direction: FlexDirection.Row,
justifyContent: FlexAlign.End}) {
Text(this.time)
.textAlign(TextAlign.Start)
.fontSize(10)
.lineHeight(13)
.fontColor("#99182431")
}.width('100%').margin({ left: 12, right: 0 })
}
.margin({left: 24, right: 24 })
.backgroundColor('#87CEFA') // 消息背景色,仅用于开发和测试
}
}
```
发送出的消息和接收到的消息的消息气泡结构基本一致,可以通过增加一个标志位,让两种消息共用MessageBubble这个自定义组件,代码如下所示。将这个标志位设置true,可以查看接收消息的效果。
| 默认设备 | 平板 |
| -------- | -------- |
| ![zh-cn_image_0000001265844904](figures/zh-cn_image_0000001265844904.png) | ![zh-cn_image_0000001266004832](figures/zh-cn_image_0000001266004832.png) |
```
@Component
struct MessageBubble {
private isReceived:boolean=false; // 通过标志位,判断是发送or接收场景,进而使用不同的样式
private content:string="OpenHarmony";
private time:string="今天 10:00";
build() {
Column() {
Flex({ justifyContent:this.isReceived? FlexAlign.Start: FlexAlign.End,
alignItems: ItemAlign.Center }) {
Stack({ alignContent:this.isReceived? Alignment.TopStart: Alignment.TopEnd }) {
Column()
.backgroundColor(this.isReceived?"#FFFFFF":"#C0EBDF")
.borderRadius(4)
.width(24)
.height(24)
Text(this.content)
.fontSize(16)
.lineHeight(21)
.padding({ left:12, right:12, top:8, bottom:8 })
.backgroundColor(this.isReceived?"#FFFFFF":"#C0EBDF")
.borderRadius(24)
.fontColor("#182431")
}
}.width('100%')
Flex({ alignItems: ItemAlign.Center, direction: FlexDirection.Row,
justifyContent:this.isReceived? FlexAlign.Start: FlexAlign.End }) {
Text(this.time)
.textAlign(TextAlign.Start)
.fontSize(10)
.lineHeight(13)
.fontColor("#99182431")
}.width('100%')
.margin({ left:this.isReceived?12:0, right:this.isReceived?0:12 })
}
.margin({left:24, right:24 })
.backgroundColor('#87CEFA') // 消息背景色,仅用于开发和测试
}
}
```
## 栅格布局
回顾方舟开发框架一多能力,消息气泡在默认设备和平板上布局不同,可以借助栅格布局来解决。为了方便测试,我们预定义一个全局数组。
```
const globalMessageList:any[] = [
{
'time':'上午 10:20',
'content':'项目介绍',
'isReceived':false
},
{
'time':'上午 10:28',
'content':'OpenHarmony是由开放原子开源基金会(OpenAtom Foundation)孵化及运营的开源项目,目标是面向全场景、全连接、全智能时代,基于开源的方式,搭建一个智能终端设备操作系统的框架和平台,促进万物互联产业的繁荣发展。',
'isReceived':false
},{
'time':'上午 10:32',
'content':'技术架构',
'isReceived':true
},
{
'time':'上午 10:35',
'content':'OpenHarmony整体遵从分层设计,从下向上依次为:内核层、系统服务层、框架层和应用层。系统功能按照“系统 > 子系统 > 组件”逐级展开,在多设备部署场景下,支持根据实际需求裁剪某些非必要的组件。',
'isReceived':true
}
];
```
结合[栅格组件](../../reference/arkui-ts/ts-container-gridcontainer.md)的定义,考虑我们当前的实际场景,GridContainer的各参数设置如下。
- columns:取默认值(auto),即根据设备尺寸自动设置栅格中的列数。
- sizeType:取默认值(SizeType.Auto),即根据设备类型自动选择。
- gutter:栅格布局列间距,当前场景未使用该参数,设置为0即可。
- margin: 栅格布局两侧间距,在开发消息气泡组件时,已经设置了左右间距,故该属性也配置为0。
栅格中仅包含我们自定义的消息气泡组件,该组件在各类型设备上的参数配置如下,可以通过[useSizeType属性](../../reference/arkui-ts/ts-universal-attributes-grid.md)设置消息气泡在不同场景下的尺寸和偏移值。
| SizeType类型 | 设备宽度(vp) | 设备总列数 | 消息气泡占用的列数 | 接收场景偏移的列数 | 发送场景偏移的列数 |
| -------- | -------- | -------- | -------- | -------- | -------- |
| XS | [0,&nbsp;320) | 2 | 2 | 0 | 0 |
| SM | [320,&nbsp;600) | 4 | 4 | 0 | 0 |
| MD | [600,&nbsp;840) | 8 | 6 | 0 | 2 |
| LG | [840,&nbsp;+∞) | 12 | 8 | 0 | 4 |
| 默认设备 | 平板 |
| -------- | -------- |
| ![zh-cn_image_0000001313844889](figures/zh-cn_image_0000001313844889.png) | ![zh-cn_image_0000001313724829](figures/zh-cn_image_0000001313724829.png) |
```
@Component
struct MessageItem {
private isReceived:boolean;
private content:string;
private time:string;
build() {
GridContainer({gutter:0, margin:0 }) {
Flex({ justifyContent: FlexAlign.End, alignItems: ItemAlign.End }) {
MessageBubble({
isReceived:this.isReceived,
content:this.content,
time:this.time
})
}
.useSizeType({
xs: { span:2, offset:0 },
sm: { span:4, offset:0 },
md: { span:6, offset:this.isReceived?0:2 },
lg: { span:8, offset:this.isReceived?0:4 }
})
}.width('100%')
}
}
@Entry
@Component
struct Conversation {
build() {
Column() { // 验证效果
MessageItem({
isReceived: globalMessageList[1].isReceived,
content: globalMessageList[1].content,
time: globalMessageList[1].time
})
MessageItem({
isReceived: globalMessageList[3].isReceived,
content: globalMessageList[3].content,
time: globalMessageList[3].time
})
}.backgroundColor('#87CEFA') // 消息背景色,仅用于开发和测试
}
}
```
# 页面结构
| 默认设备 | 平板 |
| -------- | -------- |
| ![zh-cn_image_0000001270045849](figures/zh-cn_image_0000001270045849.png) | ![zh-cn_image_0000001225486054](figures/zh-cn_image_0000001225486054.png) |
会话详情页面在默认设备和平板上的样式如上图所示,会话详情页面可以划分为三个部分:
| 页面组成 | 介绍 |
| -------- | -------- |
| 顶部标题栏 | ![zh-cn_image_0000001224250350](figures/zh-cn_image_0000001224250350.jpg) |
| 信息列表 | ![zh-cn_image_0000001268490361](figures/zh-cn_image_0000001268490361.jpg) |
| 底部输入栏 | ![zh-cn_image_0000001268250529](figures/zh-cn_image_0000001268250529.jpg) |
接下来我们详细介绍各部分的实现。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 为了方便理解,我们对会话详情页面做了一定的精简,本小节仅介绍会话详情页面最基础的实现。
# 顶部标题栏
| 默认设备 | 平板 |
| -------- | -------- |
| ![zh-cn_image_0000001268450593](figures/zh-cn_image_0000001268450593.jpg) | ![zh-cn_image_0000001225486202](figures/zh-cn_image_0000001225486202.png) |
顶部标题栏是一个简单的行布局,包含返回图标、联系人头像、联系人姓名和号码、拨号图标、设置图标共5个元素。其中,联系人姓名和号码以列布局的形式放在一起。
在默认设备和平板上,顶部标题栏的组件结构是相同的,仅联系人姓名和号码与拨号图标的间距不同。回顾方舟开发框架一多能力介绍,这个场景可以借助Blank组件使用拉伸能力。
我们先实现联系人姓名和号码,用Flex组件作为父容器,其包含两个Text子组件,分别用于存放联系人姓名和号码。Flex组件的属性设置如下:
- direction: FlexDirection.Column:子组件在Flex容器上以列的方式排布,即主轴是垂直方向。
- justifyContent: FlexAlign.Center:子组件在Flex容器主轴(垂直方向)上居中对齐。
- alignItems: ItemAlign.Start:子组件在Flex容器交叉轴(水平方向)上首部对齐。
可以查看[Flex组件](../../reference/arkui-ts/ts-container-flex.md)[Text组件](../../reference/arkui-ts/ts-basic-components-text.md)了解这两个组件各个属性的含义及详细用法。
| 默认设备 | 平板 |
| -------- | -------- |
| ![zh-cn_image_0000001246816566](figures/zh-cn_image_0000001246816566.png) | ![zh-cn_image_0000001292777537](figures/zh-cn_image_0000001292777537.png) |
```
@Component
struct TopArea {
build() {
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center,
alignItems: ItemAlign.Start}) {
Text('张三').fontSize(16).fontColor("#182431")
Text('+123 4567 8901').fontSize(14).fontColor("#66182431")
}
}
}
```
接下来我们通过width属性和height属性设置四个图标的宽高(详见[尺寸设置](../../reference/arkui-ts/ts-universal-attributes-size.md)),并将它们与联系人姓名和电话以及Blank组件一起放到Flex父容器中。为了便于查看效果,对顶部标题栏设置了淡蓝色的背景色。
| 默认设备 | 平板 |
| -------- | -------- |
| ![zh-cn_image_0000001292777233](figures/zh-cn_image_0000001292777233.png) | ![zh-cn_image_0000001246497370](figures/zh-cn_image_0000001246497370.png) |
```
@Component
struct TopArea {
build() {
Flex({ alignItems: ItemAlign.Center }) {
Image($r('app.media.back'))
.width(24)
.height(24)
Image($r('app.media.contact'))
.width(40)
.height(40)
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center,
alignItems: ItemAlign.Start}) {
Text('张三').fontSize(16).fontColor("#182431")
Text('+123 4567 8901').fontSize(14).fontColor("#66182431")
}
Blank() // 拉伸能力
Image($r("app.media.call"))
.width(24)
.height(24)
Image($r('app.media.dots'))
.width(24)
.height(24)
}
.width('100%')
.height(56)
.backgroundColor('#87CEFA') // 顶部标题栏背景色,仅用于开发测试
}
}
```
当前标题栏中子组件的布局同预期还有些差异,接下来通过margin属性,设置各个元素的左右间距(详见[尺寸设置](../../reference/arkui-ts/ts-universal-attributes-size.md))。如下图所示,最终顶部工具栏在默认设备和平板上都可以达到预期显示效果。
| 默认设备 | 平板 |
| -------- | -------- |
| ![zh-cn_image_0000001293015965](figures/zh-cn_image_0000001293015965.png) | ![zh-cn_image_0000001246656958](figures/zh-cn_image_0000001246656958.png) |
```
@Component
struct TopArea {
build() {
Flex({ alignItems: ItemAlign.Center }) {
Image($r('app.media.back'))
.width(24)
.height(24)
.margin({ left:24 }) // 设置间距
Image($r('app.media.contact'))
.width(40)
.height(40)
.margin({ left:16, right:16 }) // 设置间距
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center,
alignItems: ItemAlign.Start}) {
Text('张三').fontSize(16).fontColor("#182431")
Text('+123 4567 8901').fontSize(14).fontColor("#66182431")
}
Blank()
Image($r("app.media.call"))
.width(24)
.height(24)
Image($r('app.media.dots'))
.width(24)
.height(24)
.margin({ left:16, right:24 }) // 设置间距
}
.width('100%')
.height(56)
.backgroundColor('#87CEFA') // 顶部标题栏背景色,仅用于开发测试
}
}
```
# 总结
短信应用在默认设备和平板上的功能完全相同,因此选择了部署模型A。借助方舟开发框架一多能力,短信应用实现了在默认设备和平板上共用同一份代码,同时自然也共用安装包。
在实际开发过程中,会话详情页面需要从底层做数据交互,同时还要支持信息选择、信息删除、信息发送状态、输入框与输入法联动等等功能,会比本小节中介绍的基础版本复杂很多。读者如果对这部分感兴趣,可以访问[短信应用开源代码仓](https://gitee.com/openharmony/applications_mms/tree/master),了解会话详情页面的详细实现。
# 从一个例子开始
本章通过一个天气应用,介绍一多应用的整体开发过程,包括UX设计、工程管理及调试、页面开发等。
## UX设计
本示例中的天气应用包含主页、管理城市和添加城市三个页面,其中主页中又包含菜单和更新间隔两个弹窗,基本业务逻辑如下所示。
![image-20220820153523548](figures/image-20220820153523548.png)
“一多”建议从最初的设计阶段开始就拉通多设备综合考虑。考虑实际智能终端设备种类繁多,设计师无法针对每种具体设备各自出一份UX设计图。“一多”建议从设备屏幕宽度的维度,将设备划分为四大类。设计师只需要针对这四大类设备做设计,而无需关心具体的设备形态。
| 设备类型 | 屏幕宽度(vp) |
| -------- | -------- |
| 超小设备 | [0,&nbsp;320) |
| 小设备 | [320,&nbsp;520) |
| 中设备 | [520,&nbsp;840) |
| 大设备 | [840,&nbsp;+∞) |
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> - vp是virtual pixel(虚拟像素)的缩写,是OpenHarmony中常用的长度单位,详见本文[视觉基础](visual-basics.md)小节中的介绍。
>
> - 此处基于设备屏幕宽度划分不同设备是为了读者方便理解。通常智能设备上的应用都是以全屏的形式运行,但随着移动技术的发展,当前部分智能设备支持应用以自由窗口模式运行(即用户可以通过拖拽等操作自由调整应用运行窗口的尺寸),故以应用窗口尺寸为基准进行划分更为合适,本文后续的响应式布局章节中将详细介绍相关内容。
>
> - OpenHarmony当前仅有默认设备和平板两种设备形态,IDE在创建OpenHarmony工程时也仅可以选择默认设备和平板。随着OpenHarmony的演进,其支持的设备形态会不断丰富,本文也会定期刷新相关介绍。
默认设备和平板对应于小设备、中设备及大设备,本示例以这三类设备场景为例,介绍不同设备上的UX设计。天气主页在不同设备上的设计图如下所示。
| | 小设备 | 中设备 | 大设备 |
| -------- | -------- | -------- | -------- |
| 主页 | ![zh-cn_image_0000001334020938](figures/zh-cn_image_0000001334020938.png) | ![zh-cn_image_0000001385380457](figures/zh-cn_image_0000001385380457.jpg) | ![zh-cn_image_0000001348546856](figures/zh-cn_image_0000001348546856.jpg) |
另外,大设备中天气主页还允许用户开启或者隐藏侧边栏。
| 开启侧边栏 | 隐藏侧边栏 |
| -------- | -------- |
| ![zh-cn_image_0000001334340566](figures/zh-cn_image_0000001334340566.jpg) | ![zh-cn_image_0000001385100509](figures/zh-cn_image_0000001385100509.jpg) |
从天气应用在各设备上的UX设计图中,可以观察到如下UX的一些“规律”:
- 在不同的屏幕宽度下,应用的整体风格基本保持一致。
- 在相近的屏幕宽度范围内,应用的布局基本不变;在不同的屏幕宽度范围内,应用的布局有较大差异。
- 应用在小屏幕下显示的元素,是大屏幕中显示元素的子集。
- 考虑到屏幕尺寸及显示效果,大屏幕中可以显示的元素数量一定不少于小屏幕。
- 为充分利用屏幕尺寸优势,大屏幕可以有其独有的元素或设计(如本示例中的侧边栏)。
如此,既在各设备上体现了UX的一致性,也在各设备上体现了UX的差异性,从而既可以保障各设备上应用界面的体验,也可以最大程度复用界面代码。
在本文[应用UX设计章节](ux-design.md)中,将详细介绍应用的UX设计规则。
## 工程管理及调试
在本文[IDE使用章节](ide-using.md)中,将详细介绍一多的工程创建及管理等,本小节仅介绍最基础的工程创建及多设备预览调试。
### 工程创建
一多应用的工程创建过程,与传统应用并无较大差异。只需在工程创建过程中,注意在“Device Type”选项中勾选所有该应用期望运行的目标设备类型,保证后续该应用可以在所有目标设备上正确安装即可。
![project](figures/project.png)
### 预览调试
在代码开发过程中,可以开启预览器,并打开“Multi-profile preview”开关,实时观察应用在不同设备下的表现。
![zh-cn_image_0000001384621049](figures/zh-cn_image_0000001384621049.jpg)
特别的,还可以点击“+ New Profile”按钮,新增自定义预览器。
![previewer](figures/previewer.jpg)
## 页面开发
天气应用中涉及较多的页面和弹窗,本小节以天气主页为例,简单介绍不同设备下的页面实现思路。天气应用已经在[OpenHarmony应用示例](https://gitee.com/openharmony/applications_app_samples/tree/master/MultiDeviceAppDev/Weather)中开源,感兴趣的读者可以自行下载及了解详细代码实现。
观察天气主页在不同设备上的UX设计图,可以进行如下设计:
- 将天气主页划分为9个基础区域,如:
![home_full](figures/home_full.png)
- 基础区域9仅在大设备上显示,基础区域1-8虽然在各设备上始终展示但其尺寸及区域内的布局基本保持不变,可以结合[自适应布局](adaptive-layout.md)能力以[自定义组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/ui/ts-component-based-component.md)的形式分别实现这9个基础区域。
| | 小设备 | 中设备 | 大设备 |
| -------- | -------- | -------- | -------- |
| 主页 | ![Home_sm](figures/Home_sm.png) | ![Home_md_mark](figures/Home_md_mark.png) | ![Home_lg_mark](figures/Home_lg_mark.png) |
- 基础区域1-8之间的布局在不同设备上有较大差异,可以使用响应式布局中的[栅格布局](responsive-layout.md#栅格布局)能力实现组件间的布局效果。
- 展开和隐藏侧边栏的功能可以通过[侧边栏组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-sidebarcontainer.md)来实现。侧边栏是大设备上独有的,借助响应式布局中的[媒体查询](responsive-layout.md#媒体查询)能力,控制仅在大设备上展示侧边栏即可。
### 主页基础区域
天气主页中的9个基础区域介绍及实现方案如下表所示。
| 编号 | 简介 | 实现方案 |
| -------- | -------- | -------- |
| 1 | 标题栏 | 自适应布局拉伸能力 |
| 2 | 天气概览 | Row和Column组件,并指定其子组件按照主轴起始方向对齐或居中对齐。 |
| 3 | 每小时天气 | 自适应布局延伸能力 |
| 4 | 每日天气 | 自适应布局延伸能力 |
| 5 | 空气质量 | Canvas画布组件绘制空气质量图,并使用Row组件和Column组件控制内部元素的布局。 |
| 6 | 生活指数 | 自适应布局均分能力 |
| 7 | 日出日落 | Canvas画布组件绘制日出日落图 |
| 8 | 应用信息 | Row和Column组件,并指定其子组件居中对齐。 |
| 9 | 侧边导航栏 | 综合运用自适应布局中的拉伸能力、占比能力和延伸能力 |
天气主页涉及的内容较多,因篇幅限制,本小节仅介绍区域3(每小时天气)的实现,读者可以自行查看开源代码,了解其它基础区域的实现。
延伸能力是指容器组件内的子组件,按照其在列表中的先后顺序,随容器组件尺寸变化显示或隐藏。随着可用显示区域的增加,用户可以看到的“每小时天气”信息也不断增加,故“每小时天气”可以通过延伸能力实现,其核心代码如下所示。
```
@Component
export default struct HoursWeather {
...
build() {
// 通过列表组件实现延伸能力
List() {
LazyForEach(this.hoursDataResource, (hoursItem) => {
ListItem() {
// 具体每个小时的天气情况
Column() { ... }
}
}, item => `${item.key}`)
}
.height(Style.CARD_HEIGHT)
.borderRadius(Style.NORMAL_RADIUS)
.backgroundColor(Style.CARD_BACKGROUND_COLOR)
// 将列表方向设置为水平方向
.listDirection(Axis.Horizontal)
}
}
```
### 城市天气详情
天气主页右侧的城市天气详情由区域1-8组成,区域1(标题栏)始终固定在页面顶部,区域2-8在不同设备下的布局不同且可以随页面上下滚动。本小节介绍如何实现城市天气详情中区域2~8的布局效果。
设备屏幕可能无法一次性显示区域2-8的所有内容,故需要在外层增加滚动组件(即Scroll组件)以支持上下滚动。不同设备下区域2-8的相对位置一共有三套不同的布局,可以借助响应式布局中的[栅格布局](responsive-layout.md#栅格布局)实现这一效果。本示例中将栅格在不同场景下分别划分为4列、8列和12列,区域2-8在不同场景下的布局如下表所示。
| 小设备 | 中设备&nbsp;&nbsp;大设备(侧边栏显示状态) | 大设备(侧边栏隐藏状态) |
| -------- | -------- | -------- |
| ![zh-cn_image_0000001395631821](figures/zh-cn_image_0000001395631821.png) | ![zh-cn_image_0000001345312038](figures/zh-cn_image_0000001345312038.png) | ![zh-cn_image_0000001349454550](figures/zh-cn_image_0000001349454550.png) |
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 为提升用户体验,大设备侧边栏隐藏状态下,每日天气与空气质量的相对顺序发生了改变。可以调整通过GridCol栅格子组件的order属性,实现目标效果。
```
@Component
export default struct HomeContent {
...
build() {
// 支持滚动
Scroll() {
GridRow({
columns: { sm: 4, md: 8, lg: this.showSideBar ? 8 : 12 },
gutter: { x: Style.GRID_GUTTER, y: Style.GRID_GUTTER },
breakpoints: { reference: BreakpointsReference.WindowSize } }) {
// 天气概览
GridCol({ span: { sm: 4, md: 8, lg: this.showSideBar ? 8 : 12 }, order: 1 }) {
IndexHeader({ headerDate: this.cityListData.header, index: this.index })
.opacity(this.headerOpacity)
}
// 每小时天气
GridCol({ span: { sm: 4, md: 8, lg: 8 }, order: 2 }) {
HoursWeather({ hoursData: this.cityListData.hoursData })
}
// 每日天气
GridCol({ span: 4, order: {sm: 3, md: 3, lg: this.showSideBar ? 3 : 4} }) {
MultidayWeather({ weekData: this.cityListData.weekData })
}
// 空气质量
GridCol({ span: 4, order: {sm: 4, md: 4, lg: this.showSideBar ? 4 : 3} }) {
AirQuality({ airData: this.cityListData.airData, airIndexData: this.cityListData.airIndex })
}
// 生活指数
GridCol({ span: 4, order: 5 }) {
LifeIndex({ lifeData: this.cityListData.suitDate })
}
// 日出日落
GridCol({ span: 4, order: 6 }) {
SunCanvas()
}
// 应用信息
GridCol({ span: { sm: 4, md: 8, lg: this.showSideBar ? 8 : 12 }, order: 7 }) {
IndexEnd()
}
}
}
.width('100%')
}
}
```
### 主页整体实现
综合考虑各设备下的效果,天气主页的根节点使用侧边栏组件:
- 小设备和中设备既不展示侧边栏,也不提供控制侧边栏显示和隐藏的按钮。
- 大设备默认展示侧边栏,同时提供控制侧边栏显示和隐藏的按钮。
另外主页右侧的城市天气详情,支持左右滑动切换城市,可以使用Swiper组件实现目标效果。
- 小设备和中设备开启Swiper组件的导航点,引导用户通过左右滑动切换不同城市。
- 大设备中用户通过点击侧边栏中的城市列表即可高效的切换不同城市,此时需要关闭Swiper组件的导航点。
```
@Entry
@Component
struct Home {
...
build() {
SideBarContainer(SideBarContainerType.Embed) {
// 左侧侧边栏
SideContent({ showSideBar: $showSideBar })
// 右侧内容区
Flex({direction: FlexDirection.Column}) {
// 基础区域1标题栏
IndexTitleBar({ curBp: this.curBp, showSideBar: $showSideBar })
.height(56)
// 天气详情,通过Swiper组件实现左右滑动切换城市的效果
Swiper() {
ForEach(this.cityListWeatherData, (item, index) => {
HomeContent({ showSideBar: this.showSideBar, cityListData: item, index: index })
}, item => item.city)
}
// 大设备关闭导航点
.indicator(this.curBp !== 'lg')
.width('100%')
}
}
.height('100%')
.sideBarWidth('33.3%')
// 通过状态变量,控制不同设备下侧边栏的显隐状态
.showSideBar(this.showSideBar)
}
}
```
最终,天气首页的运行效果如下图所示。
| 小设备 | 中设备 | 大设备(隐藏侧边栏) | 大设备(显示侧边栏) |
| -------- | -------- | -------- | -------- |
| ![zh-cn_image_0000001344993822](figures/zh-cn_image_0000001344993822.jpg) | ![zh-cn_image_0000001345473362](figures/zh-cn_image_0000001345473362.jpg) | ![zh-cn_image_0000001400662385](figures/zh-cn_image_0000001400662385.png) | ![zh-cn_image_0000001395713305](figures/zh-cn_image_0000001395713305.jpg) |
## 功能开发
应用开发不仅包含应用页面开发,还包括应用后端功能开发以及服务器端开发等。服务器端开发不在本文的讨论范围内,本小节仅介绍多设备上应用功能开发的注意事项。
如前文所示,本示例的目标运行设备是小设备、中设备和大设备,对应实际的设备类型为默认设备和平板等。这些设备运行的都是标准系统,其系统能力一致,所以无需做特别考虑。但是在超小设备(对应的实际设备类型为智能穿戴设备等)上,考虑CPU、内存、硬盘等硬件限制,往往会对系统进行裁剪。如果在应用后端功能开发时调用当前系统没有的能力,就可能会引发异常。
通常有两种方式解决上述问题:
- 在应用安装包中描述其需要的系统能力,保证本应用仅被分发和安装到可以满足其诉求的系统中。
- 在使用特定系统能力前,通过canIUse接口判断系统能力是否存在,进而执行不同的逻辑。
在本文的[功能开发的一多能力介绍](development-intro.md)章节中,将详细展开介绍。
# 典型布局场景
虽然不同应用的页面千变万化,但对其进行拆分和分析,页面中的很多布局场景是相似的。本小节将介绍如何借助自适应布局、响应式布局以及常见的容器类组件,实现应用中的典型布局场景。
| 布局场景 | 实现方案 |
| -------- | -------- |
| [页签栏](#页签栏) | Tab组件&nbsp;+&nbsp;响应式布局 |
| [运营横幅(Banner)](#运营横幅banner) | Swiper组件&nbsp;+&nbsp;响应式布局 |
| [网格](#网格) | Grid组件&nbsp;/&nbsp;List组件&nbsp;+&nbsp;响应式布局 |
| [侧边栏](#侧边栏) | SiderBar组件&nbsp;+&nbsp;响应式布局 |
| [大图浏览](#大图浏览) | Image组件 |
| [操作入口](#操作入口) | Scroll组件+Row组件横向均分 |
| [顶部](#顶部) | 栅格组件 |
| [缩进布局](#缩进布局) | 栅格组件 |
| [挪移布局](#挪移布局) | 栅格组件 |
| [重复布局](#重复布局) | 栅格组件 |
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 在本文[媒体查询](responsive-layout.md#媒体查询)小节中已经介绍了如何通过媒体查询监听断点变化,后续的示例中不再重复介绍此部分代码。
## 页签栏
**布局效果**
| sm | md | lg |
| -------- | -------- | -------- |
| 页签在底部<br/>页签的图标和文字垂直布局<br/>页签宽度均分<br/>页签高度固定72vp | 页签在底部<br/>页签的图标和文字水平布局<br/>页签宽度均分<br/>页签高度固定56vp | 页签在左边<br/>页签的图标和文字垂直布局<br/>页签宽度固定96vp<br/>页签高度总占比‘60%’后均分 |
| ![页签布局手机](figures/页签布局手机.png) | ![页签布局折叠屏](figures/页签布局折叠屏.png) | ![页签布局matePadPro](figures/页签布局matePadPro.png) |
**实现方案**
不同断点下,页签在页面中的位置及尺寸都有差异,可以结合响应式布局能力,设置不同断点下[Tab组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-tabs.md)的barPosition、vertical、barWidth和barHeight属性实现目标效果。
另外,页签栏中的文字和图片的相对位置不同,同样可以通过设置不同断点下[tabBar](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-tabcontent.md#%E5%B1%9E%E6%80%A7)对应的CustomBuilder中的布局方向,实现目标效果。
**参考代码**
```
import { BreakpointSystem, BreakPointType } from 'common/BreakpointSystem'
type TabBar = {
name: string
icon: Resource
selectIcon: Resource
}
@Entry
@Component
struct Home {
@State currentIndex: number = 0
@State tabs: Array<TabBar> = [{
name: '首页',
icon: $r('app.media.ic_music_home'),
selectIcon: $r('app.media.ic_music_home_selected')
}, {
name: '排行榜',
icon: $r('app.media.ic_music_ranking'),
selectIcon: $r('app.media.ic_music_ranking_selected')
}, {
name: '我的',
icon: $r('app.media.ic_music_me_nor'),
selectIcon: $r('app.media.ic_music_me_selected')
}]
@Builder TabBarBuilder(index: number, tabBar: TabBar) {
Flex({
direction: new BreakPointType({
sm: FlexDirection.Column,
md: FlexDirection.Row,
lg: FlexDirection.Column
}).getValue(this.currentBreakpoint),
justifyContent: FlexAlign.Center,
alignItems: ItemAlign.Center
}) {
Image(this.currentIndex === index ? tabBar.selectIcon : tabBar.icon)
.size({ width: 36, height: 36 })
Text(tabBar.name)
.fontColor(this.currentIndex === index ? '#FF1948' : '#999')
.margin(new BreakPointType({
sm: { top: 4 },
md: { left: 8 },
lg: { top: 4 } }).getValue(this.currentBreakpoint))
.fontSize(16)
}
.width('100%')
.height('100%')
}
@StorageLink('currentBreakpoint') currentBreakpoint: string = 'md'
private breakpointSystem: BreakpointSystem = new BreakpointSystem()
aboutToAppear() {
this.breakpointSystem.register()
}
aboutToDisappear() {
this.breakpointSystem.unregister()
}
build() {
Tabs({
barPosition: new BreakPointType({
sm: BarPosition.End,
md: BarPosition.End,
lg: BarPosition.Start
}).getValue(this.currentBreakpoint)
}) {
ForEach(this.tabs, (item, index) => {
TabContent() {
Stack() {
Text(item.name).fontSize(30)
}.width('100%').height('100%')
}.tabBar(this.TabBarBuilder(index, item))
})
}
.vertical(new BreakPointType({ sm: false, md: false, lg: true }).getValue(this.currentBreakpoint))
.barWidth(new BreakPointType({ sm: '100%', md: '100%', lg: '96vp' }).getValue(this.currentBreakpoint))
.barHeight(new BreakPointType({ sm: '72vp', md: '56vp', lg: '60%' }).getValue(this.currentBreakpoint))
.animationDuration(0)
.onChange((index: number) => {
this.currentIndex = index
})
}
}
```
## 运营横幅(Banner)
**布局效果**
| sm | md | lg |
| -------- | -------- | -------- |
| 展示一个内容项 | 展示两个内容项 | 展示三个内容项 |
| ![banner1](figures/banner1.PNG) | ![banner2](figures/banner2.PNG) | ![banner3](figures/banner3.PNG) |
**实现方案**
运营横幅通常使用[Swiper组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-swiper.md)实现。不同断点下,运营横幅中展示的图片数量不同。只需要结合响应式布局,配置不同断点下Swiper组件的displayCount属性,即可实现目标效果。
**参考代码**
```
import { BreakpointSystem, BreakPointType } from 'common/BreakpointSystem'
@Entry
@Component
export default struct Banner {
private data: Array<Resource> = [
$r('app.media.banner1'),
$r('app.media.banner2'),
$r('app.media.banner3'),
$r('app.media.banner4'),
$r('app.media.banner5'),
$r('app.media.banner6'),
]
private breakpointSystem: BreakpointSystem = new BreakpointSystem()
@StorageProp('currentBreakpoint') currentBreakpoint: string = 'md'
aboutToAppear() {
this.breakpointSystem.register()
}
aboutToDisappear() {
this.breakpointSystem.unregister()
}
build() {
Swiper() {
ForEach(this.data, (item) => {
Image(item)
.size({ width: '100%', height: 200 })
.borderRadius(12)
.padding(8)
})
}
.indicator(new BreakPointType({ sm: true, md: false, lg: false }).getValue(this.currentBreakpoint))
.displayCount(new BreakPointType({ sm: 1, md: 2, lg: 3 }).getValue(this.currentBreakpoint))
}
}
```
## 网格
**布局效果**
| sm | md | lg |
| -------- | -------- | -------- |
| 展示两列 | 展示四列 | 展示六列 |
| ![多列列表手机](figures/多列列表手机.png) | ![多列列表折叠屏](figures/多列列表折叠屏.png) | ![多列列表matePadPro](figures/多列列表matePadPro.png) |
**实现方案**
不同断点下,页面中图片的排布不同,此场景可以通过响应式布局能力结合[Grid组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-grid.md)实现,通过调整不同断点下的Grid组件的columnsTemplate属性即可实现目标效果。
另外,由于本例中各列的宽度相同,也可以通过响应式布局能力结合[List组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-list.md)实现,通过调整不同断点下的List组件的lanes属性也可实现目标效果。
**参考代码**
通过Grid组件实现
```
import { BreakpointSystem, BreakPointType } from 'common/breakpointsystem'
type GridItemInfo = {
name: string
image: Resource
}
@Entry
@Component
struct MultiLaneList {
private data: GridItemInfo[] = [
{ name: '歌单集合1', image: $r('app.media.1') },
{ name: '歌单集合2', image: $r('app.media.2') },
{ name: '歌单集合3', image: $r('app.media.3') },
{ name: '歌单集合4', image: $r('app.media.4') },
{ name: '歌单集合5', image: $r('app.media.5') },
{ name: '歌单集合6', image: $r('app.media.6') },
{ name: '歌单集合7', image: $r('app.media.7') },
{ name: '歌单集合8', image: $r('app.media.8') },
{ name: '歌单集合9', image: $r('app.media.9') },
{ name: '歌单集合10', image: $r('app.media.10') },
{ name: '歌单集合11', image: $r('app.media.11') },
{ name: '歌单集合12', image: $r('app.media.12') }
]
private breakpointSystem: BreakpointSystem = new BreakpointSystem()
@StorageProp('currentBreakpoint') currentBreakpoint: string = 'md'
aboutToAppear() {
this.breakpointSystem.register()
}
aboutToDisappear() {
this.breakpointSystem.unregister()
}
build() {
Grid() {
ForEach(this.data, (item: GridItemInfo) => {
GridItem() {
Column() {
Image(item.image)
.aspectRatio(1.8)
Text(item.name)
.margin({ top: 8 })
.fontSize(20)
}.padding(4)
}
})
}
.columnsTemplate(new BreakPointType({
sm: '1fr 1fr',
md: '1fr 1fr 1fr 1fr',
lg: '1fr 1fr 1fr 1fr 1fr 1fr'
}).GetValue(this.currentBreakpoint))
}
}
```
通过List组件实现
```
import { BreakpointSystem, BreakPointType } from 'common/BreakpointSystem'
type ListItemInfo = {
name: string
image: Resource
}
@Entry
@Component
struct MultiLaneList {
private data: ListItemInfo[] = [
{ name: '歌单集合1', image: $r('app.media.1') },
{ name: '歌单集合2', image: $r('app.media.2') },
{ name: '歌单集合3', image: $r('app.media.3') },
{ name: '歌单集合4', image: $r('app.media.4') },
{ name: '歌单集合5', image: $r('app.media.5') },
{ name: '歌单集合6', image: $r('app.media.6') },
{ name: '歌单集合7', image: $r('app.media.7') },
{ name: '歌单集合8', image: $r('app.media.8') },
{ name: '歌单集合9', image: $r('app.media.9') },
{ name: '歌单集合10', image: $r('app.media.10') },
{ name: '歌单集合11', image: $r('app.media.11') },
{ name: '歌单集合12', image: $r('app.media.12') }
]
private breakpointSystem: BreakpointSystem = new BreakpointSystem()
@StorageProp('currentBreakpoint') currentBreakpoint: string = 'md'
aboutToAppear() {
this.breakpointSystem.register()
}
aboutToDisappear() {
this.breakpointSystem.unregister()
}
build() {
List() {
ForEach(this.data, (item: ListItemInfo) => {
ListItem() {
Column() {
Image(item.image)
Text(item.name)
.margin({ top: 8 })
.fontSize(20)
}.padding(4)
}
})
}
.lanes(new BreakPointType({ sm: 2, md: 4, lg: 6 }).getValue(this.currentBreakpoint))
.width('100%')
}
}
```
## 侧边栏
**布局效果**
| sm | md | lg |
| -------- | -------- | -------- |
| 默认隐藏侧边栏,同时提供侧边栏控制按钮,用户可以通过按钮控制侧边栏显示或隐藏。 | 始终显示侧边栏,不提供控制按钮,用户无法隐藏侧边栏。 | 始终显示侧边栏,不提供控制按钮,用户无法隐藏侧边栏。 |
| ![sm](figures/sm.png) | ![md](figures/md.png) | ![lg](figures/lg.png) |
**实现方案**
侧边栏通常通过[SideBarContainer组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-sidebarcontainer.md)实现,结合响应式布局能力,在不同断点下为SiderBarConContainer组件的sideBarWidth、showControlButton等属性配置不同的值,即可实现目标效果。
**参考代码**
```
@Entry
@Component
struct SideBarSample {
@StorageLink('currentBreakpoint') private currentBreakpoint: string = "md";
private breakpointSystem: BreakpointSystem = new BreakpointSystem()
@State showSideBar: boolean = false
@State selectIndex: number = 0;
aboutToAppear() {
this.breakpointSystem.register()
if (this.currentBreakpoint === 'sm') {
this.showSideBar = false
} else {
this.showSideBar = true
}
}
aboutToDisappear() {
this.breakpointSystem.unregister()
}
@Builder itemBuilder(index: number) {
Text(images[index].label)
.width('100%')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.borderRadius(5)
.margin(20)
.backgroundColor('#ffffff')
.textAlign(TextAlign.Center)
.width(180)
.height(36)
.onClick(() => {
this.selectIndex = index
if (this.currentBreakpoint === 'sm') {
this.showSideBar = false
}
})
}
build() {
SideBarContainer(this.currentBreakpoint === 'sm' ? SideBarContainerType.Overlay : SideBarContainerType.Embed) {
Column() {
this.itemBuilder(0)
this.itemBuilder(1)
}.backgroundColor('#F1F3F5')
.justifyContent(FlexAlign.Center)
Column() {
Image(images[this.selectIndex].imageSrc)
.objectFit(ImageFit.Contain)
.height(300)
.width(300)
}
.justifyContent(FlexAlign.Center)
.width('100%')
.height('100%')
}
.height('100%')
.sideBarWidth(this.currentBreakpoint === 'sm' ? '100%' : '33.33%')
.minSideBarWidth(this.currentBreakpoint === 'sm' ? '100%' : '33.33%')
.maxSideBarWidth(this.currentBreakpoint === 'sm' ? '100%' : '33.33%')
.showControlButton(this.currentBreakpoint === 'sm')
.autoHide(false)
.showSideBar(this.showSideBar)
.onChange((isBarShow: boolean) => {
this.showSideBar = isBarShow
})
}
}
```
## 大图浏览
**布局效果**
| sm | md | lg |
| -------- | -------- | -------- |
| 图片长宽比不变,最长边充满全屏 | 图片长宽比不变,最长边充满全屏 | 图片长宽比不变,最长边充满全屏 |
| ![大图浏览手机](figures/大图浏览手机.png) | ![大图浏览折叠屏](figures/大图浏览折叠屏.png) | ![大图浏览matePadPRo](figures/大图浏览matePadPRo.png) |
**实现方案**
图片通常使用[Image组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-basic-components-image.md)展示,Image组件的objectFit属性默认为ImageFit.Cover,即保持宽高比进行缩小或者放大以使得图片两边都大于或等于显示边界。在大图浏览场景下,因屏幕与图片的宽高比可能有差异,常常会发生图片被截断的问题。此时只需将Image组件的objectFit属性设置为ImageFit.Contain,即保持宽高比进行缩小或者放大并使得图片完全显示在显示边界内,即可解决该问题。
**参考代码**
```
@Entry
@Component
struct BigImage {
build() {
Row() {
Image($r("app.media.image"))
.objectFit(ImageFit.Contain)
}
}
}
```
## 操作入口
**布局效果**
| sm | md | lg |
| -------- | -------- | -------- |
| 列表项尺寸固定,超出内容可滚动查看 | 列表项尺寸固定,剩余空间均分 | 列表项尺寸固定,剩余空间均分 |
| ![操作入口手机](figures/操作入口手机.png) | ![操作入口折叠屏](figures/操作入口折叠屏.png) | ![操作入口matePadPro](figures/操作入口matePadPro.png) |
**实现方案**
Scroll(内容超出宽度时可滚动) + Row(横向均分:justifyContent(FlexAlign.SpaceAround)、 最小宽度约束:constraintSize({ minWidth: '100%' })
**参考代码**
```
type OperationItem = {
name: string
icon: Resource
}
@Entry
@Component
export default struct OperationEntries {
@State listData: Array<OperationItem> = [
{ name: '私人FM', icon: $r('app.media.self_fm') },
{ name: '歌手', icon: $r('app.media.singer') },
{ name: '歌单', icon: $r('app.media.song_list') },
{ name: '排行榜', icon: $r('app.media.rank') },
{ name: '热门', icon: $r('app.media.hot') },
{ name: '运动音乐', icon: $r('app.media.sport') },
{ name: '音乐FM', icon: $r('app.media.audio_fm') },
{ name: '福利', icon: $r('app.media.bonus') }]
build() {
Scroll() {
Row() {
ForEach(this.listData, item => {
Column() {
Image(item.icon)
.width(48)
.aspectRatio(1)
Text(item.name)
.margin({ top: 8 })
.fontSize(16)
}
.justifyContent(FlexAlign.Center)
.height(104)
.padding({ left: 12, right: 12 })
})
}
.constraintSize({ minWidth: '100%' }).justifyContent(FlexAlign.SpaceAround)
}
.width('100%')
.scrollable(ScrollDirection.Horizontal)
}
}
```
## 顶部
**布局效果**
| sm | md | lg |
| -------- | -------- | -------- |
| 标题和搜索框两行显示 | 标题和搜索框一行显示 | 标题和搜索框一行显示 |
| ![顶部布局手机](figures/顶部布局手机.png) | ![顶部布局折叠屏](figures/顶部布局折叠屏.png) | ![顶部布局matePadPro](figures/顶部布局matePadPro.png) |
**实现方案**
最外层使用栅格行组件GridRow布局
文本标题使用栅格列组件GridCol
搜索框使用栅格列组件GridCol
**参考代码**
```
@Entry
@Component
export default struct Header {
@State needWrap: boolean = true
build() {
GridRow() {
GridCol({ span: { sm: 12, md: 6, lg: 7 } }) {
Row() {
Text('推荐').fontSize(24)
Blank()
Image($r('app.media.ic_public_more'))
.width(32)
.height(32)
.objectFit(ImageFit.Contain)
.visibility(this.needWrap ? Visibility.Visible : Visibility.None)
}
.width('100%').height(40)
.alignItems(VerticalAlign.Center)
}
GridCol({ span: { sm: 12, md: 6, lg: 5 } }) {
Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {
Search({ placeholder: '猜您喜欢: 万水千山' })
.placeholderFont({ size: 16 })
.margin({ top: 4, bottom: 4 })
Image($r('app.media.audio_fm'))
.width(32)
.height(32)
.objectFit(ImageFit.Contain)
.flexShrink(0)
.margin({ left: 12 })
Image($r('app.media.ic_public_more'))
.width(32)
.height(32)
.objectFit(ImageFit.Contain)
.flexShrink(0)
.margin({ left: 12 })
.visibility(this.needWrap ? Visibility.None : Visibility.Visible)
}
}
}.onBreakpointChange((breakpoint: string) => {
if (breakpoint === 'sm') {
this.needWrap = true
} else {
this.needWrap = false
}
})
.padding({ left: 12, right: 12 })
}
}
```
## 缩进布局
**布局效果**
| sm | md | lg |
| -------- | -------- | -------- |
| 栅格总列数为4,内容占满所有列 | 栅格总列数为8,内容占中间6列。 | 栅格总列数为12,内容占中间8列。 |
| ![indent_sm](figures/indent_sm.jpg) | ![indent_md](figures/indent_md.jpg) | ![indent_lg](figures/indent_lg.jpg) |
**实现方案**
借助[栅格组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-gridrow.md),控制待显示内容在不同的断点下占据不同的列数,即可实现不同设备上的缩进效果。另外还可以调整不同断点下栅格组件与两侧的间距,获得更好的显示效果。
**参考代码**
```
@Entry
@Component
struct IndentationSample {
@State private gridMargin: number = 24
build() {
Row() {
GridRow({columns: {sm: 4, md: 8, lg: 12}, gutter: 24}) {
GridCol({span: {sm: 4, md: 6, lg: 8}, offset: {md: 1, lg: 2}}) {
Column() {
ForEach([0, 1, 2, 4], () => {
Column() {
ItemContent()
}
})
}.width('100%')
}
}
.margin({left: this.gridMargin, right: this.gridMargin})
.onBreakpointChange((breakpoint: string) => {
if (breakpoint === 'lg') {
this.gridMargin = 48
} else if (breakpoint === 'md') {
this.gridMargin = 32
} else {
this.gridMargin = 24
}
})
}
.height('100%')
.alignItems((VerticalAlign.Center))
.backgroundColor('#F1F3f5')
}
}
@Component
struct ItemContent {
build() {
Column() {
Row() {
Row() {
}
.width(28)
.height(28)
.borderRadius(14)
.margin({ right: 15 })
.backgroundColor('#E4E6E8')
Row() {
}
.width('30%').height(20).borderRadius(4)
.backgroundColor('#E4E6E8')
}.width('100%').height(28)
Row() {
}
.width('100%')
.height(68)
.borderRadius(16)
.margin({ top: 12 })
.backgroundColor('#E4E6E8')
}
.height(128)
.borderRadius(24)
.backgroundColor('#FFFFFF')
.padding({ top: 12, bottom: 12, left: 18, right: 18 })
.margin({ bottom: 12 })
}
}
```
## 挪移布局
**布局效果**
| sm | md | lg |
| -------- | -------- | -------- |
| 图片和文字上下布局 | 图片和文字左右布局 | 图片和文字左右布局 |
| ![diversion_sm](figures/diversion_sm.jpg) | ![diversion_md](figures/diversion_md.jpg) | ![diversion_lg](figures/diversion_lg.jpg) |
**实现方案**
不同断点下,栅格子元素占据的列数会随着开发者的配置发生改变。当一行中的列数超过栅格组件在该断点的总列数时,可以自动换行,即实现”上下布局”与”左右布局”之间切换的效果。
**参考代码**
```
@Entry
@Component
struct DiversionSample {
@State private currentBreakpoint: string = 'md'
@State private imageHeight: number = 0
build() {
Row() {
GridRow() {
GridCol({span: {sm: 12, md: 6, lg: 6}}) {
Image($r('app.media.illustrator'))
.aspectRatio(1)
.onAreaChange((oldValue: Area, newValue: Area) => {
this.imageHeight = Number(newValue.height)
})
.margin({left: 12, right: 12})
}
GridCol({span: {sm: 12, md: 6, lg: 6}}) {
Column(){
Text($r('app.string.user_improvement'))
.textAlign(TextAlign.Center)
.fontSize(20)
.fontWeight(FontWeight.Medium)
Text($r('app.string.user_improvement_tips'))
.textAlign(TextAlign.Center)
.fontSize(14)
.fontWeight(FontWeight.Medium)
}
.margin({left: 12, right: 12})
.justifyContent(FlexAlign.Center)
.height(this.currentBreakpoint === 'sm' ? 100 : this.imageHeight)
}
}.onBreakpointChange((breakpoint: string) => {
this.currentBreakpoint = breakpoint;
})
}
.height('100%')
.alignItems((VerticalAlign.Center))
.backgroundColor('#F1F3F5')
}
}
```
## 重复布局
**布局效果**
| sm | md | lg |
| -------- | -------- | -------- |
| 单列显示,共8个元素<br>可以通过上下滑动查看不同的元素 | 双列显示,共8个元素 | 双列显示,共8个元素 |
| ![repeat_sm](figures/repeat_sm.jpg) | ![repeat_md](figures/repeat_md.jpg) | ![repeat_lg](figures/repeat_lg.jpg) |
**实现方案**
不同断点下,配置栅格子组件占据不同的列数,即可实现“小屏单列显示、大屏双列显示”的效果。另外,还可以通过栅格组件的onBreakpointChange事件,调整页面中显示的元素数量。
**参考代码**
```
@Entry
@Component
struct RepeatSample {
@State private currentBreakpoint: string = 'md'
@State private listItems: number[] = [1, 2, 3, 4, 5, 6, 7, 8]
@State private gridMargin: number = 24
build() {
Row() {
// 当目标区域不足以显示所有元素时,可以通过上下滑动查看不同的元素
Scroll() {
GridRow({gutter: 24}) {
ForEach(this.listItems, () => {
// 通过配置元素在不同断点下占的列数,实现不同的布局效果
GridCol({span: {sm: 12, md: 6, lg: 6}}) {
Column() {
RepeatItemContent()
}
}
})
}
.margin({left: this.gridMargin, right: this.gridMargin})
.onBreakpointChange((breakpoint: string) => {
this.currentBreakpoint = breakpoint;
if (breakpoint === 'lg') {
this.gridMargin = 48
} else if (breakpoint === 'md') {
this.gridMargin = 32
} else {
this.gridMargin = 24
}
})
}.height(348)
}
.height('100%')
.backgroundColor('#F1F3F5')
}
}
@Component
struct RepeatItemContent {
build() {
Flex() {
Row() {
}
.width(43)
.height(43)
.borderRadius(12)
.backgroundColor('#E4E6E8')
.flexGrow(0)
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Start, justifyContent: FlexAlign.SpaceAround }) {
Row() {
}
.height(10)
.width('80%')
.backgroundColor('#E4E6E8')
Row() {
}
.height(10)
.width('50%')
.backgroundColor('#E4E6E8')
}
.flexGrow(1)
.margin({ left: 13 })
}
.padding({ top: 13, bottom: 13, left: 13, right: 37 })
.height(69)
.backgroundColor('#FFFFFF')
.borderRadius(24)
}
}
```
# 典型页面场景
- **[应用市场首页](appgallery-home-page.md)**
- **[音乐专辑页](music-album-page.md)**
\ No newline at end of file
# 应用UX设计
- **[设计原则和要点](design-principles.md)**
- **[应用架构设计](architecture-design.md)**
- **[界面布局](interface-layout-design.md)**
- **[人机交互](man-machine-interaction.md)**
- **[视觉风格](visual-style.md)**
- **[多态控件](design-polymorphic-controls.md)**
- **[设计自检表](design-checklist.md)**
- **[设计交付](design-delivery.md)**
- **[资源](design-resources.md)**
\ No newline at end of file
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
基于 8vp 为网格的基本单位可以对界面上元素的大小、位置、对齐方式进行更好的规划,构建更有层次感、秩序感,以及多设备上一致的布局效果。一些更小的控件(例如图标)大小也可以对齐 4vp 的网格大小。 基于 8vp 为网格的基本单位可以对界面上元素的大小、位置、对齐方式进行更好的规划,构建更有层次感、秩序感,以及多设备上一致的布局效果。一些更小的控件(例如图标)大小也可以对齐 4vp 的网格大小。
![zh-cn_image_0000001291670681](figures/zh-cn_image_0000001291670681.png) ![8vp](figures/8vp.png)
**字体像素单位:fp** **字体像素单位:fp**
...@@ -40,20 +40,20 @@ ...@@ -40,20 +40,20 @@
分层参数是根据使用场景定义的视觉属性ID,通过在不同色彩主题、多种设备上配置不同的数值,实现多设备适配的效果。OpenHarmony的分层参数包含色彩、字体、圆角、间距、阴影、模糊、缩放,并提供了默认实现。设备、应用、服务均可在此基础上管理并自定义不同场景的视觉属性。 分层参数是根据使用场景定义的视觉属性ID,通过在不同色彩主题、多种设备上配置不同的数值,实现多设备适配的效果。OpenHarmony的分层参数包含色彩、字体、圆角、间距、阴影、模糊、缩放,并提供了默认实现。设备、应用、服务均可在此基础上管理并自定义不同场景的视觉属性。
![zh-cn_image_0000001251153442](figures/zh-cn_image_0000001251153442.png) ![画板 copy](figures/画板copy.png)
例如,对于不同场景的主色调定义了对应的ID与默认实现 例如,对于不同场景的主色调定义了对应的ID与默认实现
| | | | | | 场景 | 色值 | ID |
| -------- | -------- | -------- | -------- | | -------- | -------- | -------- |
| 主色调 | 高亮色 | \#007DFF | ohos_id_color_emphasize | | 高亮色 | \#007DFF | ohos_id_color_emphasize |
| 高亮色反色 | \#006CDE | ohos_id_color_emphasize_contrary | | 高亮色反色 | #006CDE |ohos_id_color_emphasize_contrary|
| 警告色 | \#FA2A2D | ohos_id_color_warning | | 警告色 | \#FA2A2D |ohos_id_color_warning|
| 警示色 | \#FF7500 | ohos_id_color_alert | | 警示色 | \#FF7500 |ohos_id_color_alert|
| 通讯色 | \#E84826 | ohos_id_color_handup | | 通讯色 | \#E84826 |ohos_id_color_handup|
| 通讯色 | \#00CB87 | ohos_id_color_connected | | 通讯色 | \#00CB87 |ohos_id_color_connected|
关于OpenHarmony默认提供的所有分层参数,详见:[资源](resource.md) 关于OpenHarmony默认提供的所有分层参数,详见:[资源](design-resources.md)
...@@ -4,24 +4,20 @@ ...@@ -4,24 +4,20 @@
色彩能够赋予应用界面足够的生动性,并给用户提供不同设备上、不同应用间视觉感官上的连续性。同时,合理地运用色彩可以传达关键的状态信息,给予用户即时的状态反馈以及数据可视化呈现。 色彩能够赋予应用界面足够的生动性,并给用户提供不同设备上、不同应用间视觉感官上的连续性。同时,合理地运用色彩可以传达关键的状态信息,给予用户即时的状态反馈以及数据可视化呈现。
OpenHarmony采用蓝色作为系统的默认主色调。根据人因研究,对蓝色的接受度无论是在男性还是女性群体中,比例都是最高的。而在世界地域维度,蓝色也是最受欢迎的颜色。更重要的是,对于大多数色觉障碍人士,蓝色依然可以被辨识。 OpenHarmony采用蓝色作为系统的默认主色调。根据人因研究,对蓝色的接受度无论是在男性还是女性群体中,比例都是最高的。在世界地域维度,蓝色也是最受欢迎的颜色。更重要的是,对于大多数色觉障碍人士,蓝色依然可以被辨识。
**色值及使用场景** ## 色值及使用场景
在色彩设计上,既保持统一的色彩语言,又根据多端不同的使用场景做了调整,带来定制化用户体验。 在色彩设计上,既保持统一的色彩语言,又根据多端不同的使用场景做了调整,带来定制化用户体验。
例如,高亮色ohos_id_color_activated,在不同设备和色彩模式上会有不同具体的值: 例如,高亮色ohos_id_color_activated,在不同设备和色彩模式上会有不同具体的值:
| | |
| | | | | | -------- | -------- |
| -------- | -------- | -------- | -------- | | ![zh-cn_image_0000001400554657](figures/zh-cn_image_0000001400554657.png)<br/>用于默认设备浅色风格。 | ![zh-cn_image_0000001400874297](figures/zh-cn_image_0000001400874297.png)<br/>用于默认设备深色风格。 |
| ![zh-cn_image_0000001268654109](figures/zh-cn_image_0000001268654109.png)<br/>用于默认设备浅色风格。 | ![zh-cn_image_0000001223973992](figures/zh-cn_image_0000001223973992.png)<br/>用于默认设备深色风格。 | ![zh-cn_image_0000001223973996](figures/zh-cn_image_0000001223973996.png)<br/>用于智慧屏深色风格。 | ![zh-cn_image_0000001224333892](figures/zh-cn_image_0000001224333892.png)<br/>用于智能穿戴深色风格。 | | ![zh-cn_image_0000001350234552](figures/zh-cn_image_0000001350234552.png)<br/>用于智慧屏深色风格。 | ![zh-cn_image_0000001400674189](figures/zh-cn_image_0000001400674189.png)<br/>用于智能穿戴深色风格。 |
OpenHarmony后续将支持深色模式、浅色模式,及不同主题切换能力。 OpenHarmony后续将支持深色模式、浅色模式,及不同主题切换能力。
关于OpenHarmony默认提供的色彩相关分层参数,详见:[资源](design-resources.md)
关于OpenHarmony默认提供的色彩相关分层参数,详见:[资源](resource.md)
...@@ -4,13 +4,10 @@ ...@@ -4,13 +4,10 @@
字体直接影响界面的展示效果和用户的阅读效率。优秀的字形设计、统一的多语言字形风格、正确的字体排版,能有效地提升应用使用体验,并传递品牌连续性。 字体直接影响界面的展示效果和用户的阅读效率。优秀的字形设计、统一的多语言字形风格、正确的字体排版,能有效地提升应用使用体验,并传递品牌连续性。
多设备字号层级 ## 多设备字号层级
选择合适的字号有助于定义内容的信息层级以及增强内容的可读性。通过研究全场景设备的显示环境、用户使用时环境的差异,OpenHarmony结合分层参数为不同设备形态提供了一套构建信息层级的字号系统。
选择合适的字号有助于定义内容的信息层级,以及达到内容的可读性。通过研究全场景设备的显示环境、用户使用时环境的差异,OpenHarmony结合分层参数为不同设备形态提供了一套构建信息层级的字号系统。 ![字体 copy 2](figures/字体copy2.png)
关于OpenHarmony默认提供的字体相关分层参数,详见:[资源](design-resources.md)
![zh-cn_image_0000001292686089](figures/zh-cn_image_0000001292686089.png)
关于OpenHarmony默认提供的字体相关分层参数,详见:[资源](resource.md)
# 图标 # 图标
图标是操作系统与用户界面关键的视觉元素之一。图标应当具备直接识别关键信息或语义的特质,帮助用户轻松辨别图标所代表的含义。为了保证用户在不同的设备中视觉体验的一致性,在图标的设计上应当保持应用图标的元素一致,再根据不同的设备匹配对应的图标背板以适应于各种场景。除此之外,图标在颜色的使用上应当遵循符合人因的色彩规则,满足用户阅读的舒适度以及整体界面的和谐程度。对于面状图标与线状图标的使用也应当遵循系统的设计规则,两种样式使用同一种图形结构,降低用户阅读时再次识别的成本。 图标是操作系统与用户界面关键的视觉元素之一。图标应当具备直接识别关键信息或语义的特质,帮助用户轻松辨别图标所代表的含义。为了保证用户在不同的设备中视觉体验的一致性,在图标的设计上应当保持应用图标的元素一致,再根据不同的设备匹配对应的图标背板以适应于各种场景。除此之外,图标在颜色的使用上应当遵循符合人因的色彩规则,满足用户阅读的舒适度以及整体界面的和谐程度。对于面状图标与线状图标的使用应当遵循系统的设计规则,两种样式使用同一种图形结构,降低用户阅读时再次识别的成本。
![zh-cn_image_0000001268334113](figures/zh-cn_image_0000001268334113.jpg) ![zh-cn_image_0000001268334113](figures/zh-cn_image_0000001268334113.jpg)
......
# 视觉风格
- **[视觉基础](visual-basics.md)**
- **[色彩](visual-style-color.md)**
- **[字体](visual-style-font.md)**
- **[图标](visual-style-icon.md)**
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册