提交 735c2bdf 编写于 作者: F fanzhaonan

add multi-device application development

Signed-off-by: Nfanzhaonan <zhaonanfan@qq.com>
上级 0e299a0f
# 一次开发,多端部署
- [前言](about-this-document.md)
- [简介](introduction.md)
- 应用UX设计
- [设计原则和要点](design-principles.md)
- 应用架构设计
- [应用导航结构设计要求](navigation-design.md)
- [应用页面结构设计](page-design.md)
- 界面布局
- [概述](layout-design-intro.md)
- 布局基础
- [栅格系统](layout-grid.md)
- [自适应布局](layout-adaptive.md)
- [响应式布局](layout-responsive.md)
- [布局基础运用案例](layout-design-cases.md)
- 人机交互
- [交互基础](interaction-basics.md)
- [常见输入方式](common-input-modes.md)
- [交互事件归一](design-interaction-events-unification.md)
- 视觉风格
- [视觉基础](visual-style-basics.md)
- [色彩](visual-style-color.md)
- [字体](visual-style-font.md)
- [图标](visual-style-icon.md)
- [多态控件](design-polymorphic-components.md)
- [设计自检表](design-checklist.md)
- [IDE使用](ide-usage.md)
- 一多能力的页面开发介绍
- 布局能力
- [布局能力简介](layout-intro.md)
- 自适应布局
- [自适应布局简介](adaptive-layout-intro.md)
- [拉伸能力](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)
- 响应式布局
- [栅格断点系统](grid-breakpoint.md)
- [媒体查询](media-query.md)
- [典型场景](responsive-layout-cases.md)
- [交互归一](interaction-events-unification.md)
- [多态组件](polymorphic-components.md)
- [资源使用](resource-usage.md)
- [一多能力的功能开发介绍](development-intro.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设计、业务功能设计)-&gt;工程设计和创建-&gt;功能代码实现。本指导也是基于这个流程进行的内容编排。
阅读本文档时,应尽量按照章节顺序进行阅读。如果区分开发角色,那么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组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/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组件](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组件中子节点的在页面显示时就已经全部完成了布局计算及渲染,只不过受限于父容器尺寸,用户只能看到一部分。随着父容器尺寸增大,用户可以看到的子节点数目也相应的增加。用户还可以通过手指滑动触发列表滑动,查看被隐藏的子节点。
## 示例:
当父容器的尺寸发生改变时,页面中显示的图标数量随之发生改变。
分别通过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属性)来控制显隐,当布局主轴方向剩余尺寸不足以满足全部元素时,按照布局优先级大小,从小到大依次隐藏,直到容器能够完整显示剩余元素。具有相同布局优先级的元素将同时显示或者隐藏。
可以访问[布局约束](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/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) | 容器组件尺寸发生变化时,如果布局方向尺寸不足以显示完整内容,**自动换行**。 |
下面我们依次介绍这几种自适应布局能力。
# 占比能力
占比能力是指子组件的宽高按照预设的比例,随父容器组件发生变化。
占比能力通常有两种实现方式:
- 将子组件的宽高设置为父组件宽高的百分比,详见[尺寸设置](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/ui/ts-types.md#%E9%95%BF%E5%BA%A6%E7%B1%BB%E5%9E%8B)
- 通过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属性设置网格容器中列的数量及其宽度比例,或通过配置子组件在栅格(本章后文将详细介绍栅格系统)中占据不同的列数来实现占比能力。本小节仅介绍最基础和常用的实现方式,局限性较大或比非常小众的实现方式,本文不再展开介绍。
## 示例:
简单的播放控制栏,其中“上一首”、“播放/暂停”、“下一首”的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属性)实现当容器尺寸发生变化时,内容自适应调整。
可以访问[布局约束](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/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布局](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-universal-attributes-flex-layout.md)
- flexGrow:仅当父容器宽度大于所有子组件宽度的总和时,该属性生效。配置了此属性的子组件,按照比例拉伸,分配父容器的多余空间。
- flexShrink:仅当父容器宽度小于所有子组件宽度的总和时,该属性生效。配置了此属性的子组件,按照比例收缩,分配父容器的不足空间。
特别的,当开发者期望将父容器的剩余空间全部分配给某空白区域时,也可以通过Blank组件实现。注意仅当父组件为Row\Column\Flex组件时,Blank组件才会生效,详见[Blank组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/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组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/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')
}
}
```
# 桌面应用
[桌面](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%左右**。在后续拓展其它设备类型时,依然可以使用此部分公共代码。这既可以提高多设备场景开发效率,同时也可以降低后期的维护成本。
# 案例应用
本章将从OpenHarmony预置的系统应用中,选择一些较为典型的案例,从页面开发和工程结构的角度,介绍"一多"的具体实践。OpenHarmony的产品形态在不断丰富中,当前主要有默认设备和平板两种产品形态,本章的具体实践也将围绕这两种产品形态展开。
- **[短信应用](sms-intro.md)**
- **[桌面应用](cases-home-screen.md)**
\ No newline at end of file
# 常见输入方式
## 输入类型
**基于触控的交互**
很多设备都拥有支持多点触控的屏幕,允许用户使用手指和/或手写笔进行交互。它们与屏幕的接触状态、数量以及运动行为被识别成触控手势和操作,可以支持多种交互功能和体验(例如点击、滑动、缩放、旋转)。在多数情况下,应将触控交互作为用户首要的交互方式。
以下是基础的手势操作:
| **手势操作** | **功能描述** | **图示** |
| -------- | -------- | -------- |
| 点击 | 用户通过点击某个元素触发功能或访问界面。 | ![zh-cn_image_0000001291916533](figures/zh-cn_image_0000001291916533.png) |
| 长按 | 用户通过长按某个元素触发菜单或特定模式或进入界面。长按手势发现性差,常用功能不要使用长按来触发。<br/>场景:<br/>-&nbsp;长按操作显示弹出菜单。例如,在联系人列表中长按某个联系人。<br/>-&nbsp;长按操作显示快捷菜单。例如,长按某个桌面图标。<br/>-&nbsp;长按操作进入多选。一般用于列表或宫格界面。例如,长按信息的列表界面,长按图库的宫格界面。<br/>-&nbsp;长按操作进入编辑模式或可排序模式,并伴随振动。例如,长按闹钟的列表界面,长按桌面的空白处。<br/>-&nbsp;长按操作选择文本。例如,在文本框中长按已经输入的文字。<br/>-&nbsp;长按图标进入该图标对应的功能详情。例如,在通知面板上长按功能开关图标。 | ![zh-cn_image_0000001245596752](figures/zh-cn_image_0000001245596752.png) |
| 滑动 | 用户通过滑动来滚动列表或平移界面内容。<br/>场景:<br/>-&nbsp;通过滑动滚动列表。<br/>-&nbsp;在内容区横向滑动切换页签。<br/>-&nbsp;通过滑动平移地图。 | ![zh-cn_image_0000001245437476](figures/zh-cn_image_0000001245437476.png) |
| 拖动 | 用户将元素从一个位置移动到另外一个位置。 | ![zh-cn_image_0000001291557665](figures/zh-cn_image_0000001291557665.png) |
| 双击 | 用户快速点击两下以放大/缩小内容、选择文字或触发特定的功能。 | ![zh-cn_image_0000001245277712](figures/zh-cn_image_0000001245277712.png) |
| 捏合 | 用户使用两个手指按住屏幕向外展开以放大内容。<br/>使用两个手指按住屏幕向内收拢以缩小内容。<br/>场景:<br/>-&nbsp;放大/缩小图片。例如,在查看图片界面。<br/>-&nbsp;放大/缩小内容。例如,浏览页面或者相机取景界面。 | ![zh-cn_image_0000001291677093](figures/zh-cn_image_0000001291677093.png) |
**基于光标的交互**
当用户使用指向设备(鼠标、触摸板、AR/VR手柄、隔空手势等)与应用程序进行间接交互时,光标指向的对象和光标本身应提供适当的视觉反馈以表达对象的可交互性和到达的准确性。同时,应考虑利用光标支持精细化操作和悬浮状态的特性(相比手指触摸),以提升应用生产力、简化交互任务和增强信息展示。
基于光标的交互设计亦可推广至AR/VR手柄和隔空手势等空间交互场景。
**基于焦点的交互**
当用户使用键盘、智慧屏遥控器、车机摇杆/旋钮等非指向性输入方式与应用程序进行间接交互时,基于焦点的导航和交互是重要的输入手段。
## 典型输入方式
**鼠标**
鼠标是一种典型的基于光标的、具备像素级精度的指向型输入方式,最为适用于对用户交互具有较高精度要求的生产力应用和高密度UI的场景。
一般地,鼠标由左键、右键和滚轮键组成,这些按键的交互应遵循业界标准的规范功能和用户的既有使用习惯。鼠标也可以通过和不同的键盘按键进行结合,提供额外的快捷操作体验。
**触控板**
触控板同时具备多指触控手势输入(触屏)和精细化指向型输入(鼠标)的特性,使得触控板既适合用于基于触摸交互优化的用户界面,也适合用于对指点精度有较高要求的生产力应用。
**键盘**
键盘是一种重要的生产力输入方式,优秀的键盘使用体验应允许用户快速准确地进行文本输入、双手无需离开键盘即可在系统和应用内进行导航、访问所有的功能、以及支持无障碍体验。
**手写笔**
在触屏上,手写笔是手指精细化操作的延伸,是一种像素级精度的指点设备。手写笔提供了一种直接的、自然的方式来进行数字内容书写、绘图和标注。
**隔空手势**
隔空手势(非接触手势)是一种人与设备交互的新方式,用户可以在无需手持或接触设备的情况下与设备进行便捷的交互。随着技术的发展,隔空手势在默认设备、平板、车机、智慧屏、音箱、AR/VR等设备上都有一定的应用。通常情况下,隔空手势通常是符合用户直觉、文化习惯或者容易操作的动作。
# 设计自检表
设计自检表详细列举出了在全场景设备设计和开发过程中应当注意的设计规则,这将帮助应用减少用户舆情且提升用户体验的一致性。
自检表的要求范围分为“必选”与“推荐”两类。必选类一般为已总结出的较优解决方案或效果,表示相关设计需要按照此原则统一执行;推荐类指可能受应用品牌风格或业务特殊性影响,可适量做出修改。
请参考以下表格范围内提出的要求对应用进行检查。
| **类型** | **条目** | **说明** |
| -------- | ------------------ | ------------------------------------------------------------ |
| 应用架构 | 导航结构 | 保证同一应用/服务在各设备上导航结构一致。 |
| | 界面框架 | 尽量使用推荐的常用界面架构,以更好达到一多效果。 |
| 布局 | 自适应布局 | 关注布局标注是否逻辑合理,是否具备自适应能力。保证在不同尺寸和分辨率的设备上能够无错位/不截断/不变形地正常显示。 |
| | 响应式布局 | 关注布局是否更有效利用屏幕控件,是否具备响应式能力。保证在不同尺寸和分辨率的设备上不过多空白(50%以上)/不过于拥挤(间距小于16vp,明显截断)。 |
| | 页面结构 | 尽量使用推荐的常用页面结构,以更好达到一多效果。 |
| | 页面结构优化 | 在通用能力不适用业务诉求时,进行页面特殊优化,保证在具体设备上的使用体验。 |
| 人机交互 | 输入方式 | 需保证在各设备上完整支持触摸、鼠标、触控、键盘、遥控器、摇杆等交互方式,并符合标准定义。 |
| | 交互归一 | 应使用系统提供的控件以达到一致的交互体验。如有定制,需保证在各场景下,不同输入设备上的操作与指南要求一致。需特别注意鼠标行为。 |
| 视觉风格 | 单位 | 用于界面布局的单位应全部使用vp。只针对严格控制元素尺寸的场景使用px。 |
| | 色彩 | 用于色彩的赋值应使用分层参数。推荐支持深色模式,需保证界面在系统切换色彩模式时没有识别性问题。 |
| | 字体 | 尽量使用fp为文字大小单位,需要响应系统大字体模式,确保系统调节字体大小后,界面字体能响应变化大小,并且界面布局没有出现布局错乱问题。 |
| 多态控件 | 支持常用的控件状态 | 确保控件不同状态下的视觉效果没有缺失。控件的常用状态有:正常态、不可用态、点击态、获焦态、激活态、悬停态。 |
\ No newline at end of file
# 交互事件归一
本章节描述了在多种交互任务或场景下,应用在触屏上和其它常用的输入方式(例如鼠标、触摸板、键盘)上分别对应的正确的交互规则。**设计师和开发者应保证在当前输入方式下应用能够以正确的、符合用户习惯的交互规则进行响应。**
![zh-cn_image_0000001224333656](figures/zh-cn_image_0000001224333656.png)
## 打开/切换对象
用户通过点击某个元素触发功能、访问新页面、或改变自身状态。
| **输入方式** | **交互行为** | **示意** |
| -------- | -------- | -------- |
| 触屏 | 单指单击 | ![zh-cn_image_0000001280472681](figures/zh-cn_image_0000001280472681.png) |
| 鼠标 | 左键单击&nbsp;/&nbsp;左键双击 | ![zh-cn_image_0000001236472600](figures/zh-cn_image_0000001236472600.png) |
| 触摸板 | 单指单击&nbsp;/&nbsp;单指双击 | ![zh-cn_image_0000001280232265](figures/zh-cn_image_0000001280232265.png) |
| 键盘 | 移动焦点到对象上后按下Enter键 | ![zh-cn_image_0000001280472701](figures/zh-cn_image_0000001280472701.png) |
一般地,触屏手指的按下/抬起行为对应于光标的按下/抬起行为。
在一些特殊场景,可能会存在使用鼠标/触摸板双击打开对象的交互方案,例如电脑模式下打开桌面应用或文件。此类情况需由应用单独特殊处理,且同一功能不能同时支持单击和双击两种交互方式。
## 显示菜单
某个元素上显示弹出菜单或快捷方式菜单。
![zh-cn_image_0000001268533753](figures/zh-cn_image_0000001268533753.jpg)
| **输入方式** | **交互行为** |
| -------- | -------- |
| 触屏 | 单指长按 |
| 鼠标 | 右键单击(与PC一致)/&nbsp;左键长按(保留触屏习惯) |
| 触摸板 | 双指轻单击/重单击(与PC一致)/&nbsp;单指重长按(保留触屏习惯) |
| 键盘 | (无通用操作) |
这里的菜单指的是广义的菜单,即用于展示用户可执行的操作的临时性弹出窗口。
凡是在触屏上通过长按显示的菜单,都需要支持鼠标右键单击和触摸板双指单击的触发方式。
## 拖拽对象
直接指向某个元素并移动到界面其他位置
![zh-cn_image_0000001268653953](figures/zh-cn_image_0000001268653953.png)
| **输入方式** | **交互行为** |
| -------- | -------- |
| 触屏 | 长按某对象后触发可拖拽状态,然后移动手指改变对象位置。 |
| 鼠标&nbsp;/&nbsp;触摸板 | 鼠标左键或触摸板单指按下即可拖拽对象(无需长按等待)。 |
| 键盘 | (无通用操作) |
# 多态控件
为了支持多设备,应用需要能够在不同的设备上运行,控件作为应用的基础组成部分,需要支持不同的设备,且在视觉、交互、动效等表现形式上针对设备进行必要的调整,达到最佳体验。因此,同一控件在不同的设备上会呈现出不同的形态,称为多态控件。
![zh-cn_image_0000001268129090](figures/zh-cn_image_0000001268129090.png)
多态控件应该具备以下特点:
- 覆盖默认设备、平板,兼顾智慧屏、车机、智能穿戴等终端。
- 场景一致性。在对应的使用场景下,其交互、视觉、动效要保持一致,在设计上属性参数保持一致或差异化。
- 针对设备做优化。多态控件在不同的设备上的呈现应该是该设备下的最佳效果,因此在保证一致性的同时,还需要针对设备的特点进行优化。
## 控件的状态
- 控件的状态是一种视觉呈现,用于展示控件当前处于何种交互阶段。不同控件的相同状态应该保持一致的视觉风格,且应该清晰可见。
- 应用可能部署在不同设备上供用户使用,有些设备会支持多种输入方式。例如平板可以连接蓝牙键盘和鼠标来做文字编辑工作,此时控件需要同时满足键盘和鼠标交互,需要支持获焦态和悬停态,如果控件没有支持这两种状态,在使用键盘走焦时或鼠标悬停时,控件就无法通过呈现出相应的状态为用户提供正确的视觉引导。OpenHarmony默认提供多种交互方式的控件实现,方便开发者支持多种输入方式和交互归一。
常见的状态类型:
| | | |
| -------- | -------- | -------- |
| ![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/>操作:将鼠标悬停在控件之上。 |
# 设计原则和要点
## 设计原则
当为多种不同的设备开发应用时,有如下设计原则:
**差异性**
充分了解所要支持的设备,包括屏幕尺寸、交互方式、使用场景、用户人群等,对设备的特性进行针对性的设计。
**一致性**
除了要考虑每个设备的特性外,还需要考虑不同设备的共性,并使用通用性设计方法提供既符合设备差异性,又具有跨设备一致性的设计,从而减少用户学习的难度,降低应用开发的成本。
**灵活性**
在硬件能力、交互方式、使用场景类似的设备上,应主要考虑布局位置、内容宽度、横向组件数量的调整,避免出现因横竖屏切换、窗口尺寸变化造成的界面元素不合理空白、模糊、变形、截断的问题。
**兼容性**
在硬件能力、交互方式、使用场景差异较大的设备上,除了考虑布局位置、内容宽度、横向组件数量,还需支持不同的输入方式、考虑功能架构的调整,避免出现输入不识别、功能不可以用、使用困难的问题。
## 设计要点
很多用户的多设备体验以默认设备为核心或从默认设备往外延伸,因此,围绕全场景体验的OpenHarmony UX设计,将优先确保用户在不同的设备上获得跟使用默认设备类似的体验,同时充分利用设备的优势使体验最大化。
在进行OpenHarmony的多设备应用设计时,需考虑应用以下内容:
### 自适应应用架构
使用自适应应用架构,可以确保应用在不同终端上,以最佳的导航形式来访问应用。OpenHarmony 在开发SDK上提供了便利,开发者可以通过简单配置轻松完成,无需从0开始构建。
例如:默认设备上的底Tab的结构,在Pad上一般使用侧边Tab来代替,在大屏上则是顶部Tab。
![zh-cn_image_0000001317325609](figures/zh-cn_image_0000001317325609.png)
更多应用架构的设计内容,详见:[应用导航结构设计](navigation-design.md)[应用页面结构设计](page-design.md)
### 响应式界面布局
应用会在不同的场景下使用,常见的有横竖屏切换、分屏。这些场景会导致界面的尺寸和长宽比例发生变化。因此需要考虑内容的响应式布局,确保在各种场景下都有最佳的显示效果。
OpenHarmony 提供了多种布局能力,开发者通过组合运用使内容布局更符合业务需要与用户预期。
例如:默认设备上的滚动banner,在其他设备上可进行延伸,平板上露出更多banner,大屏上完全显示两张。
![zh-cn_image_0000001317485573](figures/zh-cn_image_0000001317485573.png)
在不同类型的设备上,界面的尺寸和比例更为多样,再加上使用上的差异,导致设计上更为复杂。为此,可以考虑使用分栏布局、重复布局、挪移布局、缩进布局,进一步解决内容的显示问题。
例如:默认设备上上下排布的大图与列表,在长宽比例更大的设备上可挪移到左右展示。
![zh-cn_image_0000001268285678](figures/zh-cn_image_0000001268285678.png)
更多界面布局的设计内容,详见:[界面布局](layout-design-intro.md)
### 交互归一
交互归一描述了在多种交互任务或场景下,应用在触屏上和其它常用的输入方式(例如鼠标、触摸板、键盘)上分别对应的正确的交互规则。设计师和开发者应保证在当前输入方式下应用能够以正确的、符合用户习惯的交互规则进行响应。通常情况下,系统已经做好了这些事情,开发者只需正确调用。如果您的操作比较特别,您需要考虑多端上的交互归一,以确保用户体验的一致。
更多交互归一的设计内容,详见:[人机交互](interaction-basics.md)
### 视觉参数化
通过参数,方便的调整各端的视觉,使得各端具备该设备特有的风格。在OpenHarmony中,边距、圆角、阴影、字体大小等,都可以通过参数来进行调整。
![zh-cn_image_0000001317085757](figures/zh-cn_image_0000001317085757.png)
更多视觉参数化的设计内容,详见:[视觉风格](visual-style-basics.md)
### 多态控件
应用在多设备上运行,设备也可在不同交互方式下使用。控件作为应用的基础组成部分,需要支持不同的设备,且在视觉、交互、动效等表现形式上针对设备进行必要的调整,达到最佳体验。因此,同一控件在不同的设备上会呈现出不同的形态,称为多态控件。
OpenHarmony默认提供支持多设备的控件,开发者可以直接使用并对不同状态进行自定义。例如平板可以连接蓝牙键盘和鼠标来做文字编辑工作,此时控件需要同时满足键盘和鼠标交互,需要支持获焦态和悬停态。
### 针对性优化
在上述设计内容以外,在具体设备上,推荐针对性地进行特殊的操作和布局优化,使之符合当前设备的使用习惯。
例如:在以键鼠操作的界面上,为确保用户的使用习惯,需要提供额外的设计。
| **以触控为主** | **以键鼠操作为主** |
| -------- | -------- |
| 下拉刷新 | 界面上提供“刷新”图标或适配F5快捷键 |
| 滑动多选 | 鼠标&nbsp;框选 |
| 下拉关闭 | 界面上提供“关闭”图标 |
| 长按浮起拖拽 | 鼠标直接拖拽 |
# 一多能力的功能开发介绍
应用开发至少包含两部分工作:UI页面开发和底层功能开发(部分需要联网的应用还会涉及服务端开发)。如“打开设备NFC”功能,除了开发页面,还需要调用系统API开启NFC。前面章节主要介绍了如何解决页面适配的问题,本章节主要介绍应用如何解决设备系统能力差异的兼容问题。
我们以一个简单例子说明:一个具有NFC功能的应用,如何兼容运行在有NFC和无NFC系统能力的设备上。
应用从开发到用户可以使用,一般要经历几个阶段:应用分发和下载-&gt;应用安装-&gt;应用运行,要解决上面提到的兼容问题,一般有如下几种解决思路:
1. 分发和下载:有NFC设备的用户才能在应用市场可见该应用,提供用户下载能力。
2. 安装:有NFC能力的设备才允许安装该应用。
3. 运行:在运行阶段通过动态判断方式,在NFC设备上功能运行正常,在无NFC设备上运行不发生Crash。
所以,对应的解决方案就有:
1. 在分发阶段,核心分发逻辑:上架的应用使用的系统能力是设备系统能力的子集。满足这个条件,用户才能在应用市场看到该应用。
2. 在安装阶段,核心安装逻辑:安装的应用调用的系统能力是设备系统能力的子集。满足这个条件,用户才能安装该应用。
3. If/Else的动态逻辑判断。伪代码简单示例如下:
```
if (该设备有系统能力1) {
运行系统能力1相关的代码;
} else {
提示用户该设备不支持;
}
```
OpenHarmony支持的设备类型分为两大类:
1. 典型设备类型,如默认设备、平板等。系统已经定义好了这些设备的系统能力集合,对应用开发者来说,只需要感知在IDE中创建Module时设备类型的选择,如下图所示:
![zh-cn_image_0000001267573986](figures/zh-cn_image_0000001267573986.png)
或者module.json文件中的deviceTypes,如下图所示:
![zh-cn_image_0000001266934142](figures/zh-cn_image_0000001266934142.png)
简单来说,应用开发者基于典型设备类型的应用开发,仅需选择设备类型,系统完成分发或者安装的匹配逻辑。
2. 厂家自定义设备类型。由于厂家提供的系统能力千差万别,OpenHarmony允许厂家自己自定义设备的系统能力,但需要通过标准的格式形成一个ID,称为PCID(产品兼容性标识,Product Compatibility ID),并对外发布这个PCID,一般通过设备认证中心发布。应用开发者如果希望应用可以分发和安装到厂家自定义的设备上,就必须拿到对应的自定义设备PCID,否则就无法声明应用使用或调用的系统能力集(也是通过一个ID表示,称为RPCID,英文全名:Required Product Compatibility ID,中文名:要求的产品兼容性标识)。
鉴于当前OpenHarmony版本还不具备设备认证中心,本章节仅讨论典型设备类型下多设备应用开发的场景。
## 编码时的API联想
IDE中提供了API的联想功能,方便开发者使用系统能力。当开发者选择多个设备类型时,API的联想范围就是选择类型设备提供的API的并集,如同时支撑默认设备和平板,API的联想范围就是默认设备和平板支持的API的并集。API的联想效果如下:
![zh-cn_image_0000001267334018](figures/zh-cn_image_0000001267334018.gif)
## 动态逻辑判断
开发者可以通过canIUse接口,判断目标设备是否支持某系统能力,进而执行不同的业务逻辑。
```
import geolocation from'@ohos.geolocation';
const isLocationAvailable =canIUse('SystemCapability.Location.Location');
if (isLocationAvailable) {
console.log('该设备支持位置信息');
geolocation.getCurrentLocation((location) => {
console.log(location.latitude, location.longitude);
})
} else {
console.log('该设备不支持位置信息');
}
```
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 开发者通过 import 方式导入的模块,若当前设备不支持该模块,import 的结果为 undefined。故开发者在使用 API 时,需要判断其是否存在。
# 常见问题
## 如何查询设备类型
设备类型分为default(默认设备)、tablet、tv、wearable等,有多种查询设备类型的方式。
1. 通过命令行的方式查询设备类型。
通过命令行查询指定系统参数(const.build.characteristics)进而确定设备类型,详见[系统参数介绍](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/subsystems/subsys-boot-syspara.md)
```ts
# 方法一
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)
```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中各个字段的含义请参考[设备信息](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/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查询显示设备的属性(包括屏幕宽、高和屏幕密度等),详见[屏幕属性](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/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获取应用窗口,进而查询应用窗口的宽高等,详见[窗口](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis/js-apis-window.md)
注意必须在应用创建窗口后才可以拿到窗口对象,window.getTopWindow依赖AbilityContext作为入参,可以在MainAbility中通过this.context拿到AbilityContext对象,详见[Ability开发指导](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/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) **说明:**
> 通过窗口尺寸调整显示更加“可靠”,因为顶部状态栏、三键导航等可能占据部分屏幕空间,另外部分设备上的应用可能以窗口形式显示。
# 栅格断点系统
栅格断点系统定义了不同水平宽度设备对应的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) **说明:**
> - 请访问[栅格布局](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/ui/ui-ts-layout-grid-container.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),了解栅格布局的详细用法。
>
> - 类Web开发范式,通过[grid-container](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-js/js-components-grid-container.md)、 [grid-row](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-js/js-components-grid-row.md)、[grid-col](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/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 })
}
}
```
# IDE使用
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) **说明:**
> 本章的内容基于[DevEco Studio 3.0 Beta3](https://developer.harmonyos.com/cn/develop/deveco-studio#download_beta_openharmony)版本进行介绍,如您使用DevEco Studio其它版本,可能存在文档与产品功能界面、操作不一致的情况,请以实际功能界面为准。
## 工程创建
参考[创建OpenHarmony工程](https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ohos-create-new-project-0000001263280423),先创建出最基本的项目工程。可以看到DevEco Studio创建出的默认工程,仅包含一个的entry类型的模块。
![zh-cn_image_0000001267274204](figures/zh-cn_image_0000001267274204.jpg)
而在本文“[工程结构](introduction.md#部署模型)”小节中,推荐开发者采用common、features、product三层工程结构,这样在一个应用工程内可以更容易、清晰地管理多个设备的模块。工程结构示例如下所示:
```
/application
├── common # 公共特性目录
├── features # 功能模块目录
│ ├── feature1 # 子功能
│ ├── feature2 # 子功能2
│ └── ... # 子功能n
└── product # 产品层目录
├── wearable # 智能穿戴泛类目录
├── default # 默认设备泛类目录
└── ...
```
当然开发者也可以使用平级的目录进行模块管理,只是看上去会显得有点“杂乱”,工程结构示例如下所示:
```
/application
├── common
├── feature1
├── feature2
├── featureN
├── wearable
├── default
└── productN
```
## 新建Module
参考[开发OpenHarmony npm包](https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ohos-development-npm-package-0000001222578434),新建三个npm模块,分别命名为common、feature1、feature2。参考[添加/删除Module](https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ohos-adding-deleting-module-0000001218760594),新建一个entry类型的模块,假设命名为“wearable”(仅仅为了说明某一类产品)。示例如下:
![zh-cn_image_0000001315434285](figures/zh-cn_image_0000001315434285.png)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> - 在一个工程中同一个设备类型只支持一个Entry类型的模块。
>
> - 当前的DevEco Studio(3.0 Beta3版本)在创建工程时,设备类型仅能选择phone和tablet,默认该模块已经选择了entry类型,那么在创建wearable这个模块,只能选择feature类型。
>
> - 在下一个小节,我们将介绍如何修改Module的配置,包括Module的类型以及其支持的设备类型等。
## 修改Module配置
### 修改Module名称
修改创建工程时默认的entry模块名称。在该模块上点击鼠标右键,依次选择”Refactor -&gt; Rename”,将名称修改为default。
![zh-cn_image_0000001315914185](figures/zh-cn_image_0000001315914185.jpg)
### 修改Module类型及其设备类型
通过修改每个模块中的配置文件(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模块编译出的hap包在默认设备和平板上安装和运行。
![zh-cn_image_0000001267914116](figures/zh-cn_image_0000001267914116.png)
- 将wearable模块的deviceTypes配置为["wearable"],同时将其type字段配置为entry。
即wearable模块编译出的hap包仅在智能穿戴设备上安装和运行。
![zh-cn_image_0000001267514192](figures/zh-cn_image_0000001267514192.png)
## 调整目录结构
调整目录结构
在工程根目录(MyApplication)上点击鼠标右键,依次选择“New -&gt; Directory”新建子目录。创建product和features两个子目录。
![zh-cn_image_0000001266874320](figures/zh-cn_image_0000001266874320.png)
用鼠标左键将default目录拖拽到新建的product目录中,在IDE弹出的确认窗口中,点击“Refactor”即可。
![zh-cn_image_0000001315714137](figures/zh-cn_image_0000001315714137.jpg)
按照同样的步骤,将wearable目录放到product目录中,将feature1和feature2放到features目录中。
![zh-cn_image_0000001316914105](figures/zh-cn_image_0000001316914105.png)
## 修改依赖关系
回顾之前小节中关于“工程结构”的介绍,我们推荐在common目录中存放基础公共代码,features目录中存放相对独立的功能模块代码,product目录中存放完全独立的产品代码。这样在product目录中依赖features和common中的公共代码来实现功能,可以最大程度实现代码复用。
配置依赖关系可以通过修改模块中的package.json文件。如下图所示,通过修改default模块中的package.json文件,使其可以使用common、feature1和feature2模块中的代码。更多详情参考[配置OpenHarmony npm包依赖](https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ohos-development-npm-package-0000001252769386#section89674298391)
![zh-cn_image_0000001267274208](figures/zh-cn_image_0000001267274208.png)
同样的,修改feature1和feature2模块中的package.json文件,使其可以使用common模块中的代码。
修改package.json文件后,一定要点击右上角的“Sync Now”,否则改动不会生效!
## 引用npm包中的代码
[开发OpenHarmony npm包](https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ohos-development-npm-package-0000001252769386)中,仅介绍了如何使用npm包中的页面和资源,本小节以例子的形式补充介绍如何使用npm包中的类和函数。
示例如下:
- 在common模块中新增ComplexNumber类,用于表征复数(数学概念,由实部和虚部组成),该类包含toString()方法,将复数转换为字符形式。
- 在common模块中新增Add函数,用于计算并返回两个数字的和。
- 在default模块中,使用common模块新增的ComplexNumber类和Add函数。
1. 在”common/src/main/ets”目录中,按照需要新增文件和自定义类和函数。
![zh-cn_image_0000001315434289](figures/zh-cn_image_0000001315434289.png)
2. 在”common/index.ets”文件中,申明需要export的类、函数的名称及在当前模块中的位置,否则其它模块无法使用。
![zh-cn_image_0000001315914189](figures/zh-cn_image_0000001315914189.png)
3. 在default模块中import和使用这些类和函数。注意提前在default模块的package.json文件中配置对common模块的依赖关系。
![zh-cn_image_0000001267914120](figures/zh-cn_image_0000001267914120.png)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 如果需要将npm包发布供其他开发者使用,当前npm包可发布到npm官方中心仓和OpenHarmony npm专用仓,具体可参考[发布OpenHarmony npm包](https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ohos-development-npm-package-0000001222578434#section663116411397)。
## 总结
本章主要介绍了如何实现推荐的工程结构,以便更好的进行多设备应用开发。
关于IDE的基本使用,比如如何进行编译构建、如何签名、如何使用预览器等,[DevEco Studio使用指南](https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ohos-deveco-studio-overview-0000001263280421)中已经有非常详尽的介绍,本文不再重复介绍。
# 交互基础
在全场景的数字体验中,越来越多类型的智能终端设备分布在用户的日常生活中,可交互的用户界面广泛存在于默认设备、平板、PC、智能穿戴设备、智慧屏、车机、虚拟现实(VR)和增强现实(AR)等设备上。应用可能在多种设备上运行或在单一设备上被用户通过多种输入方式操控,也可能在多种距离上被用户操控。这需要其用户界面能够识别和支持不同的交互场景,以便用户以习惯的、舒适的方法与其进行交互。
![zh-cn_image_0000001224293580](figures/zh-cn_image_0000001224293580.png)
## 输入方式
典型的输入方式包括但不限于触屏上手指/手写笔等直接交互、鼠标/触摸板/键盘/表冠/遥控器/车机摇杆/旋钮/手柄/隔空手势等间接交互、以及语音交互。
设计和开发应用时,**设计师和开发者应考虑到应用具有使用多种输入方式的可能性**,并实现相应的功能,保证在当前输入方式下应用能够以正确的、符合用户习惯的方式进行响应。
## 交互距离
典型的设备交互距离包括但不限于15cm(智能穿戴设备)、30cm(默认设备)、60cm(桌面设备)、260cm(大屏),具体距离会在用户使用过程中产生一定范围的变化。
设计和开发应用时,设计师和开发者应考虑到多种距离下使用的可能性,保证界面元素的大小、展示信息的密度符合用户的预期。
![zh-cn_image_0000001313720673](figures/zh-cn_image_0000001313720673.png)
# 交互归一
对于不同类型的智能设备,用户可能有不同的交互方式,如通过触摸屏、鼠标、触控板等。如果针对不同的交互方式单独做适配,会增加开发工作量同时产生大量重复代码。为解决这一问题,我们统一了各种交互方式的API,即实现了**交互归一**
## 基础输入
常见的基础输入方式及其在各输入设备上的表现如下图所示。
![zh-cn_image_0000001313602309](figures/zh-cn_image_0000001313602309.jpg)
基础输入对应的开发接口,以及当前支持情况如下表所示。
| 输入 | 开发接口 | 触控屏 | 鼠标 | 触控板 |
| -------- | -------- | -------- | -------- | -------- |
| 点击 | [onClick](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-universal-events-click.md) | √ | √ | √ |
| 长按 | [LongPressGesture](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-basic-gestures-longpressgesture.md) | √ | √ | × |
| 双击 | [TapGesture](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-basic-gestures-tapgesture.md) | √ | √ | √ |
| 轻扫 | [SwipeGesture](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-basic-gestures-swipegesture.md) | √ | √ | √ |
| 滚动及平移 | [PanGesture](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-basic-gestures-pangesture.md) | √ | √ | √ |
| 缩放 | [PinchGesture](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-basic-gestures-pinchgesture.md) | √ | √ | √ |
| 旋转 | [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) **说明:**
> - 点击事件(onClick)其实是点击手势(TapGesture)的一个特殊场景(单指单次点击)。该场景使用的非常广泛,为了方便开发者使用及符合传统开发习惯,所以专门提供了开发接口。
>
> - 触控板支持长按输入的功能正在开发中。
## 拖拽事件
拖拽是应用开发中经常碰到的场景。拖拽发生在两个组件之间,它不是简单的单次输入,而是一个”过程”,通常包含如下步骤(以将组件A拖拽到组件B中为例)。
- 长按或点击组件A,触发拖拽。
- 保持按压或点击,持续将组件A向组件B拖拽。
- 抵达组件B中,释放按压点击,完成拖拽。
- 也可以在未抵达组件B的中途,释放按压点击,取消拖拽。
一个完整的拖拽事件,包含多个拖拽子事件,如下表所示(请访问拖拽事件了解详细用法)。当前触控屏和鼠标的拖拽事件已经实现”交互归一”,对触控屏的支持正在开发中。
| 名称 | 功能描述 |
| -------- | -------- |
| onDragStart | 绑定A组件,触控屏长按/鼠标左键按下后移动触发 |
| onDragEnter | 绑定B组件,触控屏手指、鼠标移动进入B组件瞬间触发 |
| onDragMove | 绑定B组件,触控屏手指、鼠标在B组件内移动触发 |
| onDragLeave | 绑定B组件,触控屏手指、鼠标移动退出B组件瞬间触发 |
| onDrop | 绑定B组件,在B组件内,触控屏手指抬起、鼠标左键松开时触发 |
# 简介
## 背景
随着终端设备形态日益多样化,分布式技术逐渐打破单一硬件边界,一个应用或服务,可以在不同的硬件设备之间随意调用、互助共享,让用户享受无缝的全场景体验。而作为应用开发者,广泛的设备类型也能为应用带来广大的潜在用户群体。但是如果一个应用需要在多个设备上提供同样的内容,则需要适配不同的屏幕尺寸和硬件,开发成本较高。OpenHarmony 系统面向多终端提供了“一多”的能力,让开发者可以基于一种设计,高效构建多端可运行的应用。
![zh-cn_image_0000001267340890](figures/zh-cn_image_0000001267340890.jpg)
## 定义及目标
**定义**:一套代码工程,一次开发上架,多端按需部署。
**目标**:支撑开发者快速高效开发多设备(多种终端设备形态的简称)应用,实现对不同设备兼容的同时提供跨设备的流转、迁移和协同的分布式体验。
![zh-cn_image_0000001315500981](figures/zh-cn_image_0000001315500981.jpg)
为了实现“已整改”的目标,需要解决两个基础问题:
- 不同设备间的屏幕尺寸、色彩风格等存在差异,页面如何适配。
- 不同设备的系统能力有差异,如智能穿戴设备是否具备GPS、智慧屏是否具备摄像头等,功能如何兼容。
从第3章开始将从UX设计、系统能力等角度,详尽的解答上述问题。
## 基础知识
为了更好的阅读后面的章节,本小节主要介绍了一些基础知识,方便读者理解内容。
### 应用程序包结构
OpenHarmony 的应用以APP Pack (Application Package) 形式发布,它是由一个或多个HAP包以及描述每个HAP属性的pack.info文件组成。
HAP是OpenHarmony的安装包,一个HAP在工程目录中对应一个Module,由Module编译而来,可分为entry和feature两种类型的HAP。
- **entry**:应用的主模块包。一个APP中,对于同一设备类型,可以有一个或多个entry类型的HAP,来支持该设备类型中不同规格(如API版本、屏幕规格等)的具体设备。
- **feature**:应用的动态特性模块包。一个APP Pack可以包含零个、一个或多个feature类型的HAP。
![zh-cn_image_0000001266965046](figures/zh-cn_image_0000001266965046.png)
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> - Module是开发者开发的相对独立的功能模块,由代码、资源、第三方库及应用配置文件组成,属于IDE开发视图的概念。Module分为entry、feature及har三种类型,相应的可以编译生成entry类型的HAP包、feature类型的HAP包,以及HAR包。
>
> - 如果需要了解应用程序包结构更多详情,可以查看[包结构说明](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/quick-start/package-structure.md)。
### 方舟开发框架
OpenHarmony提供了方舟开发框架(简称:ArkUI),提供开发者进行应用UI开发时所必须的能力。
方舟开发框架提供了两种开发范式,分别是基于JS扩展的类Web开发范式(“类Web开发范式”的简称)和基于TS扩展的声明式开发范式(“声明式开发范式”的简称)。
- **类Web开发范式**:采用经典的HML、CSS、JavaScript三段式开发方式。使用HML标签文件进行布局搭建,使用CSS文件进行样式描述,使用JavaScript文件进行逻辑处理。UI组件与数据之间通过单向数据绑定的方式建立关联,当数据发生变化时,UI界面自动触发更新。此种开发方式,更接近Web前端开发者的使用习惯,快速将已有的Web应用改造成方舟开发框架应用。主要适用于界面较为简单的中小型应用开发。
- **声明式开发范式**:采用TS语言并进行声明式UI语法扩展,从组件、动效和状态管理三个维度提供了UI绘制能力。UI开发更接近自然语义的编程方式,让开发者直观地描述UI界面,不必关心框架如何实现UI绘制和渲染,实现极简高效开发。同时,选用有类型标注的TS语言,引入编译期的类型校验,更适用大型的应用开发。
两种开发范式的对比如下。
| **开发范式名称** | **语言生态** | **UI更新方式** | **适用场景** | **适用人群** |
| -------- | -------- | -------- | -------- | -------- |
| 类Web开发范式 | JS语言 | 数据驱动更新 | 界面较为简单的类小程序应用和卡片 | Web前端开发人员 |
| 声明式开发范式 | 扩展的TS语言(eTS) | 数据驱动更新 | 复杂度较大、团队合作度较高的程序 | 移动系统应用开发人员、系统应用开发人员 |
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> - 声明式开发范式占用内存更少,**更推荐开发者选用声明式开发范式来搭建应用UI界面**。
>
> - 可以查看[方舟开发框架概述](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/ui/arkui-overview.md),了解方舟开发框架更多详情。
### 部署模型
我们既可以在不同类型的设备上部署相同的HAP包,也可以在不同类型的设备上部署不同的HAP包。
“一多”有两种部署模型:
- **部署模型A**:不同类型的设备上按照一定的工程结构组织方式,通过一次编译生成**相同**的HAP包(或HAP包组合)。
- **部署模型B**:不同类型的设备上按照一定的工程结构组织方式,通过一次编译生成**不同**的HAP包(或HAP包组合)。
建议开发者从设备类型及应用功能两个维度,结合具体的业务场景,考虑选择哪种部署模型。但不管采用哪种部署模型,都应该采用一次编译。
**设备类型**
从屏幕尺寸、交互方式及使用距离三个维度考虑,我们将常用的设备分为三大泛类:
- 默认设备、平板
- 车机、智慧屏
- 智能穿戴
对于相同泛类的设备,优先选择部署模型A,对于不同泛类设备,优先选择部署B。
**应用功能**
- 方舟开发框架提供了丰富的多设备适配能力,相同泛类的设备通常总是可以使用部署模型。部署模型A需要的开发和维护工作量更小,而且可以保证不同类型设备上体验的一致性。
- 仅当同一泛类不同类型设备上规划的功能差异非常大时,才推荐使用部署模型B,如默认设备和平板分别交给两个团队设计、开发和维护等。
一般应用在不同设备上选择部署模型的思路如下:
![zh-cn_image_0000001317848113](figures/zh-cn_image_0000001317848113.png)
(备注:页面导航是用于应用内页面之间的跳转。例如默认设备设备上PageA跳转到PageB,Pad设备上也是PageA跳转到PageB,两种设备因为屏幕大小不同,默认设备上PageB是覆盖显示在PageA上的,Pad设备上PageB是在PageA的右边并且同时显示,但因为都是PageA跳转到PageB,那么我们认为它们的页面导航逻辑相同。)
**工程结构**
部署模型不同,往往代码工程结构也不同。
部署模型A对应的代码工程结构抽象后一般如下所示:
```
/application
├── common # 可选。公共特性目录, har类型的module
├── features # 可选。功能模块目录
│ ├── feature1 # 子功能1, har类型的module
│ ├── feature2 # 子功能2, har类型的module
│ └── ...
└── product # 必选。产品层目录, entry类型的module,编译后为hap包
```
部署模型B对应的代码工程结构抽象后一般如下所示:
```
/application
├── common # 可选。公共特性目录, har类型的module
├── features # 可选。功能模块目录
│ ├── feature1 # 子功能1, har类型的module
│ ├── feature2 # 子功能2, har类型的module
│ └── ...
└── product # 必选。产品层目录
├── wearable # 智能穿戴泛类目录, entry类型的module,编译后为hap包
├── default # 默认设备泛类目录, entry类型的module,编译后为hap包
└── ...
```
- common:公共特性目录,如工具类、公共配置等。
- features:功能模块目录,存放应用中相对独立的各个功能的实现(包括该功能相关的UI代码及业务逻辑代码),如账户管理等。
- product:产品层目录,通过引用common和feature目录中代码的方式做功能和特性的集成,同时也作为主入口。**这一层是两个部署模型主要差异点**,部署模型A可以直接在product目录中做功能和特性集成,部署模型B则需要在product目录下再建一级子目录,在不同的子目录中对不同的产品做差异化的功能和特性集成。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 无论是用部署模型A还是部署模型B,在开发阶段,都应考虑**不同类型设备间最大程度的复用代码**,以减少开发及后续维护的工作量。
# 自适应布局
自适应布局是通过设定元素与外部容器的相对关系实现的。当外部容器大小、位置等发生变化时,元素即可以根据相对关系自动变化以适应外部环境的变化。通常自适应布局能根据vp/px变化进行无级连续的变化。
## 自适应拉伸
某单个内容或某组内容的显示宽度不是固定值,而是通过相对参照物的方式来确定其显示宽度。当参照物的宽度发生变化时,内容或内容间距的宽度随之发生自适应拉伸。
左右拉伸:例如,列表开关组合中,在窗口宽度变化时,开关控件固定宽度并相对列表的右边缘位置固定,整个组合与文本宽度均自适应变化。
![zh-cn_image_0000001258762686](figures/zh-cn_image_0000001258762686.gif)
均分拉伸:例如,在图标型网格中,当窗口宽度变化时,入口图标间距与图标离左右边缘间距同时均等变化。
![zh-cn_image_0000001291556325](figures/zh-cn_image_0000001291556325.gif)
自适应拉伸适用于文字、普通按钮、间距等展示宽度灵活,对宽高比不敏感的内容和内容组合。
当可能出现的拉伸宽度不足以显示默认内容时,应根据场景选择优先保证内容完整或者优先保证其他内容的屏效,并进行截断或换行等组合适配。
![zh-cn_image_0000001245276368](figures/zh-cn_image_0000001245276368.png)
## 自适应缩放
组件的显示大小是固定比例,通过相对参照物的方式来确定其宽或高。当参照物的大小发生变化时,元素的大小随之发生自适应缩放。
完整缩放:例如,在宽度或高度变化时,时钟始终保证表盘完整展示并根据较短边决定宽高。
![zh-cn_image_0000001291675753](figures/zh-cn_image_0000001291675753.gif)
占比缩放:例如,带主体和背景的插画,画面内容根据宽度变化裁切,根据高度变化按50%比例缩放。
![zh-cn_image_0000001245116688](figures/zh-cn_image_0000001245116688.gif)
自适应缩放适用于图片、圆形按钮、banner、反应真实物体形状的图像等必须保证宽高比的内容。
不推荐将所有元素同时缩放、或某内容放大过大超过屏幕50%。这将导致获取信息量不增反减,不符合用户预期。
![zh-cn_image_0000001292842113](figures/zh-cn_image_0000001292842113.gif)
## 自适应延伸
组件的显示数量不是固定的,而是通过相对参照物的方式来确定其显示数量。当参照物的宽度发生变化时,组件随之发生自适应延伸显示更多数量。
同功能内容延伸:例如,子页签和可滑动宫格在默认宽度下通过露出最后内容,提示右方有更多入口,在宽度变化时,可在每个元素宽度不变、保持滑动交互时显示更多数量。
![zh-cn_image_0000001291556369](figures/zh-cn_image_0000001291556369.gif)
不同功能内容延伸或隐藏:例如,默认处于同一排的不同音乐播放按钮优先级不同,在宽度变化时可延伸或隐藏低优先级的按钮,最大化适应不同窗口尺寸。
![zh-cn_image_0000001245276416](figures/zh-cn_image_0000001245276416.gif)
自适应延伸/隐藏适用于页签、操作块、推荐栏目等具有相同交互层级且有更多数据可以填充的内容。
注意:需要判断因隐藏而不展示的内容对功能完整性是否有影响,并考虑通过滑动或“更多”按钮提供查看使用该内容的方式。
## 自适应折行
定义了折行能力的组件,可以根据组件容器的可用空间,体现纵向布局或者横向布局。
例如,在宽度足够时,操作块位于同一行,在宽度变小时,可根据内容能显示的宽度纵向排布。
![zh-cn_image_0000001291675805](figures/zh-cn_image_0000001291675805.png)
自适应折行适用于页签、操作块、内容流等具有相同交互层级,且希望保证类型和数量完整性的内容。
自适应布局对应OpenHarmony系统提供的自适应布局能力中的拉伸、均分、缩放、占比、延伸、隐藏、折行。自适应布局能力详见本文“[自适应布局](adaptive-layout-intro.md)”。
# 布局基础运用案例
## 平级导航的复合网格视图
平级导航的复合网格视图常出现在同时展示多种不同内容的界面。
例如,市场类应用作为典型的平级导航,其首页不同板块采用了不同布局能力。
![zh-cn_image_0000001317088945](figures/zh-cn_image_0000001317088945.png)
- 标题栏与搜索栏:因元素单一、位置固定在顶部,因此适合采用自适应拉伸,并在大尺寸界面中从纵排变为横排,充分利用顶部区域。
- 运营横幅:在小设备上默认为多张轮播展示,随宽度变化采用自适应缩放,在中尺寸界面通过重复布局变为并排多张。
- 图标型网格:对于数量固定、且子内容重要程度相同的网格,需保证完全展示,可采用均分拉伸。对于数量不限的网格,则采用自适应延伸,在更大宽度上展示更多数量。
- 底部导航栏:导航类控件本身综合了均分和折行,在宽度变化时能占用均等宽度并在足够宽度下并排,当在大尺寸界面中,挪移到左边,使不同页签距离更近、同时符合视觉走向。
在横竖屏切换时,也保持了一致的布局能力,实际上完成了大尺寸和中尺寸的切换。
![zh-cn_image_0000001317328797](figures/zh-cn_image_0000001317328797.png)
当界面出现在智慧屏上,虽然同是大尺寸界面,为了符合设备样式和遥控器交互规则,搜索栏转化为图标入口,导航栏挪移到页面上部。
![zh-cn_image_0000001268448854](figures/zh-cn_image_0000001268448854.png)
## 层级导航的列表视图
层级导航的列表视图常出现在多类简单信息并列或多入口业务入口的界面。
例如,设置类应用作为典型的层级导航,其列表控件采用自适应拉伸。
![zh-cn_image_0000001268128998](figures/zh-cn_image_0000001268128998.png)
在中尺寸设备中,为避免中间区域空白过大,采用缩进布局,大尺寸设备中,为充分利用横向空间,建议采用栅格系统形成分栏效果,并让列表元素在各自区域保持拉伸。
## 专辑详情页面
专辑详情不限于展示音乐内容,也用于展示视频、短视频、电台、书本等内容类合集。
例如,歌单类界面作为典型的内容垂类页面,其总体分为标题栏、歌单信息、歌单操作、歌单列表、播放栏几个板块。
- 标题栏:采用自适应拉伸。
- 歌单信息:采用自适应缩放,并在中尺寸界面进行缩进处理使内容呈现协调。
- 歌单操作:板块内部采用均分拉伸,在小尺寸设备上利用纵向空间、中尺寸设备上挪移到歌单封面右边。
- 歌单列表:板块内部采用左右拉伸,在中尺寸设备上可与歌单信息使用相同缩进布局。
- 播放栏:固定在界面底部,保持左右拉伸即可。
![zh-cn_image_0000001268288870](figures/zh-cn_image_0000001268288870.png)
在横竖屏切换时,完成了中尺寸和大尺寸的切换。歌单列表板块进行挪移的同时,内部采用了重复布局。
歌单信息和歌单操作板块因较小宽高比,挪移到上下排布。
![zh-cn_image_0000001268608782](figures/zh-cn_image_0000001268608782.png)
当界面出现在智慧屏上,为了符合沉浸简约的设备信息和遥控器交互规则,将部分歌单信息替代原来标题栏的位置,并取消播放栏。同时歌单列表居左,更方便遥控器选择。
![zh-cn_image_0000001317208833](figures/zh-cn_image_0000001317208833.png)
# 概述
布局不是静态固定的,当显示环境发生变化时,如横竖屏切换、调节字体大小、应用分屏,要及时调整内容的布局方式以适应变化。本章提供了布局基础的概念和介绍。详见[布局基础](layout-grid.md)
了解布局的基础概念后,通过调用栅格系统、自适应布局和响应式布局能力就可以让内容更好地适配显示环境的变化。综合运用布局基础能力,可实现常用页面结构的多设备适配。详见[布局基础运用案例](layout-design-cases.md)
# 栅格系统
栅格系统是一个多设备下通用的辅助定位系统,适用于应用窗口的整体布局,也支持界面局部内容使用。栅格系统由 Margin,Gutter,Column 三个属性构成。Margin是相对屏幕、窗口等父容器左右边缘的距离,决定了内容可展示的整体宽度;Gutter是每个Column的间距,决定内容间的紧密程度; Column是内容的占位元素,其数量决定了内容的布局复杂度。Margin大小、Gutter大小、Column数量综合决定Column的具体宽度。
通过栅格系统进行布局,可以更好达到多设备下布局的一致性。
![zh-cn_image_0000001224173302](figures/zh-cn_image_0000001224173302.png)
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)
# 布局能力简介
开发多设备上同一页面时,建议开发者优先使用一套布局,尽可能的实现代码复用。
一般情况下,可以通过页面的组件结构(组件个数、组件的父子/兄弟关系、组件类型)来判断是否可以使用一套布局:
- 对于页面组件结构相同的场景,在开发过程中可以灵活使用[自适应布局](adaptive-layout-intro.md)[栅格布局](grid-breakpoint.md)能力来达到预期效果。
- 对于页面组件结构不同的场景,可以借助[媒体查询](media-query.md)能力动态加载多套布局。
下面将详细介绍这些布局能力。
# 响应式布局
当基本的自适应布局无法满足多终端上屏幕的体验要求时,我们需要针对不同终端的屏幕特点,设定容器与栅格的关系达到响应式的布局。通常响应式布局能根据栅格断点变化进行有级变化。
## 栅格断点系统
根据设备的水平宽度,OpenHarmony提供了断点系统,覆盖超小、小、中、大 四种屏幕类型,并结合栅格系统默认提供了对应 Column 的数量关系。不同的设备根据自身屏幕水平宽度,在不同的断点范围,系统将自动匹配不同数量的栅格。应用也可针对具体界面自定义栅格。
![zh-cn_image_0000001305864477](figures/zh-cn_image_0000001305864477.png)
栅格断点系统与日常使用的设备屏幕类型有一定的对应关系,例如:超小对应智能穿戴设备,小对应默认设备,中对应平板,大对应智慧屏与PC。设计师可面向希望运行的设备进行所属屏幕类型的适配。
随着智能设备种类的增加,越来越多产品在四种屏幕类型上具备不同的交互能力,如支持触摸的运动相机(小)、仅支持遥杆的手持云台(小-中)、不可移动的智能台灯(中-大)等,需结合具体设备交互进行对应设计,不可一概而论。
## 缩进布局
为了在宽屏上内容显示有更好的效果,在不同宽度的设备上进行不同缩进效果。
![zh-cn_image_0000001264774952](figures/zh-cn_image_0000001264774952.gif)
缩进适用于,因宽度明显变大,内容拉伸以后导致屏幕空白内容超过50%,或文本内容过长(每行大于30字),但没有上下级界面可供同时展示或上下级界面不适合同时显示的场景。
OpenHarmony提供的默认实现为,当栅格为8column或12column时可以响应6column和8column的缩进布局。
## 挪移布局
利用屏幕的宽度优势,将原先的上下布局切换成左右布局。
例如,上下排布的插画和文字,横屏后左右排布。
![zh-cn_image_0000001264296340](figures/zh-cn_image_0000001264296340.gif)
挪移布局适用于横竖屏切换,以及类似的宽高比明显变化(大于200%)同时希望保证内容完整的场景。
## 重复布局
利用屏幕的宽度优势,将相同属性的组件横向并列排布。
![zh-cn_image_0000001306203573](figures/zh-cn_image_0000001306203573.gif)
重复布局适用于对宽高比敏感的图片和及组合内容,当内容缩放以后导致原图放大超过150%的场景。
OpenHarmony栅格系统提供的分栏实现为,当栅格为8column或12column时可以将默认4栅格的页面整体进行重复布局。
响应式布局对应OpenHarmony系统提供的布局能力中的[栅格断点系统](grid-breakpoint.md)[媒体查询](media-query.md),详见本文 “响应式布局”。
# 媒体查询
媒体查询是一种常用的功能,在移动设备上应用非常广泛。开发者经常需要根据设备的大致类型或特定的特征和设备参数(例如屏幕分辨率)来修改应用的样式。为此媒体查询提供了如下功能:
- 针对设备和应用的属性信息,可以设计出相匹配的布局样式。
- 当屏幕发生动态改变时(比如横竖屏切换、屏幕宽高比),页面布局同步更新。
当前支持对如下媒体特征做媒体查询。
| 类型 | 说明 |
| -------- | -------- |
| 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) **说明:**
> - 声明式开发范式,请查看[媒体查询](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/ui/ui-ts-layout-mediaquery.md)了解详细用法。
>
> - 类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_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%')
}
}
```
# 应用导航结构设计要求
应用中的导航用于引导用户在应用的各个页面进行浏览。好的导航让用户知道身处何处,去往何方,以及来自哪里。
## 导航的原则
导航需要遵循以下原则:
- **一致:**导航操作的结果应该与用户的期望保持一致。相同或类似的场景使用用户熟悉的界面布局和控件,在多设备上确保一致的应用架构和导航行为,让用户无论在什么页面,都知道如何导航。例如二级界面使用左上角的返回按钮来返回界面的上一个层级。
- **清晰:**导航应该提供清晰的路径。用户使用的时候,逻辑关系简单且容易理解,能够知道当前处在界面的什么位置,操作后将会跳转到什么位置,不会迷失方向。例如使用底部页签,让用户在平级页面之间进行切换。
导航要避免以下设计:
- **层级过深:**导航层级建议在三层以内。对于太深的层次,会带来操作效率的问题。如果确实需要深层级设计,建议使用面包屑设计或增加一键回到首页的功能。
- **导航复杂:**在侧边导航中,使用底部页签,会让操作变得复杂,建议仅使用侧边导航。
## 导航的分类
常用的应用导航有:平级导航、层级导航和混合导航。
**平级导航**
平级导航结构中,页面均处在同一层级。
使用场景:用于展示同等地位或同等层级的界面。
![zh-cn_image_0000001224053150](figures/zh-cn_image_0000001224053150.jpg)
例如:以Tab方式组成的页面。图中照片、相册、发现为一级界面,从视频相册进入二级内容界面。
![zh-cn_image_0000001226444718](figures/zh-cn_image_0000001226444718.png)
多设备设计:可转化导航类控件到符合设备体验的位置上。默认设备上使用Tab导航,PAD和PC使用侧边Tab导航,智慧屏使用顶部Tab导航。
![zh-cn_image_0000001270924709](figures/zh-cn_image_0000001270924709.png)
**层级导航**
层级导航结构由父页面和子页面组成。父页面可以有一个或多个子页面。每个子页面都有一个父页面。
层级导航适用于多层级的复杂结构。层级结构深的内容,用户访问的路径变长,效率降低,可以通过适当的层级穿透设计(例如:控制中心中的蓝牙开关,解决了进“设置-蓝牙”界面设置操作路径过长的问题)解决此问题。
使用场景:页面存在上下级关系的应用。
![zh-cn_image_0000001224173138](figures/zh-cn_image_0000001224173138.jpg)
例如:通过从内容进入后经返回键返回之前的页面。
![zh-cn_image_0000001270804909](figures/zh-cn_image_0000001270804909.png)
多设备设计:可以考虑将上下层级的界面在同一界面展示。默认设备和智慧屏上使用上下层级关系。平板和PC使用上分栏的方式展示内容。
![zh-cn_image_0000001226124842](figures/zh-cn_image_0000001226124842.png)
**混合导航**
在实际应用设计中,仅使用平级或层级导航可能无法应对更复杂的业务结构。此时需区分不同页面的导航关系,对同等地位或同等层级的页面使用平级导航结构,对具有复杂关系的页面使用层级导航结构。
使用场景:应用由几个同等级的模块组成,每个模块又有上下层级关系页面。
![zh-cn_image_0000001268653317](figures/zh-cn_image_0000001268653317.jpg)
多设备设计:可以根据平级导航、层级导航自身的设计规则综合运用,一般平级导航优先级比层级高。
![zh-cn_image_0000001263489242](figures/zh-cn_image_0000001263489242.png)
# 应用页面结构设计
## 通用页面结构
应用程序由多个页面组成。我们将常见的页面进行了梳理,总结了以下常用的页面结构。
**启动页面**
针对内容型应用,应用首页内容的获取需要花费一定的时间,此时可以使用启动页缓解页面加载内容的等待感。启动页可以展示应用的品牌形象或者广告,避免让用户等待过长时间。没有网络加载内容的应用,不需要使用启动页。
![zh-cn_image_0000001317205637](figures/zh-cn_image_0000001317205637.png)
用户总是希望第一时间看到应用内容,因此在页面加载完成后,需要及时呈现内容。
从后台加载应用时,不应该显示启动页。当应用被切换到后台后,再从后台加载回来时,不应该再次显示启动页。应用需要保留应用的状态,以便从后台恢复,方便用户继续浏览。
**列表内容页面**
列表内容页面通常用于文字和数据的展示,利于提升使用效率。
![zh-cn_image_0000001268605598](figures/zh-cn_image_0000001268605598.png)
列表应该按照一定的逻辑排序,便于用户浏览和操作。例如:按字母顺序排序、按时间排序。
列表应该是同类项的集合,应该对外呈现一致的布局样式。常见的是单行列表、双行列表和三行列表。
列表显示的内容要主次分明,用户一眼就能关注到重要的信息和操作。
**网格内容页面**
网格内容页面通常用于图片和视频的展示,利于沉浸浏览内容。
![zh-cn_image_0000001268445650](figures/zh-cn_image_0000001268445650.png)
网格视图显示同等重要的项目,具有统一的布局。
网格视图以图像为主组织内容。例如图库中用网格视图展示图片。
网格视图可以辅以文字和操作。例如应用市场中使用网格展示应用程序图标、简单描述和下载按钮。
网格视图应该考虑响应式布局。在横竖屏切换时,网格视图应该能够调整网格的数量以适应页面的宽度变化。
**多选页面**
多选页面是对页面内的数据多项选择,然后进行批量处理。常见的是针对列表的多项选择或宫格的多项选择。
![zh-cn_image_0000001268125810](figures/zh-cn_image_0000001268125810.png)
**详情页面**
详情页用于展示应用的详细描述和操作。
![zh-cn_image_0000001317325613](figures/zh-cn_image_0000001317325613.png)
**空页面**
在页面内没有数据的时候,使用空页面。
![zh-cn_image_0000001317485577](figures/zh-cn_image_0000001317485577.png)
**设置页面**
设置页面通常是一个模块所有设置项的聚合。
![zh-cn_image_0000001268285682](figures/zh-cn_image_0000001268285682.png)
**我的页面**
针对内容型应用,可以提供我的页面,用于承载用户的信息和资产内容。
![zh-cn_image_0000001317085761](figures/zh-cn_image_0000001317085761.png)
**关于页面**
关于页面用于呈现应用的基本情况,包括联系方式,法律条款等内容。
![zh-cn_image_0000001317205645](figures/zh-cn_image_0000001317205645.png)
## 垂类页面结构
垂类是指垂直领域,为特定的人群提供特定的服务,属于应用的细分类别。例如:华为音乐、网易云音乐等属于音乐类,华为视频、优酷等属于视频类,快手、抖音等属于直播类。垂类页面结构是在特定领域长期使用的过程中,形成的广受用户接受和理解的页面结构。
例如:
音乐类应用都有音乐歌单,音乐专辑,音乐播放界面。
视频类应用都有视频详情和视频播放界面。
直播类应用,都有瀑布流推荐和直播界面。
...
常见的垂类页面结构有:
- 音乐播放页面
- 专辑详情页面
- 视频详情页面
- 视频播放页面
**音乐播放界面**
音乐类应用中的播放器界面,该界面通常有音乐操控(播放,暂停,上一首,下一首)、歌词显示等功能。
![zh-cn_image_0000001268605602](figures/zh-cn_image_0000001268605602.png)
**专辑详情页**
音乐类应用中的音乐专辑详情界面,该界面通常有专辑介绍、专辑包含的歌曲列表等功能。
![zh-cn_image_0000001268445654](figures/zh-cn_image_0000001268445654.png)
**视频详情页面**
视频类应用的视频详情界面,该界面通常有视频播放器、视频剧集显示、视频简介等功能。
![zh-cn_image_0000001268125814](figures/zh-cn_image_0000001268125814.png)
**视频播放界面**
视频类应用的视频播放界面,该界面通常有视频画面预览、播放控制等功能。
![zh-cn_image_0000001317325617](figures/zh-cn_image_0000001317325617.png)
## 特殊页面结构
部分应用界面在差异较大的设备间切换,无法使用自适应和响应式布局设计方法进行适配,从用户预期上也需要调整应用架构时,将需要做特殊适配。
例如,同时具有底部Tab和子页签的页面,在大屏上应考虑将底部入口置于顶部工具栏或与子页签融合。
![zh-cn_image_0000001317485581](figures/zh-cn_image_0000001317485581.png)
# 一多能力的页面开发介绍
本章介绍如何使用方舟开发框架"一多"能力,开发出在多设备上正常显示的页面。方舟开发框架更推荐开发者使用声明式开发范式开发应用,故本章的内容和示例都基于声明式开发范式,所有示例代码均可以从[码云开源代码仓](https://gitee.com/openharmony/app_samples/tree/master/ETSUI/AdaptiveCapabilities)中获取。
- **[布局能力](layout-intro.md)**
- **[交互归一](interaction-events-unification.md)**
- **[多态组件](polymorphic-components.md)**
- **[资源使用](resource-usage.md)**
\ No newline at end of file
# 多态组件
方舟开发框架不仅提供了多种基础组件,如文本显示、图片显示、按键交互等。并且针对不同类型设备进行了组件设计,提供了组件在不同平台上的样式适配能力,此种组件称为“多态组件”。开发者在使用多态组件时,无需考虑设备差异,只需关注功能实现即可。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> OpenHarmony上的多态组件能力正在逐步补齐中。
# 资源使用
在页面开发过程中,经常需要用到颜色、字体、间距、图片等资源,在不同的设备或配置中,这些资源的值可能不同。有两种方式处理:
- [自定义资源](#自定义资源):借助资源文件能力,开发者在应用中自定义资源,自行管理这些资源在不同的设备或配置中的表现。
- [系统资源](#系统资源):开发者直接使用系统预置的资源定义(即分层参数)。
## 自定义资源
### 资源文件介绍
开发者可以在项目工程的resources目录中创建指定类型的资源文件,通过”key-value”的形式定义资源,同时可以借助资源限定词能力,定义同一资源在不同设备(默认设备、智慧屏等)或同一设备不同配置(横竖屏、深浅色等)下的表现。工程内的resources目录按照两级目录的形式来组织:
- 一级目录为base目录、限定词目录以及rawfile目录
- base目录是默认存在的目录。当应用的resources资源目录中没有与设备状态匹配的限定词目录时,会自动引用该目录中的资源文件。
- 限定词目录需要开发者自行创建,其可以由一个或多个表征应用场景或设备特征的限定词组合而成,包括移动国家码和移动网络码、语言、文字、国家或地区、横竖屏、设备类型、颜色模式和屏幕密度等维度,限定词之间通过下划线(_)或者中划线(-)连接。
- 在引用rawfile中的资源时,不会根据系统的状态去匹配,rawfile目录中可以直接存放资源文件。
- 二级目录为资源目录
- 用于存放字符串、颜色、浮点数等基础元素,以及媒体等资源文件。
- 当前支持的文件和资源类型如下:
| 文件名 | 资源类型 |
| -------- | -------- |
| color.json | 颜色资源。 |
| float.json | 间距、圆角、字体等资源。 |
| string.json | 字符串资源。 |
| plural.json | 字符串资源。 |
| media目录 | 图片资源 |
### 资源文件使用
在工程中,通过 "$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-application-resource-access.md),了解资源访问的更多细节。
>
> - 类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)。
### 示例
按照如下说明,配置工程resources目录中的json资源文件(如文件不存在,需手工创建)。另外,准备两张不同的图片,分别放置于resources/base/media/my_image.jpg及resources/tablet/media/my_image.jpg路径下。
```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
{
"string":[
{
"name":"my_string",
"value":"tablet"
}
]
}
//resources/tablet/element/color.json
{
"color":[
{
"name":"my_color",
"value":"#0000ff"
}
]
}
//resources/tablet/element/float.json
{
"float":[
{
"name":"my_float",
"value":"80vp"
}
]
}
```
![zh-cn_image_0000001267577314](figures/zh-cn_image_0000001267577314.png)
页面源码如下:
```ts
@Entry
@Component
struct Index {
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Text($r("app.string.my_string"))
.fontSize($r("app.float.my_float"))
.fontColor($r("app.color.my_color"))
Image($r("app.media.my_image"))
.width(100)
.height(100)
}
.width('100%')
.height('100%')
}
}
```
## 系统资源
除了自定义资源,开发者也可以使用系统中预定义的资源(即[分层参数](visual-style-basics.md),同一资源ID在设备类型、深浅色等不同配置下有不同的取值)。
在开发过程中,分层参数的用法与资源限定词基本一致。开发者可以通过"$r('sys.type.resource_id')"的形式引用系统资源。sys代表是系统资源;type代表资源类型,值可以取color、float、string和media;resource_id代表资源id。
可以访问本文"[资源](OHOS_资源分层设计表_V1.0.01.xlsm)",获取OHOS上支持的系统资源ID及其在不同配置下的取值。也可以访问[OpenHarmony/resources代码仓](https://gitee.com/openharmony/resources/tree/master/systemres/main/resources),了解OpenHarmony系统中预置的分层参数资源,可以发现这里目录结构与工程中的resources目录类似,也是通过资源限定词匹配不同的设备或设备状态。
> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:**
> 仅声明式开发范式支持使用分层参数,类Web开发范式不支持。
# 典型场景
当基本的自适应布局无法满足多终端上屏幕的体验要求时,我们可以针对不同终端的屏幕特点,借助栅格系统和媒体查询能力,实现更复杂的布局效果。
## 缩进布局
在上一小节“栅格系统”中,我们介绍了四种不同的设备宽度类型,在不同宽度类型的设备中,默认的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)
}
}
```
# 概览
[短信](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组件](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),显示整个消息列表。
| 默认设备 | 平板 |
| -------- | -------- |
| ![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组件作为父容器,同时包含文本输入框(请访问[文本输入组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/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布局](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/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属性可以设置边框的圆角半径(详见[边框设置](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/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组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/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
}
];
```
结合[栅格组件](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-gridcontainer.md)的定义,考虑我们当前的实际场景,GridContainer的各参数设置如下。
- columns:取默认值(auto),即根据设备尺寸自动设置栅格中的列数。
- sizeType:取默认值(SizeType.Auto),即根据设备类型自动选择。
- gutter:栅格布局列间距,当前场景未使用该参数,设置为0即可。
- margin: 栅格布局两侧间距,在开发消息气泡组件时,已经设置了左右间距,故该属性也配置为0。
栅格中仅包含我们自定义的消息气泡组件,该组件在各类型设备上的参数配置如下,可以通过[useSizeType属性](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/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组件](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)了解这两个组件各个属性的含义及详细用法。
| 默认设备 | 平板 |
| -------- | -------- |
| ![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属性设置四个图标的宽高(详见[尺寸设置](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/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属性,设置各个元素的左右间距(详见[尺寸设置](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/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),了解会话详情页面的详细实现。
# 视觉基础
**虚拟像素单位:vp**
虚拟像素(virtual pixel)是一台设备针对应用而言所具有的虚拟尺寸(区别于屏幕硬件本身的像素单位)。它提供了一种灵活的方式来适应不同屏幕密度的显示效果。
![zh-cn_image_0000001224333864](figures/zh-cn_image_0000001224333864.png)
相同的vp,在不同像素密度的屏幕上,对应不同px,一般称px/vp为像素密度比。像素密度比为当前设备屏幕的dpi/160。
在dpi为160的OpenHarmony设备上,像素密度比为1,则1vp等于1px。
以vp为尺寸标注单位,可使相同元素在不同密度的设备上具有一致的视觉体量,使用px则容易导致体量不一致的问题。
**8vp网格系统**
基于 8vp 为网格的基本单位可以对界面上元素的大小、位置、对齐方式进行更好的规划,构建更有层次感、秩序感,以及多设备上一致的布局效果。一些更小的控件(例如图标)大小也可以对齐 4vp 的网格大小。
![zh-cn_image_0000001291670681](figures/zh-cn_image_0000001291670681.png)
**字体像素单位:fp**
字体像素(font pixel) 大小默认情况下与 vp 相同,即默认情况下 1 fp = 1vp。如果用户在设置中选择了更大的字体,字体的实际显示大小就会在 vp 的基础上乘以 scale 系数,即 1 fp = 1 vp \* scale。
**视觉属性:分层参数**
分层参数是根据使用场景定义的视觉属性ID,通过在不同色彩主题、多种设备上配置不同的数值,实现多设备适配的效果。OpenHarmony的分层参数包含色彩、字体、圆角、间距、阴影、模糊、缩放,并提供了默认实现。设备、应用、服务均可在此基础上管理并自定义不同场景的视觉属性。
![zh-cn_image_0000001251153442](figures/zh-cn_image_0000001251153442.png)
例如,对于不同场景的主色调定义了对应的ID与默认实现
| | | | |
| -------- | -------- | -------- | -------- |
| 主色调 | 高亮色 | \#007DFF | ohos_id_color_emphasize |
| 高亮色反色 | \#006CDE | ohos_id_color_emphasize_contrary |
| 警告色 | \#FA2A2D | ohos_id_color_warning |
| 警示色 | \#FF7500 | ohos_id_color_alert |
| 通讯色 | \#E84826 | ohos_id_color_handup |
| 通讯色 | \#00CB87 | ohos_id_color_connected |
关于OpenHarmony默认提供的所有分层参数,详见:[资源](OHOS_资源分层设计表_V1.0.01.xlsm)
# 色彩
色彩能够赋予应用界面足够的生动性,并给用户提供不同设备上、不同应用间视觉感官上的连续性。同时,合理地运用色彩可以传达关键的状态信息,给予用户即时的状态反馈以及数据可视化呈现。
OpenHarmony采用蓝色作为系统的默认主色调。根据人因研究,对蓝色的接受度无论是在男性还是女性群体中,比例都是最高的。而在世界地域维度,蓝色也是最受欢迎的颜色。更重要的是,对于大多数色觉障碍人士,蓝色依然可以被辨识。
**色值及使用场景**
在色彩设计上,既保持统一的色彩语言,又根据多端不同的使用场景做了调整,带来定制化用户体验。
例如,高亮色ohos_id_color_activated,在不同设备和色彩模式上会有不同具体的值:
| | | | |
| -------- | -------- | -------- | -------- |
| ![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/>用于智能穿戴深色风格。 |
OpenHarmony后续将支持深色模式、浅色模式,及不同主题切换能力。
关于OpenHarmony默认提供的色彩相关分层参数,详见:[资源](OHOS_资源分层设计表_V1.0.01.xlsm)
# 字体
字体直接影响界面的展示效果和用户的阅读效率。优秀的字形设计、统一的多语言字形风格、正确的字体排版,能有效地提升应用使用体验,并传递品牌连续性。
多设备字号层级
选择合适的字号有助于定义内容的信息层级,以及达到内容的可读性。通过研究全场景设备的显示环境、用户使用时环境的差异,OpenHarmony结合分层参数为不同设备形态提供了一套构建信息层级的字号系统。
![zh-cn_image_0000001292686089](figures/zh-cn_image_0000001292686089.png)
关于OpenHarmony默认提供的字体相关分层参数,详见:[资源](OHOS_资源分层设计表_V1.0.01.xlsm)
# 图标
图标是操作系统与用户界面关键的视觉元素之一。图标应当具备直接识别关键信息或语义的特质,帮助用户轻松辨别图标所代表的含义。为了保证用户在不同的设备中视觉体验的一致性,在图标的设计上应当保持应用图标的元素一致,再根据不同的设备匹配对应的图标背板以适应于各种场景。除此之外,图标在颜色的使用上应当遵循符合人因的色彩规则,满足用户阅读的舒适度以及整体界面的和谐程度。对于面状图标与线状图标的使用也应当遵循系统的设计规则,两种样式使用同一种图形结构,降低用户阅读时再次识别的成本。
![zh-cn_image_0000001268334113](figures/zh-cn_image_0000001268334113.jpg)
关于OpenHarmony默认提供的图标库,详见[线上图标库](https://developer.harmonyos.com/cn/design/harmonyos-icon/)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册