鸿蒙开发笔记-19-ArkUI:线性布局、层叠布局、弹性布局、相对布局

151 阅读12分钟

ArkUI简介

ArkUI(方舟UI框架)为应用的UI开发提供了完整的基础设施,包括简洁的UI语法、丰富的UI功能(组件、布局、动画以及交互事件),以及实时界面预览工具等,可以支持开发者进行可视化界面开发。它采用了基于TypeScript扩展的ArkTS语言,其声明式语法更接近自然语义,让开发者能够以一种直观的方式描述UI,无需关心如何实现UI绘制和渲染,大大简化了开发流程,提高了开发效率。

ArkUI提供了多种布局方式, 可以根据实际应用场景选择合适的布局进行页面开发。其中,线性布局、层叠布局、弹性布局和相对布局是比较常用的几种布局方式,下面我们将分别对它们进行详细介绍。

一、线性布局 (Row/Column)

1.1 基本概念

线性布局是开发中最常用的布局方式之一,它通过线性容器RowColumn构建。线性布局是其他布局的基础,其子元素在线性方向上(水平方向和垂直方向)依次排列。Column容器内子元素按照垂直方向排列,Row容器内子元素按照水平方向排列。根据不同的排列方向,开发者可选择使用RowColumn容器创建线性布局。

  • 布局容器:具有布局能力的容器组件,可以承载其他元素作为其子元素,布局容器会对其子元素进行尺寸计算和布局排列。
  • 布局子元素:布局容器内部的元素。
  • 主轴:线性布局容器在布局方向上的轴线,子元素默认沿主轴排列。Row容器主轴为水平方向,Column容器主轴为垂直方向。
  • 交叉轴:垂直于主轴方向的轴线。Row容器交叉轴为垂直方向,Column容器交叉轴为水平方向。
  • 间距:布局子元素的间距。

1.2 常用属性及效果

属性类型说明示例值
spacenumber子元素间距20
alignItemsHorizontalAlign/VerticalAlign交叉轴对齐Center / End
justifyContentFlexAlign主轴对齐SpaceBetween

间距设置

在布局容器内,可以通过space属性设置排列方向上子元素的间距,使各子元素在排列方向上有等间距效果。

对齐方式

通过alignItems属性设置子元素在交叉轴(排列方向的垂直方向)上的对齐方式。且在各类尺寸屏幕中,表现一致。其中,交叉轴为垂直方向时,取值为VerticalAlign类型,水平方向取值为HorizontalAlign类型。alignSelf属性用于控制单个子元素在容器交叉轴上的对齐方式,其优先级高于alignItems属性,如果设置了alignSelf属性,则在单个子元素上会覆盖alignItems属性。

排列方式

通过justifyContent属性设置子元素在容器主轴上的排列方式。可以从主轴起始位置开始排布,也可以从主轴结束位置开始排布,或者均匀分割主轴的空间。常见的取值有:

  • justifyContent(FlexAlign.Start):元素在垂直方向方向首端对齐,第一个元素与行首对齐,同时后续的元素与前一个对齐。
  • justifyContent(FlexAlign.Center):元素在垂直方向方向中心对齐,第一个元素与行首的距离与最后一个元素与行尾距离相同。
  • justifyContent(FlexAlign.End):元素在垂直方向方向尾部对齐,最后一个元素与行尾对齐,其他元素与后一个对齐。
  • justifyContent(FlexAlign.SpaceBetween):垂直方向均匀分配元素,相邻元素之间距离相同。第一个元素与行首对齐,最后一个元素与行尾对齐。
  • justifyContent(FlexAlign.SpaceAround):垂直方向均匀分配元素,相邻元素之间距离相同。第一个元素到行首的距离和最后一个元素到行尾的距离是相邻元素之间距离的一半。
  • justifyContent(FlexAlign.SpaceEvenly):垂直方向均匀分配元素,相邻元素之间的距离、第一个元素与行首的间距、最后一个元素到行尾的间距都完全一样。

1.3 自适应特性

自适应拉伸

常用空白填充组件Blank,在容器主轴方向自动填充空白空间,达到自适应拉伸效果;RowColumn作为容器,只需要添加宽高为百分比,当屏幕宽高发生变化时,会产生自适应效果。例如:

