[鸿蒙开发] 10 - ArkUI中的布局和基本组件

1,523 阅读9分钟

前言:应用开发最离不开的就是UI的构建了,本篇文章只是记录自己的学习心得,对ArkUI中的布局和基本组件有个大概的认识,等需要用到的时候再去查官方文档。

1. ArkUI简介

ArkUI(方舟UI框架)提供了基础设施,包括UI语法、UI功能以及实时界面预览工具等,可以支持开发者进行可视化界面开发。

它提供了两种开发范式:

  • 基于ArkTS的声明式开发范式(简称:声明式开发范式);
  • 兼容JS的类Web开发范式(简称:类Web开发范式);

ArkUI框架示意图: image.png

目前鸿蒙首推:声明式开发范式。采用ArkTS语言,从组件、动画和状态管理三个维度提供UI绘制的能力。

image.png

2. 声明式开发范式概述

基于ArkTS的声明式开发范式的ArkUI框架提供了构建应用UI所必须的能力,主要包括:

  • ArkTS:优选的主力应用开发语言;
  • 布局:定义了组件在界面中的位置,是UI的必要元素;
  • 组件:组件是UI的必要元素,用于构建页面;
  • 页面路由和组件导航:可以通过页面路由实现页面间的跳转;
  • 图形:支持开发者自定义绘制图形;
  • 动画:提供了丰富的动画能力:属性动画、显式动画、转场动画等;
  • 交互事件:点击手势、长按手势、拖动手势、旋转手势等等;

特点不用多说,总结一个字:好!

整体架构如下:

image.png

  • 声明式UI前端:提供了UI开发范式的基础语言规范,并提供内置的UI组件、布局和动画,提供了多种状态管理机制;
  • 语言运行时:提供了针对UI范式语法的解析能力、跨语言调用支持的能力和TS语言高性能运行环境;
  • 声明式UI后端引擎:后端引擎提供了兼容不同开发范式的UI渲染管线,提供多种基础组件、布局计算、动效、交互事件,提供了状态管理和绘制能力;
  • 渲染引擎:提供了高效的绘制能力,将渲染管线收集的渲染指令,绘制到屏幕的能力;
  • 平台适配层:提供了对系统平台的抽象接口,具备接入不同系统的能力,如系统渲染管线、生命周期调度等;

3.布局概述

3.1 布局结构

在鸿蒙开发中,组件会按照布局的要求进行排列,构建成应用的页面。通常为分层结构,如下所示:

image.png

Page表示页面的根节点,Column/Row等为系统组件。针对于不同的页面结构,ArkUI提供了不同的布局组件来帮助开发者实现对应的布局效果。

3.2 布局元素的组成

image.png

  • 组件区域(蓝色):表示组件的大小,width、height属性用于设置组件区域的大小。从图中可以看出组件区域 = border + padding + 组件内容;
  • 组件内容区(黄色):为组件区域大小减去组件的border值,组件内容区大小会作为组件内容进行大小测算时的布局测算限制(???暂时无法理解这句话);
  • 组件内容:(绿色)组件内容本身占用的大小;
  • 组件布局边界:(虚线)组件通过margin设置外编剧,组件布局边界 = 组件区域 + margin;

4. 布局概述

4.1 如何选择页面结构布局?

声明式UI提供了下面9种常见布局,可以根据不同的场景选择使用:

布局应用场景
线性布局 Row Column横向、纵向线性排列
层叠布局 Stack可以实现堆叠效果,比如蒙层
弹性布局 Flex和线性布局类似,区别是弹性布局默认能够让子组件压缩或者拉伸
相对布局 RelativeContainer二维空间中的布局方式,可以基于父组件、其他组件进行布局,在页面元素分布复杂或者通过线性布局会使容器嵌套过深时推荐使用
栅格布局 GridRow GridCol多设备场景下通用的辅助定位工具,将空间分割为有规律的栅格,可以实现不同设备下不同的布局
媒体查询 @ohos.mediaquery可根据不同设备类型或同设备不同状态修改应用的样式
列表 List展示列表数据
网格 Grid网格布局,比如计算器、相册、日历等场景
轮播 Swiper轮播图

4.2 布局位置

position、offset等属性影响了布局容器相对于自身或其他组件的位置:

定位能力使用场景实现方式
绝对定位尽量避免使用绝对定位,在屏幕适配上有缺陷使用position实现绝对定位,设置元素左上角相对于父容器左上角偏移位置
相对定位不脱离文档流,原位置仍然保留,仅相对于原位置进行偏移使用offset可以实现相对定位,设置元素相对于自身的偏移

