提交 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) **说明:**
> 通过窗口尺寸调整显示更加“可靠”,因为顶部状态栏、三键导航等可能占据部分屏幕空间,另外部分设备上的应用可能以窗口形式显示。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册