@Entry
@Component
struct BlankExample {
  build() {
    Column() {
      Row() {
        Text('Bluetooth').fontSize(18)
        Blank()
        Toggle({ type: ToggleType.Switch, isOn: true })
      }.backgroundColor(0xFFFFFF).borderRadius(15).padding({ left: 12 }).width('100%')
    }.backgroundColor(0xEFEFEF).padding(20).width('100%')
  }
}

自适应缩放

自适应缩放是指子元素随容器尺寸的变化而按照预设的比例自动调整尺寸,适应各种不同大小的设备。在线性布局中,可以使用以下两种方法实现自适应缩放:

  • 父容器尺寸确定时,使用layoutWeight属性设置子元素和兄弟元素在主轴上的权重,忽略元素本身尺寸设置,使它们在任意尺寸的设备下自适应占满剩余空间。
  • 父容器尺寸确定时,使用百分比设置子元素和兄弟元素的宽度,使他们在任意尺寸的设备下保持固定的自适应占比。

自适应延伸

自适应延伸是指在不同尺寸设备下,当页面的内容超出屏幕大小而无法完全显示时,可以通过滚动条进行拖动展示。通常有以下两种实现方式:

  • List中添加滚动条:当List子项过多一屏放不下时,可以将每一项子元素放置在不同的组件中,通过滚动条进行拖动展示。可以通过scrollBar属性设置滚动条的常驻状态,edgeEffect属性设置拖动到内容最末端的回弹效果。
  • 使用Scroll组件:在线性布局中,开发者可以进行垂直方向或者水平方向的布局。当一屏无法完全显示时,可以在ColumnRow组件的外层包裹一个可滚动的容器组件Scroll来实现可滑动的线性布局。

1.4 代码示例

百分比宽度

Column() {
  // 三等分布局
  Row() {
    Text('33%').width('33%').backgroundColor('#F5DEB3')
    Text('33%').width('33%').backgroundColor('#D2B48C')
    Text('34%').width('34%').backgroundColor('#F5DEB3')
  }
}

权重分配

Row() {
  // 1:2:3比例分配
  Text('1').layoutWeight(1).backgroundColor('#F5DEB3')
  Text('2').layoutWeight(2).backgroundColor('#D2B48C')
  Text('3').layoutWeight(3).backgroundColor('#F5DEB3')
}

二、层叠布局 (Stack)

2.1 基本概念

层叠布局(StackLayout)用于在屏幕上预留一块区域来显示组件中的元素,提供元素可以重叠的布局。层叠布局通过Stack容器组件实现位置的固定定位与层叠,容器中的子元素(子组件)依次入栈,后一个子元素覆盖前一个子元素,子元素可以叠加,也可以设置位置。

2.2 对齐方式

Stack组件通过alignContent参数实现位置的相对移动,支持九种对齐方式,如Alignment.TopStart(左上对齐)、Alignment.Top(顶部水平居中)、Alignment.TopEnd(右上对齐)等。例如:

@Entry
@Component
struct StackExample {
  build() {
    // 层叠布局,通过alignContent属性设置子组件对齐方式为左上角对齐
    Stack({ alignContent: Alignment.TopStart }) {
      // 文本显示组件,先放的组件处于最底层
      Text('Stack').width('90%').height('100%').backgroundColor('#e1dede')
        .align(Alignment.BottomEnd)
      // 文本显示组件,位于上一个元素的上一层
      Text('Item 1').width('70%').height('80%').backgroundColor(0xd2cab3)
        .align(Alignment.BottomEnd)
      // 文本显示组件,位于上一个元素的上一层
      Text('Item 2').width('50%').height('60%').backgroundColor(0xc1cbac)
        .align(Alignment.BottomEnd)
    }.width('100%').height(150).margin({ top: 5 })
  }
}

2.3 Z序控制

Stack容器中子组件的层级除了可按照添加顺序决定,还能通过zIndex()进行手动的设置,zIndex的值越大,层级越高。例如:

