HarmonyOS一次开发多端部署三巨头之界面级一多开发

界面级一多开发

  • 引言
    • [1. 布局能力](#1. 布局能力)
      • [1.1 自适应布局](#1.1 自适应布局)
        • [1.1.1 拉伸能力](#1.1.1 拉伸能力)
        • [1.1.2 均分能力](#1.1.2 均分能力)
        • [1.1.3 占比能力](#1.1.3 占比能力)
        • [1.1.4 缩放能力](#1.1.4 缩放能力)
        • 1.1.5延伸能力
        • [1.1.6 隐藏能力](#1.1.6 隐藏能力)
        • [1.1.7 折行能力](#1.1.7 折行能力)
      • [1.2 响应式布局](#1.2 响应式布局)
        • [1.2.1 断点和媒体查询](#1.2.1 断点和媒体查询)
        • [1.2.2 栅格布局](#1.2.2 栅格布局)
    • [2. 视觉风格](#2. 视觉风格)
      • [2.1 分层参数](#2.1 分层参数)
      • [2.2 自定义资源](#2.2 自定义资源)
    • [3. 交互归一](#3. 交互归一)
    • [4. IDE多设备预览](#4. IDE多设备预览)

引言

一次开发多端部署
定义 :一套代码工程,一次开发上架,多端按需部署
目标 :支撑开发者快速高效的开发多终端设备上的应用

为了实现一多开发的定义与目标,我们需要解决三个问题

  1. 页面如何适配:不同设备间的屏幕尺寸,色彩风格等存在差异。
  2. 功能如何兼容:不同设备的系统能力有差异,如智能穿戴设备,是否具有定位能力、智慧屏是否具有摄像头等。
  3. 工程如何组织:如何实现同一套代码,同时能部署到多种不同的设备上。

今天为大家带来的是界面级一多开发的具体实现。

1. 布局能力

  1. 自适应布局(Adaptive Layout) :元素可以根据相对关系自动变化以适应外部容器变化的布局能力。当前开发框架提炼了其七种自适应布局能力,这些布局也可以独立使用,也可多种布局叠加使用。
  2. 响应式布局(ResponsiveLayout):元素可以根据特定的特征(如窗口宽度、屏幕方向等)触发变化以适应外部容器变化的布局能力,响应式布局依据断点、媒体查询、栅格等能力实现

1.1 自适应布局

1.1.1 拉伸能力

拉伸能力是指容器组件尺寸发生变化时,增加或减少的空间全部分配给容器组件内指定区域。本例中,页面有中间的内容区以及两侧留白区组成

TypeScript 复制代码
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 均分能力

均分能力是指容器组件发生变化时,增加或减少的空间均匀分配给容器组件内所有空白区域。本例中,父容器尺寸变化过程中,图标及文字的尺寸不变,图标间的间距及图标离左右边缘的的距离同时均等改变。

TypeScript 复制代码
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"的比例均分父容器主轴方向上的空间。

TypeScript 复制代码
Row() {
    Column() {..}
    .layoutWeight(1)//设置子组件在父容器主轴上的布局权重
    Column() {..}
    .layoutWeight(1)//设置子组件在父容器主轴上的布局权重
    Column() {..}
    .layoutWeight(1)//设置子组件在父容器主轴上的布局权重
}
.width(this.rate * 100 + "%")
1.1.4 缩放能力

缩放能力是指子组件的宽高按照预设的比例,随容器组件发生变化,且变化过程中子组件的宽高比不变。例如本例中,Column组件随其Flex父组件尺寸变化而缩放的过程中,始终保持预设的宽高比,其中的图片也始终显示正常

TypeScript 复制代码
Column() {
    Column() {
        Image($r('app.media.image'))
        .width('100%').height('100%')
    }
    .aspectRatio(1)//固定高宽比
}
.height(this.sliderHeight)
.width(this.sliderWidth)
1.1.5延伸能力

延伸能力是指容器组件内的子组件,按照其在列表的先后顺序,随容器组件变化显示或隐藏。它可以根据显示区域的尺寸,显示不同数量的元素。本例中,当父容器尺寸发生变化时,页面中显示的图标数量随之发生改变。

TypeScript 复制代码
Row({ space: 10 }) {
    //通过List组件实现隐藏能力
    List( {space:10 }){...}
    .listDirection(Axis.Horizontal)
    .width('100%')
}
.width(this.rate * 100 + '%')
1.1.6 隐藏能力

隐藏能力是指容器组件内的子组件,按照其预设的显示优先级,随容器组件尺寸变化显示或隐藏,其中相同显示优先级的子组件同时显示或隐藏。本例中,下面五个按键设置了不同的优先级,父组件宽度变化后,优先显示高优先级的元素。

TypeScript 复制代码
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 折行能力

折行能力是指容器组件发生尺寸发生变化,当布局方向尺寸不足以显示完整内容时自动换行。它常用于横竖屏适配或默认设备向平板切换的场景。本例中,当父容器尺寸发生变化时,其中的内容做自适应换行。

TypeScript 复制代码
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 断点和媒体查询

断点 :将窗口宽度划分为不同的范围(即断点),监听窗口尺寸变化,当断点改变时同步调整页面布局。
注意 :断点支持自定义,取值范围可修改,下表是常用的四个断点范围

媒体查询:媒体查询提供丰富的媒体特征监听能力,可以监听应用显示应用区域变化、横竖屏、深浅色、设备类型等

TypeScript 复制代码
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');
      }
    })
  }
}

以上是媒体查询和断点结合的工具类,目的是为了记录屏幕尺寸的变化,而我们需要使用这个工具类时,可以进行如下操作

TypeScript 复制代码
//先引入工具类
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参数)。
TypeScript 复制代码
@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定义了一套系统资源,预置在系统中,开发者可以直接使用

使用了分层参数后,系统选择了深色模式,字体和背景也能自适应

TypeScript 复制代码
@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事件。

TypeScript 复制代码
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预览器支持多设备预览,在平常代码的书写中可以提供开发者更好的预览体验。

相关推荐
小付同学呀1 分钟前
前端快速入门学习4——CSS盒子模型、浮动、定位
前端·css·学习
鸿蒙布道师9 分钟前
鸿蒙NEXT开发数值工具类(TS)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
OpenTiny社区2 小时前
TinyPro 中后台管理系统使用指南——让页面搭建变得如此简单!
前端·vue.js·开源
我有一只臭臭2 小时前
webpack配置解析
前端·webpack
我有一只臭臭2 小时前
Vue中webpack的使用
前端·vue.js·webpack
郭涤生2 小时前
微服务系统记录
笔记·分布式·微服务·架构
今天也想MK代码2 小时前
ReFormX:现代化的 React 表单解决方案 - 深度解析与最佳实践
前端·react.js·性能优化
醋醋3 小时前
Vue2源码记录3
前端·vue.js
dleei3 小时前
react入门(上)
前端·react.js·前端框架