4.3 对子组件的约束

使用容器组件可以对其中的子组件进行排列,对于子组件也是有一些属性可以设置对自己的约束的:

对子组件的约束能力使用场景实现方式
拉伸容器组件尺寸变化时,增加或减小的空间分配给容器组件内指定区域flexGrow基于父容器的剩余空间控制组件拉伸;flexShrink设置父容器的压缩尺寸来控制组件压缩
缩放子组件按照固定的宽高比进行变化aspectRatio属性指定当前组件的宽高比来控制缩放
占比子组件的宽高按预设的比例随父容器发生变化1.将子组件的宽高设置为父组件宽高的百分比;2.layoutWeight属性,使子元素自适应占满剩余空间;
隐藏指子组件按照预设的显示优先级,随容器变化隐藏或显示通过displayPriority属性来控制组件的显示或隐藏

5. 基本组件

5.1 Button 按钮:

1.创建不包含子组件的按钮

image.png

// label:按钮标题
// type:按钮类型,枚举值
// stateEffect:是否开启点击效果
Button(label?: ResourceStr, options?: { type?: ButtonType, stateEffect?: boolean })

示例:
Button('Ok', { type: ButtonType.Normal, stateEffect: true }) 
  .borderRadius(8) 
  .backgroundColor(0x317aff) 
  .width(90)
  .height(40)

2.创建包含子组件的按钮:

image.png

Button(options?: {type?: ButtonType, stateEffect?: boolean})

示例:
Button({ type: ButtonType.Normal, stateEffect: true }) {
  Row() {
    Image($r('app.media.loading')).width(20).height(40).margin({ left: 12 })
    Text('loading').fontSize(12).fontColor(0xffffff).margin({ left: 5, right: 12 })
  }.alignItems(VerticalAlign.Center)
}.borderRadius(8).backgroundColor(0x317aff).width(90).height(40)

5.2 Radio 单选框:

image.png

// value:单选框名称
// group:所属群组名称
Radio(options: {value: string, group: string})

示例:
  Radio({ value: 'Radio1', group: 'radioGroup' })
    .onChange((isChecked: boolean) => {
      if(isChecked) {
        //需要执行的操作
      }
    })
  Radio({ value: 'Radio2', group: 'radioGroup' })
    .onChange((isChecked: boolean) => {
      if(isChecked) {
        //需要执行的操作
      }
    })

5.3 切换按钮 Toggle

Toggle提供状态按钮样式、勾选框样式和开关样式。

// type: 类型,Button CheckBox Switch
// isOn: 是否选中
Toggle(options: { type: ToggleType, isOn?: boolean })

// 示例:
Toggle({ type: ToggleType.Button, isOn: true }) {
  Text('status button')
  .fontColor('#182431')
  .fontSize(12)
}.width(100).selectedColor(Color.Pink)
Toggle({ type: ToggleType.Checkbox, isOn: true })
  .selectedColor(Color.Pink)
Toggle({ type: ToggleType.Switch, isOn: true })
  .selectedColor(Color.Pink)

image.png

5.4 进度条 Progress

// value: 初始进度值
// total: 进度总长度
// type: 样式,枚举值。Linear 线性样式,Ring 环形无刻度样式,ScaleRing 环形有刻度样式,Eclipse 圆形样式,Capsule 胶囊样式
Progress(options: {value: number, total?: number, type?: ProgressType})

// 示例:
Progress({ value: 24, total: 100, type: ProgressType.Linear }) // 创建一个进度总长为100,初始进度值为24的线性进度条

image.png

5.5 文本展示 Test/Span

使用很简单:

Text('我是一段文本')

Span组件需要写到Text组件内,单独写Span组件不会显示信息,同时Span内容会覆盖Text内容:

Text() {
  Span('我是Span1,').fontSize(16).fontColor(Color.Grey)
    .decoration({ type: TextDecorationType.LineThrough, color: Color.Red })
  Span('我是Span2').fontColor(Color.Blue).fontSize(16)
    .fontStyle(FontStyle.Italic)
    .decoration({ type: TextDecorationType.Underline, color: Color.Black })
  Span(',我是Span3').fontSize(16).fontColor(Color.Grey)
    .decoration({ type: TextDecorationType.Overline, color: Color.Green })
}
.borderWidth(1)
.padding(10)

image.png