Stack ( ) {
  Row ( )
   . width ( 150 )
   . height ( 150 )
   . backgroundColor ( '#255FA7' ) //蓝色
   . shadow ( { radius : 50 } ) . zIndex ( 3 )
  Row ( )
   . width ( 200 )
   . height ( 200 )
   . backgroundColor ( '#E66826' ) //橙色
   . shadow ( { radius : 50 } ) . zIndex ( 2 )
  Row ( )
   . width ( 250 )
   . height ( 250 )
   . backgroundColor ( '#107B02' ) //绿色
   . shadow ( { radius : 50 } ) . zIndex ( 1 )
} . width ( 300 ) . height ( 300 ) . backgroundColor ( '#E5E5E5' ) //灰色

2.4 实战案例:音乐播放器控件

@Entry
@Component
struct PlayerControls {
  @State progress: number = 0.6
  
  build() {
    Stack({ alignContent: Alignment.Center }) {
      // 进度条背景
      Line()
        .width('80%')
        .height(4)
        .backgroundColor('#e0e0e0')
      
      // 进度条前景
      Line()
        .width(`${this.progress * 80}%`)
        .height(4)
        .backgroundColor('#4a90e2')
      
      // 可拖动滑块
      Circle()
        .fill(Color.White)
        .strokeWidth(2)
        .stroke('#4a90e2')
        .radius(12)
        .position({
          x: `${this.progress * 80}%`,
          y: '50%'
        })
        .onTouch((event) => {
          // 处理拖动逻辑
        })
    }
    .height(50)
    .width('100%')
  }
}

三、弹性布局 (Flex)

3.1 基本概念

弹性布局(Flex)提供更加有效的方式对容器中的子元素进行排列、对齐和分配剩余空间。容器默认存在主轴与交叉轴,子元素默认沿主轴排列,子元素在主轴方向的尺寸称为主轴尺寸,在交叉轴方向的尺寸称为交叉轴尺寸。弹性布局在开发场景中用例特别多,比如页面头部导航栏的均匀分布、页面框架的搭建、多行数据的排列等等。

3.2 布局参数

  • 核心三轴系统
graph LR
    A[主轴 Main Axis] --> B[排列方向]
    C[交叉轴 Cross Axis] --> D[对齐方式]
    D --> E[alignItems]
    D --> F[alignSelf]
    G[多行控制] --> H[alignContent]

布局方向 (direction)

通过direction参数可以设置主轴的方向,可选值有FlexDirection.Row(水平方向,元素从左到右排列)、FlexDirection.RowReverse(水平方向,元素从右到左排列)、FlexDirection.Column(垂直方向,元素从上到下排列)、FlexDirection.ColumnReverse(垂直方向,元素从下到上排列)。

主轴排列方式 (justifyContent)

justifyContent参数用于设置子元素在主轴方向的排列方式,可选值有Start(分布在起始端)、Center(居中)、End(分布在结束端)、SpaceBetween(均匀分布,首尾两项两端对齐,中间元素等间距分布)、SpaceAround(均匀分布,所有子元素两侧都留有相同的空间)、SpaceEvenly(均匀分布,所有子元素之间以及首尾两元素到两端的距离都相等)。

交叉轴对齐方式 (alignItems)

alignItems参数用于设置子元素在交叉轴的对齐方式,可选值有Start(启始端对齐)、Center(居中对齐)、End(结束端对齐)、Stretch(拉伸到容器尺寸)、BaseLine(沿文本基线对齐,限于Text文本组件)。

布局换行 (wrap)

弹性布局分为单行布局和多行布局。默认情况下,Flex容器中的子元素都排在一条线(又称“轴线”)上。wrap属性控制当子元素主轴尺寸之和大于容器主轴尺寸时,Flex是单行布局还是多行布局。可选值有FlexWrap.NoWrap(不换行)、FlexWrap.Wrap(换行,每一行子元素按照主轴方向排列)、FlexWrap.WrapReverse(换行,每一行子元素按照主轴反方向排列)。

交叉轴多行排列方式 (alignContent)

Flex容器中包含多行(列)时,可使用alignContent设置多行在交叉轴的排列方式,可选值与justifyContent类似。

3.3 实战:跨设备仪表盘

