鸿蒙纪·梦始卷#02 | 从计数器认识布局基础

1,279 阅读7分钟

《鸿蒙纪元》张风捷特烈 计划打造的一套 HarmonyOS 开发系列教程合集。致力于创作优质的鸿蒙原生学习资源,帮助开发者进入纯血鸿蒙的开发之中。本系列的所有代码将开源在 HarmonyUnit 项目中:

github: github.com/toly1994328…
gitee: gitee.com/toly1994328…


本文是《鸿蒙纪·梦始卷》 的第二章,上一篇介绍了环境搭建和初始项目,本篇将真正进入代码编写中,通过一个简单的计数器小功能,初步体验鸿蒙开发中的布局表现,以及交互行为:

  • [1]. 了解基础布局特性
  • [2]. 了解简单的交互行为和改变数据状态
  • [3]. 简单了解组件的封装与拆分

一、从计数器认识简单的布局特性

HarmonyOS 应用开发使用的是 ArkUI 视图框架进行开发的,它也是一个 声名式 的 UI 框架。使用 TypeScript 的变种 ArkTs 语言进行开发。如果你有 FlutterReactCompose 的开发经验,那么对 ArkUI 将驾轻就熟。

计数器是我非常喜欢的一个小案例,点击按钮使界面上的数字增加。它虽然非常简单,但麻雀虽小五脏俱全。从中可以了解界面编程的基本布局特性,也可以了解事件的回调以及界面数据更新的方式:


1. 列布局 Column 的背景和宽高

首先通过一个小案例看一下基本的布局特性,比如排列背景色边距尺寸等。

  • ArkUI 中背景色是所有组件的通用属性,通过 backgroundColor 方法可以指定组件颜色。
  • Column 组件可以容纳多个组件,在自身区域内排布。
@Component
struct Index {
  build() {
    Column() {
      Text('计数器')
        fontSize(28)
        .fontWeight(FontWeight.Bold)
        .backgroundColor('#440000ff')
    }
    .backgroundColor('#44ff0000')
  }
}

上面代码中,让 Column 容纳一个 Text 文本,可以看出 Column 的区域尺寸默认是其容纳组件的尺寸:


  • ArkUI 中的尺寸也是所有组件的通用属性,通过 widthheight 设置。

如下所示,将 Column 的 width 设置为 '100%',它将会横向铺满(红色示意)。并且其内容组件默认会 横向居中

当把 Column 的 height 设置为 '100%' 时,可以看到红色区域会占满全屏,这也表示 Column 自身的区域尺寸。


2. 对齐方式与边距

Column 组件有控制内容组件对齐方式的属性,比如这里想让文字水平方向居左,可以设置 alignItems(HorizontalAlign.Start) 。Column 具体的布局特性,将在以后详细展开。


现在想让文字离左边和上边有点边距,看上去不那么拥挤:

  • ArkUI 中的边距也是所有组件的通用属性,通过 paddingmargin 设置内边距和外边距。

padding 表示内边距,内边距属于组件的区域尺寸,如下所示,Text 的区域被扩张:

而外边距 margin 不会影响 Text 的实际区域尺寸,只是进行偏移:

如果去除 Text 颜色,两者在视觉表现上没有区别,但实际的布局效果是不同的。所以,在视图布局的学习过程中,设置背景色产看布局区域,可以很好地辅助理解布局特性。


3 计数器布局

Column 中可以容纳多个组件,如下所示,其中可以再放置一个粉色的 Column,此时高度 100% 会让粉色区域高度和父区域相同,超过屏幕溢出的部分将不可见。

  • 注: 这里可以将 .height('100%') 改为 .layoutWeight(1) 让粉色区域填充剩余高度区域:

在粉色的 Column 中,放置两个文字组件,如下所示:

现在希望让两行文字在 竖直方向 居中显示,可以设置 Column 的 justifyContent 为 FlexAlign.Center 。此时去掉测试区域的颜色,就可以得到期望的计数器布局效果:


4.学习小技巧

到这里标记一个小的里程碑,下面是 Index.ets 的所有代码。可以通过 git 提交一下,大家可以通过每次的提交,记录当前的完整项目;同时,通过里程碑,也可以回顾自己开发过程的点滴成长。我也会在每个小里程碑放上一个提交记录,大家可以直接访问连接查看代码: 计数器-布局-v1

@Entry
@Component
struct Index {
  @State message: string = '鸿蒙纪元';

  build() {
    Column() {
      Text('计数器')
        .fontSize(28)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 20, left: 20 })
      Column() {
        Text('下面是你点击按钮的次数:')
          .fontSize(18)
        Text('0')
          .fontSize(36)
          .fontColor('#2e3032')
          .margin({ top: 10 })
      }
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
    }
    .alignItems(HorizontalAlign.Start)
    .width('100%')
    .height('100%')
  }
}

由于布局组件的属性和方法比较多,大家在学习过程中可以有意的记录一下遇到的知识点,有利于梳理、积累、沉淀:


二、层叠布局与按钮、图标展示

这一小节我们将实现如下的布局界面,并实现按钮点击时增加计数器的数值:

打开时点击按钮

1. 按钮和图标的展示