文本组件经常会遇到很多样式设置,ArkUI提供了一些属性用于设置文本样式:

  • textAlign: 设置文本对其样式;
  • textOverflow:控制文本超长处理,需要配合maxLines一起使用,默认会折行;
  • lineHeight:控制设置文本行高;
  • decoration:设置文本装饰线(删除线、下划线等)和颜色;
  • baselineOffset:设置文本基线的偏移量;
  • letterSpacing:设置文本字符间距;
  • minFontSize、maxFontSize:设置自适应字体大小,需要搭配使用;
  • textCase:控制文本大小写;
  • copyOption:设置文本是否可复制粘贴;

5.6 文本输入 TextInput TextArea

  • TextInput为单行输入:
TextInput(value?:{placeholder?: ResourceStr, text?: ResourceStr, controller?: TextInputController})
  • TextArea为多行输入:
TextArea(value?:{placeholder?: ResourceStr, text?: ResourceStr, controller?: TextAreaController})

5.7 展示图片 Image

支持网络图片、本地图片。并且也支持多种图片格式:png,jpg,bmp,svg,和gif。

Image(src: PixelMap | ResourceStr | DrawableDescriptor)

同时可以给Image设置属性,达到一些自定义的效果:

  • objectFit: 设置图片的填充模式;
  • interpolation:分辨率较低并放大显示会有锯齿,可以使用interpolation对图片进行插值,让图片显示的更清晰;
  • objectRepeat:用于设置图片的重复样式方式;
  • renderMode:设置图片渲染模式为原色或黑白;
  • sourceSize:设置图片的解码尺寸,可以降低图片的分辨率;
  • colorFilter:可以为图片增加滤镜;
  • syncLoad: 设置同步加载图片,默认是异步加载的;

5.8 视频播放 Video

Video(value: VideoOptions)

其中VideoOptions对象包括以下参数:

  • src: 视频播放源的路径;
  • currentProgressRate: 设置视频播放倍速;
  • previewUrl: 指定视频未播放时的预览图片路径;
  • controller:设置视频控制器,用于自定义控制视频;

另外,Video组件的属性主要用于设置视频的播放形式:

@Component
export struct VideoPlayer {
  private controller: VideoController | undefined;

  build() {
    Column() {
      Video({
        controller: this.controller
      })
        .muted(false) //设置是否静音
        .controls(false) //设置是否显示默认控制条
        .autoPlay(false) //设置是否自动播放
        .loop(false) //设置是否循环播放
        .objectFit(ImageFit.Contain) //设置视频适配模式
    }
  }
}

Video控制器主要用于控制视频的状态,提供的默认控制器支持视频的播放、暂停、进度调整、全屏显示四项基本功能,如果需要可以将默认控制器关掉,然后使用自定义控制器。

5.9 自定义弹窗(CustomDialog)

CustomDialog是自定义弹窗,可用于广告、中奖、警告、软件更新等与用户交互响应的操作。我们可以通过CustomDialogController类显示自定义弹窗。

1.创建自定义弹窗:

@CustomDialog
struct CustomDialogExample {
  cancel?: () => void
  confirm?: () => void
  controller: CustomDialogController

  build() {
    Column() {
      Text('我是内容').fontSize(20).margin({ top: 10, bottom: 10 })
      Flex({ justifyContent: FlexAlign.SpaceAround }) {
        Button('cancel')
          .onClick(() => {
            this.controller.close()
            if (this.cancel) {
              this.cancel()
            }
          }).backgroundColor(0xffffff).fontColor(Color.Black)
        Button('confirm')
          .onClick(() => {
            this.controller.close()
            if (this.confirm) {
              this.confirm()
            }
          }).backgroundColor(0xffffff).fontColor(Color.Red)
      }.margin({ bottom: 10 })
    }
  }
}

2.页面内使用

@Entry
@Component
struct CustomDialogUser {
  // 创建构造器和装饰器呼应相连
  dialogController: CustomDialogController = new CustomDialogController({
    builder: CustomDialogExample({
      cancel: ()=> { this.onCancel() },
      confirm: ()=> { this.onAccept() },
    }),
  })

  onCancel() {
    console.info('Callback when the first button is clicked')
  }

  onAccept() {
    console.info('Callback when the second button is clicked')
  }

  build() {
    Column() {
      Button('click me')
        .onClick(() => {
          // 点击事件绑定的组件让弹窗弹出
          this.dialogController.open()
        })
    }.width('100%').margin({ top: 5 })
  }
}

image.png