@Entry
@Component
struct Dashboard {
  @Builder MetricCard(title: string, value: number) {
    Flex({ direction: FlexDirection.Column }) {
      Text(title).fontSize(12).opacity(0.7)
      Text(`${value}`).fontSize(24).marginTop(8)
    }
    .padding(16)
    .backgroundColor('#ffffff')
    .borderRadius(12)
  }

  build() {
    Flex({
      direction: FlexDirection.Row,
      wrap: FlexWrap.Wrap,
      alignItems: ItemAlign.Stretch
    }) {
      this.MetricCard('CPU使用率', 75).flexGrow(1)
      this.MetricCard('内存', 62).flexGrow(1)
      this.MetricCard('温度', 42).flexGrow(2) // 双倍宽度
      this.MetricCard('网络', 28).flexGrow(1)
    }
    .padding(16)
  }
}

四、相对布局 (RelativeContainer)

4.1 基本概念

在应用的开发过程中,经常需要设计复杂界面,此时涉及到多个相同或不同组件之间的嵌套。如果布局组件嵌套深度过深,或者嵌套组件数过多,会带来额外的开销。相对布局(RelativeContainer)支持容器内部的子元素设置相对位置关系,适用于界面复杂场景的情况,对多个子组件进行对齐和排列。子元素支持指定兄弟元素作为锚点,也支持指定父容器作为锚点,基于锚点做相对位置布局。

4.2 锚点设置

为了明确定义锚点,必须为RelativeContainer及其子元素设置ID,用于指定锚点信息。ID默认为__container__,其余子元素的ID通过id属性设置。不设置id的组件能显示,但是不能被其他子组件作为锚点,相对布局容器会为其拼接id,此id的规律无法被应用感知。互相依赖,环形依赖时容器内子组件全部不绘制。同方向上两个以上位置设置锚点,但锚点位置逆序时此子组件大小为0,即不绘制。

4.3 实战:仿聊天界面

@Entry
@Component
struct ChatBubble {
  @Prop isMe: boolean = true
  @Prop message: string
  
  build() {
    RelativeContainer() {
      Text(this.message)
        .padding(12)
        .backgroundColor(this.isMe ? '#dcf8c6' : '#ffffff')
        .borderRadius(12)
        .alignRules({
          top: { anchor: "__container__", align: VerticalAlign.Top },
          right: this.isMe ? 
            { anchor: "__container__", align: HorizontalAlign.End, offset: -16 } : 
            { anchor: "__container__", align: HorizontalAlign.Start, offset: 16 }
        })
        .maxWidth('70%')
      
      // 时间标签
      Text('10:30')
        .fontSize(10)
        .opacity(0.6)
        .alignRules({
          bottom: { anchor: "bubble", align: VerticalAlign.Bottom },
          right: this.isMe ? 
            { anchor: "bubble", align: HorizontalAlign.Start } : 
            { anchor: "bubble", align: HorizontalAlign.End }
        })
        .margin({ top: 4 })
    }
    .height(80)
    .margin({ bottom: 16 })
  }
}

五、总结

线性布局适合用于元素的线性排列,层叠布局可实现元素的重叠效果,弹性布局能够灵活地分配剩余空间,相对布局则适用于复杂界面的设计。在实际开发中,开发者可以根据具体的需求选择合适的布局方式,或者将多种布局方式结合使用,以构建出美观、高效且适配多种设备的用户界面。

5.1 布局渲染性能对比

布局类型渲染复杂度适用场景性能建议
线性布局★☆☆列表/表单避免深层嵌套
层叠布局★★☆重叠元素控制zIndex数量
弹性布局★★☆复杂网格慎用flexShrink
相对布局★★★精确定位减少锚点依赖

5.2 布局选择决策树

graph TD
    A[是否需要元素重叠] --> |是| B(层叠布局 Stack)
    A --> |否| C[是否需要弹性分配空间]
    C --> |是| D(弹性布局 Flex)
    C --> |否| E[是否线性排列]
    E --> |水平/垂直| F(线性布局 Row/Column)
    E --> |复杂相对位置| G(相对布局 RelativeContainer)

我是今阳,如果想要进阶和了解更多的干货,欢迎关注微信公众号 “今阳说” 接收我的最新文章