界面级一多开发
引言
一次开发多端部署
定义:一套代码工程,一次开发上架,多端按需部署
目标:支撑开发者快速高效的开发多终端设备上的应用
为了实现一多开发的定义与目标,我们需要解决三个问题
- 页面如何适配:不同设备间的屏幕尺寸,色彩风格等存在差异。
- 功能如何兼容:不同设备的系统能力有差异,如智能穿戴设备,是否具有定位能力、智慧屏是否具有摄像头等。
- 工程如何组织:如何实现同一套代码,同时能部署到多种不同的设备上。
今天为大家带来的是界面级一多开发的具体实现。
1. 布局能力
- 自适应布局(Adaptive Layout):元素可以根据相对关系自动变化以适应外部容器变化的布局能力。当前开发框架提炼了其七种自适应布局能力,这些布局也可以独立使用,也可多种布局叠加使用。
- 响应式布局(ResponsiveLayout):元素可以根据特定的特征(如窗口宽度、屏幕方向等)触发变化以适应外部容器变化的布局能力,响应式布局依据断点、媒体查询、栅格等能力实现
1.1 自适应布局
1.1.1 拉伸能力
拉伸能力是指容器组件尺寸发生变化时,增加或减少的空间全部分配给容器组件内指定区域。本例中,页面有中间的内容区以及两侧留白区组成
Row() {
//通过flexGrow和flexShrink属性,将多余的的空间全部分配给图片,将不足的控件全部分配给两侧空白区域
Row().width(150)
.flexGrow(0).flexShrink(1)
Image($r("app.media.image")).width(400)
.flexGrow(1).flexShrink(0)
Row().width(150)
.flexGrow(0).flexShrink(1)
}
1.1.2 均分能力
均分能力是指容器组件发生变化时,增加或减少的空间均匀分配给容器组件内所有空白区域。本例中,父容器尺寸变化过程中,图标及文字的尺寸不变,图标间的间距及图标离左右边缘的的距离同时均等改变。
Column() {
Row() {
Foreach(this.list,(item: number) => {..})
}.width('100%')
//均匀分配父容器主轴方向的剩余空间
.justifyContent(FlexAlign.spaceEvenly)
//同上Row
Row() {..}
}.width(this.rate * 100 + '%')
1.1.3 占比能力
占比能力是指子组件的宽高按照预设比例,随父容器发生变化。本例中,简单的播放器控制栏,其中“上一首”、“播放/暂停”、“下一首”的layoutWeight属性都设置为1,因此他们按照比例“1:1:1”的比例均分父容器主轴方向上的空间。
Row() {
Column() {..}
.layoutWeight(1)//设置子组件在父容器主轴上的布局权重
Column() {..}
.layoutWeight(1)//设置子组件在父容器主轴上的布局权重
Column() {..}
.layoutWeight(1)//设置子组件在父容器主轴上的布局权重
}
.width(this.rate * 100 + "%")
1.1.4 缩放能力
缩放能力是指子组件的宽高按照预设的比例,随容器组件发生变化,且变化过程中子组件的宽高比不变。例如本例中,Column组件随其Flex父组件尺寸变化而缩放的过程中,始终保持预设的宽高比,其中的图片也始终显示正常
Column() {
Column() {
Image($r('app.media.image'))
.width('100%').height('100%')
}
.aspectRatio(1)//固定高宽比
}
.height(this.sliderHeight)
.width(this.sliderWidth)
1.1.5延伸能力
延伸能力是指容器组件内的子组件,按照其在列表的先后顺序,随容器组件变化显示或隐藏。它可以根据显示区域的尺寸,显示不同数量的元素。本例中,当父容器尺寸发生变化时,页面中显示的图标数量随之发生改变。
Row({ space: 10 }) {
//通过List组件实现隐藏能力
List( {space:10 }){...}
.listDirection(Axis.Horizontal)
.width('100%')
}
.width(this.rate * 100 + '%')
1.1.6 隐藏能力
隐藏能力是指容器组件内的子组件,按照其预设的显示优先级,随容器组件尺寸变化显示或隐藏,其中相同显示优先级的子组件同时显示或隐藏。本例中,下面五个按键设置了不同的优先级,父组件宽度变化后,优先显示高优先级的元素。
Row() {
Image($r('app.media.favorite'))
.displayPriority(1)//布局优先级
Image($r('app.media.down'))
.displayPriority(2)//布局优先级
Image($r('app.media.pause'))
.displayPriority(3)//布局优先级
Image($r('app.media.next'))
.displayPriority(2)//布局优先级
Image($r('app.media.list'))
.displayPriority(1)//布局优先级
}
.width(this.rate * 100 + '%')
1.1.7 折行能力
折行能力是指容器组件发生尺寸发生变化,当布局方向尺寸不足以显示完整内容时自动换行。它常用于横竖屏适配或默认设备向平板切换的场景。本例中,当父容器尺寸发生变化时,其中的内容做自适应换行。
Column() {
//通过Flex组件warp参数实现自适应执行
Flex({
warp: FlexWarp,
direction: FlexDirection.Row
}) {
ForEach(this.imageList), (item: Resource) => {
Image(item).width(183).height(138)
})
}
.width(this.rate * 100 + '%')
}
1.2 响应式布局
1.2.1 断点和媒体查询
断点:将窗口宽度划分为不同的范围(即断点),监听窗口尺寸变化,当断点改变时同步调整页面布局。
注意:断点支持自定义,取值范围可修改,下表是常用的四个断点范围
。
媒体查询:媒体查询提供丰富的媒体特征监听能力,可以监听应用显示应用区域变化、横竖屏、深浅色、设备类型等
import { mediaquery } from "@kit.ArkUI";
declare interface BreakpointTypeOption<T> {
xs?: T
sm?: T
md?: T
lg?: T
xl?: T
xxl?: T
}
export class BreakpointType<T> {
options: BreakpointTypeOption<T>;
constructor(option: BreakpointTypeOption<T>) {
this.options = option
}
getValue(currentBreakPoint: string) {
if (currentBreakPoint === 'xs') {
return this.options.xs;
} else if (currentBreakPoint === 'sm') {
return this.options.sm;
} else if (currentBreakPoint === 'md') {
return this.options.md;
} else if (currentBreakPoint === 'lg') {
return this.options.lg;
} else if (currentBreakPoint === 'xl') {
return this.options.xl;
} else if (currentBreakPoint === 'xxl') {
return this.options.xxl;
} else {
return undefined;
}
}
}
interface Breakpoint {
name: string,
size: number,
mediaQueryListener?: mediaquery.MediaQueryListener
}
export enum BreakpointTypeEnum {
SM = 'sm',
MD = 'md',
LG = 'lg',
XL = 'xl'
}
export class BreakpointSystem {
private currentBreakpoint: string = "md";
private breakpoints: Breakpoint[] = [
{ name: 'sm', size: 320 },
{ name: 'md', size: 600 },
{ name: 'lg', size: 840 },
{ name: 'xl', size: 1500 }
];
private updateCurrentBreakpoint(breakpoint: string) {
if (this.currentBreakpoint !== breakpoint) {
this.currentBreakpoint = breakpoint;
AppStorage.setOrCreate<string>('currentBreakpoint', this.currentBreakpoint);
console.log('on current breakpoint: ' + this.currentBreakpoint);
}
}
public register() {
this.breakpoints.forEach((breakpoint: Breakpoint, index) => {
let condition: string;
if (index === this.breakpoints.length - 1) {
condition = '(' + breakpoint.size + 'vp<=width' + ')';
} else {
condition = '(' + breakpoint.size + 'vp<=width<' + this.breakpoints[index + 1].size + 'vp)';
}
breakpoint.mediaQueryListener = mediaquery.matchMediaSync(condition);
breakpoint.mediaQueryListener.on('change', (mediaQueryResult) => {
if (mediaQueryResult.matches) {
this.updateCurrentBreakpoint(breakpoint.name);
}
})
})
}
public unregister() {
this.breakpoints.forEach((breakpoint: Breakpoint) => {
if (breakpoint.mediaQueryListener) {
breakpoint.mediaQueryListener.off('change');
}
})
}
}
以上是媒体查询和断点结合的工具类,目的是为了记录屏幕尺寸的变化,而我们需要使用这个工具类时,可以进行如下操作
//先引入工具类
import { BreakpointSystem } from '@ohos/utils';
struct Index {
..
//给BreakpointSystem实例化
@StorageProp('currentBreakpoint') currentBreakpoint: string = BreakpointTypeEnum.MD;
..
//在页面的生命周期中注册监听和取消监听
aboutToAppear() {
this.breakpointSystem.register();
}
aboutToDisappear() {
this.breakpointSystem.unregister();
}
build() {
1.2.2 栅格布局
根据设备的水平宽度,将不同的屏幕尺寸划分为不同数量的栅格,来实现屏幕的自适应。且栅格和栅格之间可以设置一个间距,本例中为12vp
- 可以调节布局占栅格的数量(设置参数cpan)、偏移量(设置参数offset),来实现栅格的适配。
- 可以修改断点的取值范围,支持启用最多六个断点(设置breakpoints和value参数)。
@Entry
@Component
struct LoginPage {
aStorageProp('currentDeviceSize')currentDeviceSize: string = CommonConstants.SM;
build(){
GridRow({
columns:{sm:4,md:8,lg:12 },
gutter:{x:'12vp'}) {
Gridcol({
span:sm:4,md:6,lg:8
offset:{sm:0,md:1,lg:2} {
Column(){
// Title component
LoginTitle()
//Bottom component
LoginBottom()
}
}
}
.backgroundColor($r(#F1F3F5))
}
onPageShow(){
MultipleDevicesutils.register();
}
}
总结
- 通过设置GridCol的span属性分配组件所占栅格列数
- 通过设置GridCol的offset、GridRow的gutter等属性改变间距实现最佳效果
2. 视觉风格
2.1 分层参数
为了保证各组件有相同风格的默认样式,或者为了保证HarmonyOS系统应用有统一的风格。UX定义了一套系统资源,预置在系统中,开发者可以直接使用
使用了分层参数后,系统选择了深色模式,字体和背景也能自适应
@Entry
@Component
struck Index {
build() {
Row() {
Column() {
Text('分层参数')
.fontColor($r('sys.color.ohos_id_color_primary'))
.fontSize($r('sys.float.ohos_id_text_size_headline3'))
}
}
.backgroundColor($r('sys.color.ohos_id_color_background'))
}
}
2.2 自定义资源
开发者可以在resources目录中通过限定词目录来定义不同设备状态的资源,资源可以按照“key-value”的形式自定义。应用在运行态选择使用某资源时,系统会根据设备状态优先从相匹配的目录中寻找资源。
3. 交互归一
对于不同类型的智能设备,用户可能有不同的交互方式,如通过触摸屏、鼠标、触控板等。针对不同来自不同输入设备的相同输入,通过交互归一提供给开发者统一的API。交互归一后开发者无需关注当前设备和输入设备类型,只需在交互归一事件接口中做逻辑响应即可。
以缩放交互为例,通过多指触控的张合来完成缩放动作,在多设备场景下,缩放交互会出现多种不同的操作输入方式,如表所示。在开发接口上,这些缩放操作都统一为PinchGesture的API事件。
Image()
.scale({ x: this.scaleValue, y: this.scaleValue, z: 1 })
.gesture(
//双指捏合触发该手势事件
PinchGesture({ fingers: 2 })
.onActionStart((event?: GestureEvent)=>{})
.onActionUpdate((event?: GestureEvent)=>{
this.scaleValue = this.pinchValue * event.scale
})
.onActionEnd(()=>{
this.pinchValue = this.scaleValue
})
)
4. IDE多设备预览
IDE预览器支持多设备预览,在平常代码的书写中可以提供开发者更好的预览体验。