通过 Button 可以展示按钮,type 设置为 ButtonType.Circle 以圆形展示;stateEffect 为 true 时,点击可以有颜色加深的反馈效果。
图标通过 SymbolGlyph 组件展示,可以加载系统内置的图标资源。所以得资源可以在官方的 harmonyos-symbol 中查看。使用 fontSize 设置大小、fontColor 设置颜色、fontWeight 设置字重。

Button({ type: ButtonType.Circle, stateEffect: true }) {
  SymbolGlyph($r('sys.symbol.plus'))
    .fontSize(24)
    .fontColor([Color.White])
    .fontWeight(FontWeight.Bold)
}.width(56).height(56)

2. Stack 层叠布局

层叠布局可以让对个组件自下而上层层排列,通过 alignContent 可以设置子组件的对齐方式,比如这里设置为右下方 BottomEnd,按钮也就对齐到右下。可以通过对按钮设置外边距空出右侧和下方的间隔,从而完成想要的布局效果:


3. 简单的状态变化

我们称界面上随交互变化的数据为 可变状态量,比如当前需求下,可变状态量是呈现的数直,会随着按钮点击交互而增加。组件内部可以通过 @State 声明可变状态量,即如下的 counter 变量。按钮可以通过 onClick 设置点击的回调事件监听,只需要触发 counter 增加,界面上的数值就会自动更新渲染:

struct Index {
  @State counter: number = 0;

  Button(//略同...
    .onClick(()=>this.counter++ )

这里按钮触发的逻辑只有一行,这样写起来比较简单。如果是比较复杂的逻辑,建议提取一个函数(方法) 进行处理,更利于分离视图构建逻辑行为数据逻辑 的代码,比如提取 increment 方法处理数据增加的业务逻辑; 按钮点击时触发 this.increment 即可。这样如果行为需求有什么变更,直接在 increment 方法中处理即可,方便定位代码和拓展功能:

increment(): void {
  this.counter++;
}

  Button(//略同...
    .onClick(()=>this.increment() )

大家可以继续梳理一下已经遇到的知识点,计数器的基本功能完成了,可以再提交一个小里程碑 计数器-v2-基本功能


三、组件封装

目前项目中,所有的代码都放在一块,点击这里查看 Index.ets。嵌套结构看起来可读性很差。随着后面功能需求的增加,代码量会激增。学会合理地拆分组件,进行独立维护,是个很好的习惯。

比如头部标题栏是一个相对独立的区域,以后可能有被修改复用的可能,就可以将它视为一个组件来单独维护。使用 @Component 声明一个组件,在其中的 build 方法中处理构建逻辑:


1. 拆分 AppBar

应用的头部栏可以被其他界面复用,也会遇到修改的需求。适合拆分出来单独处理,比如下面使用 @Component 声明一个 AppBar 组件,构造是可以传入 title 字符串指定展示的标题文字。这里通过 Row 组件可以让若干个子组件横向排列,通过 justifyContent(FlexAlign.Center) 可以让子组件居中对其:

@Component
struct AppBar {
  private title: string = '';

  build() {
    Row() {
      Text(this.title)
        .fontSize(20).fontWeight(FontWeight.Bold)
        .fontColor($r('sys.color.white'))
    }
    .backgroundColor('#317bd4')
    .width('100%')
    .height(56)
    .padding({ left: 20, right: 20 })
    .justifyContent(FlexAlign.Center)
  }
}

如果其他界面中也需要构建类似的头部栏,每次都写一遍的话很不优雅。布局结构是类似的,无非是一些配置属性的差异,可以通过构造传参来指定数据。如下所示,AppToolBar 可以通过 title 指定文字标题:

如果你有其他的自定义属性,可以自行添加,比如背景色、高度等。


2. 增加首尾插槽组件

对于组件构建来说,设置插槽可以增加布局的灵活性。比如 AppBar 的首尾部分,使用者想要自己传入组件,来满足更多样化的使用场景:

那左侧的 leading 来说,通过 @BuilderParam 声明是组件构建者参数;通过 leadingBuilder 设置默认的构建器。构建器需要通过 @Builder 注解标识:

@Component
struct AppBar {
  private title: string = '';

  @Builder
  leadingBuilder() {}

  @Builder
  tailingBuilder() {}

  @BuilderParam leading: () => void = this.leadingBuilder;
  @BuilderParam tailing: () => void = this.tailingBuilder;

  build() {
    Row() {
      this.leading()
      Text(this.title)
        .fontSize(20).fontWeight(FontWeight.Bold)
        .fontColor($r('sys.color.white'))
      this.tailing()
    }
    .backgroundColor('#317bd4')
    .width('100%')
    .height(56)
    .padding({ left: 20, right: 20 })
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

这样在 AppBar 构造时,可以通过 leading 参数,让外界构建具体内容,插入到 AppBar 结构的内部。通过这种方式,我们就可以封装一些通用的结构,便于复用:

到此,我们遇到的几个注解也可以整理一些,进入知识树中。知识树在一开始并不需要尽善尽美,它是你点滴的积累。可以在后续不断随着知识累计,调节各个枝点。这里再提交一个小里程碑 计数器-v3-组件简单封装


尾声

到这里,我们就已经完成了一个简单的计数器功能,并简单地认识鸿蒙开发中界面的布局已经组件封装。 下一章我们将继续完善当前的计数器,了解沉浸标题栏、资源文件的使用、国际化的处理、布局调试工具的使用,敬请期待 ~
更多文章和视频知识资讯,大家可以关注我的公众号、掘金和 B 站 。