作者:wanglei,华为UI编程框架技术专家
HarmonyOS自诞生以来,就是为满足分布式多设备应用场景而设计的,大到智慧屏、车机、平板,小到手机、手表。在多设备场景下进行应用UI界面开发,面临新的困难与挑战,如下图所示:
图1 多设备开发的挑战
为了使UI界面在色彩风格、屏幕尺寸、交互方式和组件功能等差异下仍能够正常显示,无疑需要开发人员花费大量精力在UI适配。开发人员常常需要实现多套界面布局(甚至多套工程),来满足不同设备间的设计差异。即使页面差异不大,也需要进行多设备测试,多次打包编译在设备或者模拟器上运行查看效果。后期维护过程中也需要不断查看不同设备下的兼容性,这些都极大地增加了应用开发者的工作量。
为了解决上述问题,简化开发者在多设备上的开发调试成本,我们提出了一次开发多端部署的设计理念,实现通过一套工程代码,一次开发上架,即可按需部署到不同设备。为了实现这一目标,我们从Harmony系统能力、ArkUI 3.0框架能力和开发工具能力三个方面,为开发者提供了多种适配方法和能力。
下面将一一为大家介绍。
一、HarmonyOS系统能力
首先介绍一下系统层面提供的能力。系统能力无需开发者进行页面调整,也无需进行业务逻辑调整,仅通过增加简单的几行配置描述,即可由系统进行多设备适配。系统能力与开发范式无关,因此在新的UI编程框架下仍可使用。下面我们依次介绍两种系统能力:模拟小窗和平行视界。
1. 模拟小窗
模拟小窗是最常用也是最简单的一种多设备适配方式,通过利用系统的悬浮窗能力,将低分辨率的应用,以悬浮窗口的形式在高分辨率屏幕上进行显示。常见的使用场景是手机应用在平板或PC上运行的场景,如下图所示:
图2 模拟小窗
模拟小窗的使用方式非常简单,只需在项目中config.json文件中增加两条描述,分别配置好应用的目标设备类型和响应的窗口尺寸,即可将低分辨率的应用运行在高分辨率设备上。示例代码如下:
- {
- “app”: {
- ...
- “smartWindowSize”:“360*640”,
- “smartWindowDeviceType”: [
- “tablet”
- ]
- },
- ...
- }
复制代码
这种使用方式能够良好地保证我们应用的展示效果和原始平台效果是一致的,无需开发者进行额外的界面的适配。但是这种方式也有局限,最显著的问题就是没有办法利用全部高分辨率的屏幕,整个屏幕内展示的数据量没有因屏幕分辨率的增加而增大,造成了屏幕上空间的浪费。为解决此问题,系统提供了另一种适配方案——平行视界。
2. 平行视界
系统针对折叠屏、平板设备提供了平行视界的能力,借助分屏显示的思想,将屏幕分为左右两个部分,分别显示应用相关联的两个页面内容。这样每个区域都能够保持良好的界面显示效果,也增加了屏幕内的有效数据量,良好地利用了屏幕显示区域。常用于新闻、购物类的场景,将相关的两个页面同时显示,如下图所示:
图3 平行视界
使用平行视界时,首先需要在config.json文件中配置metaData,声明支持平行视界。同时,还需要增加easygo.json文件进行页面路由关系配置,指导系统进行分屏显示。
平行视界的详细使用说明,可参考官网:
二、ArkUI 3.0框架能力
上述两种是通过配置即可实现的多端适配方案,使用简单,但是使用场景比较受限。为了更加精准地适配多设备界面,ArkUI 3.0框架提供了媒体查询、多态控件、原子布局和栅格系统,方便开发者选择合适的能力进行UI界面构建。
1. 媒体查询
媒体查询是CSS提供的标准能力,是响应式Web设计的关键部分。在新的UI范式中仍保留了此能力,作为最基础的UI响应式设计能力。在新的UI范式中,通过API接口方式对外提供媒体查询能力,可以探查的范围包括页面尺寸、设备分辨率、屏幕方向、页面宽高比、屏幕尺寸、设备类型、屏幕类型和主题模式。开发者可以根据不同的查询结果,进行定制化处理。比如:当屏幕方向变化时,可以调整界面内布局样式和组件显示效果;也可以根据设备类型不同,控制组件的显示和隐藏;并且当查询状态发生变化时,提供事件通知。
图4 媒体查询
2. 多态组件
UI界面构建离不开组件的使用。ArkUI 3.0框架为开发者提供了多态组件,通过组件将不同设备的样式风格和交互方式进行封装,替开发者完成大部分适配相关工作。开发者在使用多态组件时,无需考虑设备差异,只需关注功能实现即可。
下面通过一个示例来看看,相同的一套开发代码在手机、智慧屏和车机上展示的不同效果。
图5 多态组件
示例代码如下:
- Column() {
- Text("手机 / 平板")
- .margin({top: 150})
- Button("普通按钮")
- .margin({top: 40})
- .onClick(() => {
- AlertDialog.show({title: '发现新版本', message: '当前使用移动数据网络,将消耗 XXX MB流量,是否更新',
- primaryButton: {
- value: '立即更新',
- action: () => {}
- },
- secondaryButton: {
- value: '以后再说',
- action: () => {}
- }})
- })
- }
复制代码
产品设计人员常常不满足于使用系统默认样式,希望能够针对不同的平台使用自定义的风格样式。为了避免开发者逐个组件地进行样式调整,ArkUI 3.0框架将组件样式相关设置信息(如颜色、尺寸、圆角弧度、内容文本等)抽象出来形成了一个参数表,按照参数名称和参数值的方式进行映射。UI组件样式属性值都来自这张参数表,开发者和设计人员只需调整参数值,即可调整组件的显示效果。
3. 原子布局
多设备间差异最大的还是屏幕的分辨率,差异分辨率适配离不开自适应布局的能力。针对常见的开发场景,我们提炼了七种原子布局能力。这些布局可以独立使用,也可多种布局叠加使用。下面我们依次介绍这七种原子布局能力:
(1)折行布局:常用于横竖屏适配或手机向平板切换的场景。比如,竖直方向空间减少,但是水平方向上显示区域增加,这时可考虑使用折行布局,将竖直方向的排版变成水平方向。
图6 折行布局
折行布局利用了Flex布局的折行能力实现,配合布局约束设置,即可实现折行排布效果。示例代码如下:
- Flex({direction: FlexDirection.Column, wrap: FlexWrap.Wrap}) {
- Flex({justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center}) {
- Text("First Content")
- .fontColor(Color.White)
- .fontSize(30)
- }
- .constraintSize({minWidth: '50%', minHeight: '50%', maxWidth: 400})
- .backgroundColor(Color.Gray)
- Flex({justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center}) {
- Text("Second Content")
- .fontSize(30)
- .fontColor(Color.White)
- }
- .constraintSize({minWidth: '50%', minHeight: '50%', maxWidth: 400})
- .backgroundColor('rgb(207, 171, 103)')
复制代码
(2)均分布局:常用于内容数量固定、均分显示的场景,比如工具栏、底部菜单栏等。
图7 均分布局
示例代码如下:
- @Entry
- @Component
- struct Index {
- build() {
- Flex({direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceEvenly}) {
- Text('Hello')
- .fontSize(20)
- .borderColor(Color.Red)
- .borderWidth(1)
- Text('World')
- .fontSize(20)
- .borderColor(Color.Red)
- .borderWidth(1)
- Text('Ark')
- .fontSize(20)
- .borderColor(Color.Red)
- .borderWidth(1)
- Text('UI')
- .fontSize(20)
- .borderColor(Color.Red)
- .borderWidth(1)
- }
- .width('100%')
- .height('100%')
- }
- }
复制代码
仅需配置为FlexAlign.SpaceEvenly模式,即可在Flex组件中将内容均分显示。
(3)隐藏布局:是一种比较高级的布局方式,常用于分辨率变化较大,且不同分辨率下显示内容有所差异的场景。主要思想是通过增加或减少显示内容,来保持最佳的显示效果。比如媒体播放控制器,在宽屏场景可以完整显示全部控制项(包含播放、暂停、上一首、下一首、快进、快退,可能还有点赞和收藏按钮等),而在低分辨率场景只保留部分控制项(比如播放、暂停、上一首、下一首按钮)。
图8 隐藏布局
隐藏布局使用方式非常简单,只需通过displayPriority方法设置显示优先级,具有相同优先级的元素会同时显示或隐藏。在进行布局计算时,会根据当前可用空间,计算当前可显示组件进行显示。示例代码如下:
- Row({space: 10}) {
- Text('1')
- .width(100)
- .textAlign(TextAlign.Center)
- .fontSize(40)
- .backgroundColor(Color.Red)
- .displayPriority(2)
- Text('2')
- .width(100)
- .fontSize(40)
- .textAlign(TextAlign.Center)
- .backgroundColor(Color.Red)
- .displayPriority(1)
- Text('3')
- .width(100)
- .textAlign(TextAlign.Center)
- .fontSize(40)
- .backgroundColor(Color.Red)
- .displayPriority(3)
- Text('4')
- .width(100)
- .textAlign(TextAlign.Center)
- .fontSize(40)
- .backgroundColor(Color.Red)
- .displayPriority(1)
- Text('5')
- .width(100)
- .textAlign(TextAlign.Center)
- .fontSize(40)
- .backgroundColor(Color.Red)
- .displayPriority(2)
- }
复制代码
(4)占比布局:是一种很常见的布局,就是根据容器尺寸按照比例进行显示。
图9 占比布局
通过设置百分比尺寸,即可实现比例调整。示例代码如下:
- @Entry
- @Component
- struct Index {
- build() {
- Row() {
- Text('Hello')
- .fontSize(20)
- .width('50%')
- .backgroundColor(Color.Red)
- Text('World')
- .fontSize(20)
- .width('20%')
- .backgroundColor(Color.Yellow)
- Text('Ark')
- .fontSize(20)
- .width('15%')
- .backgroundColor(Color.Green)
- Text('UI')
- .fontSize(20)
- .width('15%')
- .backgroundColor(Color.Gray)
- }
- .width('100%')
- .height('100%')
- }
- }
复制代码
(5)拉伸缩放布局:组件尺寸跟随父容器尺寸变化,产生拉伸或缩放的展示效果。
图10 拉伸缩放布局
通过设置相对容器的比例,实现拉伸或缩放的展示效果。示例代码如下:
- <pre class="language-markup" style="font-family: 微软雅黑;">
- <div><font size="4">Row() {</font></div><code><font size="4"><div><span style="font-family: 微软雅黑;"> Image($r('app.media.background'))</span></div><div><span style="font-family: 微软雅黑;"> .objectFit(ImageFit.Fill)</span></div><div><span style="font-family: 微软雅黑;"> .width('100%')</span></div><div><span style="font-family: 微软雅黑;"> .height('100%')</span></div><div><span style="font-family: 微软雅黑;"> }</span></div></font></code></pre>
- <p></p><div><p></p></div>
复制代码
(6)固定宽高比布局:在拉伸缩放时保持自身宽高比,通常用于图片缩放场景中,可保持图片显示效果正常,避免图片被拉长或压瘪,引起显示失真。
图11 固定宽高比布局
通过设置宽高比,保持按照固定宽高比进行拉伸显示,保障图片不会产生变形。示例代码如下:
- Row() {
- Image($r('app.media.background'))
- .objectFit(ImageFit.Fill)
- .width('100%')
- .height('100%')
- .aspectRatio(1.2)
- }
复制代码
(7)延伸布局:根据尺寸调整内容显示数量,主要是通过像列表这样的能力来实现。
图12 延伸布局
根据宽度不同,显示不同数量的内容,并且可以通过滑动操作,显示出更多内容。示例代码如下:
- @Entry
- @Component
- struct Index {
- private data: string[] = ['Hello', 'World', 'Ark', 'UI', 'This', 'Is', 'Layout', 'Demo']
- build() {
- Flex({direction:FlexDirection.Column, justifyContent: FlexAlign.Center}) {
- List({space: 10}) {
- ForEach(this.data, (item) => {
- ListItem() {
- Text(item)
- .fontSize(20)
- .width(80)
- }.height(80)
- .backgroundColor(Color.Red)
- })
- }
- .listDirection(Axis.Horizontal)
- .width('100%')
- .height(100)
- }
- .width('100%')
- .height('100%')
- }
- }
复制代码
此示例通过List作为布局容器,进行内容线性排布,并支持滑动响应。
4. 栅格系统
ArkUI 3.0框架还提供了完整的栅格系统。所谓栅格系统是来自UX设计中的栅格设计,将屏幕宽度按照不同数量的栅格划分为不同的列,组件的尺寸占用一个或多个栅格。采用这种方式进行设计的布局系统,称之为栅格系统。使用栅格系统,可以屏蔽屏幕分辨率的差异,在不同分辨率的屏幕上保持显示内容的相对尺寸一致。
常见的栅格系统有8栅格系统和12栅格系统,而我们提供的是动态栅格系统,可以根据不同的屏幕尺寸,动态地调整栅格数量。使用动态栅格系统时,不同分辨率的设备使用不同的栅格配置,比如:手机竖屏采用4栅格系统,手机横屏和折叠屏采用8栅格系统,大屏采用12栅格系统。
图13 动态栅格系统
为了方便开发者使用,ArkUI 3.0框架提供了栅格布局容器GridContainer。下面我们来看一个示例,代码如下:
- Stack() {
- GridContainer({sizeType: SizeType.Auto}) {
- Row() {
- Button('OK')
- .fontSize(30)
- .gridSpan(2)
- .useSizeType({lg: 4})
- }
- }
- }
复制代码
栅格布局容器可以设置为固定栅格数,也可以设置为Auto模式。此示例采用的是Auto模式,栅格布局容器会根据宽度动态调整栅格数量。同时通过useSizeType属性方法,可以设置在不同栅格模式下,组件占用的栅格数量。比如:“.useSizeType({lg: 4})”表示在large栅格系统(即12栅格系统)中,Button组件宽度占用4栅格显示。
因此,此示例在手机和平板上的显示效果如下:
图14 显示效果
三、开发工具能力
除了上面的系统能力和ArkUI 3.0框架能力外,我们还从开发工具(DevEco Studio)方面,为开发者提供了各种各样的开发模板,以及多设备预览等能力,减少开发者的开发调试成本,提升开发效率。
1. 开发模板
开发模板主要包括工程模板和卡片模板。
- 工程模板:DevEco Studio预置了丰富的工程模板,可以根据工程向导轻松创建适应于各类设备的工程,并自动生成对应的代码和资源模板。创建工程时,开发者可以挑选合适的工程模板。
- 卡片模板:DevEco Studio提供了多种类型的卡片模板,开发者可以根据需要展示的信息类型灵活选择模板,快速构建服务卡片。
图15 工程模板和卡片模板
2. 多设备预览
DevEco Studio还支持多设备预览能力,开发者可以在同一窗口中,同时查看多种设备下UI界面的展示效果。预览器与真机设备采用相同渲染引擎和UI框架,可以最大程度地做到预览效果与真机运行效果的一致。以下动图展示了多设备预览能力:
四、结束语
实现完美的一次开发多端部署效果,离不开开发者的参与。UI开发框架和系统在实现一次开发多端部署的过程中进行了初步的探索,也期待开发者能反馈更多的多设备UI开发过程中的痛点,以及期待系统框架提供的能力。
欢迎开发者和我们一起,在开源社区将一次开发多端部署的能力丰富起来!