HarmonyOS ArkUI 弹性布局(Flex)从入门到实战完整指南

本文详细介绍 HarmonyOS ArkUI 中 Flex 弹性布局的使用方法,涵盖核心概念、属性配置、常见场景和完整示例代码,适合 HarmonyOS 开发者快速上手。

效果

一、前言

在 HarmonyOS ArkUI 开发中,布局是构建界面的基石。当我们面对多行标签排列、自适应导航栏、剩余空间分配等需求时,传统的 Row/Column 线性布局往往力不从心——需要手动拆分多行、计算宽度,代码冗余且不易维护。

Flex 弹性布局正是解决这类问题的利器。它提供了方向控制、自动换行、空间分配、交叉轴对齐等丰富能力,让复杂排列需求变得简洁直观。

本文将从基础概念讲起,逐步深入到实际案例,帮助你全面掌握 Flex 布局。

二、Flex 基础概念

2.1 什么是 Flex 布局

Flex 布局是一种一维布局模型,能够在主轴方向上灵活分配子组件的空间和间距。其核心概念包括:

概念 说明
主轴(Main Axis) 子组件排列的主要方向,由 direction 属性决定
交叉轴(Cross Axis) 垂直于主轴的方向
Flex 容器 使用 Flex() 创建的布局容器
Flex 子项 容器内的直接子组件

2.2 Flex 容器创建方式

Flex 容器有两种创建方式:

// 方式一:使用 Flex 组件(推荐)
Flex() {
  Text('子项1')
  Text('子项2')
}

// 方式二:使用 .flexDirection() 修饰符
Row() {
  Text('子项1')
  Text('子项2')
}
.flexDirection(FlexDirection.Row)

建议:在大多数场景下使用 Flex() 组件更直观,因为它提供了完整的 Flex 属性配置。

三、核心属性详解

3.1 direction — 主轴方向

控制子组件的排列方向:

Flex({ direction: FlexDirection.Row }) {
  // 水平排列(默认)
}

Flex({ direction: FlexDirection.Column }) {
  // 垂直排列
}

Flex({ direction: FlexDirection.RowReverse }) {
  // 水平反向排列
}

Flex({ direction: FlexDirection.ColumnReverse }) {
  // 垂直反向排列
}

3.2 wrap — 换行控制

控制子组件超出主轴时是否换行:

说明
FlexWrap.NoWrap 不换行(默认),子项可能被压缩
FlexWrap.Wrap 换行,新行向交叉轴正方向排列
FlexWrap.WrapReverse 换行,新行向交叉轴反方向排列
Flex({ wrap: FlexWrap.Wrap }) {
  ForEach(tagList, (tag: string) => {
    Text(tag)
      .padding({ left: 12, right: 12, top: 6, bottom: 6 })
      .backgroundColor('#F2F3F5')
      .borderRadius(16)
  })
}

适用场景:标签云、技能列表、分类筛选等多行排列场景。

3.3 justifyContent — 主轴对齐

控制子组件在主轴上的分布方式:

说明
FlexAlign.Start 从起始端开始排列(默认)
FlexAlign.Center 居中对齐
FlexAlign.End 从末端开始排列
FlexAlign.SpaceBetween 两端对齐,子项间距相等
FlexAlign.SpaceAround 每个子项两侧间距相等
FlexAlign.SpaceEvenly 所有间距(包括两端)完全相等
Flex({ justifyContent: FlexAlign.SpaceBetween }) {
  Text('左侧')
  Text('中间')
  Text('右侧')
}
.width('100%')

3.4 alignItems — 交叉轴对齐

控制子组件在交叉轴上的对齐方式:

Flex({ alignItems: ItemAlign.Center }) {
  // 交叉轴居中对齐
}

Flex({ alignItems: ItemAlign.Stretch }) {
  // 交叉轴拉伸填充(子项未设置交叉轴尺寸时生效)
}

3.5 alignContent — 多行对齐

仅在多行 Flex 布局wrap 不为 NoWrap)中生效,控制行与行之间的分布:

Flex({
  wrap: FlexWrap.Wrap,
  alignContent: FlexAlign.Start
}) {
  // 多行排列,行与行之间从交叉轴起始端开始排列
}

3.6 space — 子项间距

使用 LengthMetrics 精确设置主轴和交叉轴间距:

import { LengthMetrics } from '@kit.ArkUI';

Flex({
  space: {
    main: LengthMetrics.vp(12),  // 主轴间距 12vp
    cross: LengthMetrics.vp(8)   // 交叉轴间距 8vp
  }
}) {
  // 子项之间自动添加间距
}

四、子项弹性属性

Flex 子项可以设置弹性属性,控制其在主轴方向上的尺寸行为:

4.1 flexGrow — 剩余空间分配

Flex({ direction: FlexDirection.Row }) {
  Text('固定')
    .width(80)

  Text('弹性填充')
    .flexGrow(1)  // 占据剩余空间
    .height(40)
}
.width('100%')

当多个子项都设置了 flexGrow 时,剩余空间按比例分配。

4.2 flexShrink — 空间不足时压缩

Flex({ direction: FlexDirection.Row }) {
  Text('长文本内容可能会超出容器')
    .flexShrink(1)  // 允许压缩
    .flexBasis(200)

  Text('固定')
    .width(80)
}
.width('100%')

4.3 flexBasis — 主轴基准尺寸

Flex({ direction: FlexDirection.Row }) {
  Text('基准100')
    .flexBasis(100)  // 主轴基准宽度 100

  Text('基准200')
    .flexBasis(200)  // 主轴基准宽度 200
}

注意flexBasis 在主轴方向上会覆盖 width/height 设置。

4.4 alignSelf — 子项独立对齐

子项可以覆盖容器的 alignItems 设置:

Flex({ alignItems: ItemAlign.Center }) {
  Text('居中')

  Text('顶部对齐')
    .alignSelf(ItemAlign.Start)

  Text('底部对齐')
    .alignSelf(ItemAlign.End)
}
.height(100)

五、完整示例:标签筛选组件

下面通过一个实际案例,展示 Flex 布局在标签筛选场景中的应用:

@ObservedV2
class TagItem {
  @Trace label: string = '';
  @Trace selected: boolean = false;

  constructor(label: string) {
    this.label = label;
  }
}

@Entry
@ComponentV2
struct FlexTagDemo {
  @Local tags: TagItem[] = [
    new TagItem('全部'), new TagItem('推荐'), new TagItem('热门'),
    new TagItem('最新'), new TagItem('收藏'), new TagItem('关注'),
    new TagItem('排行榜'), new TagItem('精选')
  ];

  toggleTag(tag: TagItem): void {
    tag.selected = !tag.selected;
  }

  build() {
    Column({ space: 20 }) {
      Text('Flex 标签筛选示例')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)

      // 使用 Flex 实现自动换行的标签排列
      Flex({ wrap: FlexWrap.Wrap }) {
        ForEach(this.tags, (tag: TagItem) => {
          Text(tag.label)
            .fontSize(14)
            .fontColor(tag.selected ? '#FFFFFF' : '#333333')
            .backgroundColor(tag.selected ? '#4A90D9' : '#F2F3F5')
            .borderRadius(20)
            .padding({ top: 8, bottom: 8, left: 16, right: 16 })
            .margin({ right: 10, bottom: 10 })
            .onClick(() => {
              this.toggleTag(tag);
            })
        }, (tag: TagItem) => tag.label)
      }
      .width('100%')

      // 统计信息
      Text(`已选择 ${this.tags.filter(t => t.selected).length} 个标签`)
        .fontSize(14)
        .fontColor('#999999')
    }
    .padding(20)
    .width('100%')
    .height('100%')
  }
}

示例要点解析

  1. FlexWrap.Wrap:标签超出容器宽度时自动换行,无需手动计算每行放几个。
  2. 子项 margin:通过子项的 margin 控制标签之间的间距,兼容性更好(详见下方注意事项)。
  3. @ObservedV2 + @Trace:使用 V2 状态管理,标签选中状态变化能精确驱动 UI 更新。
  4. ForEach:动态渲染标签列表,配合稳定唯一的 keyGenerator 确保渲染效率。

六、Flex vs Row/Column 对比

场景 推荐布局 原因
单行/单列简单排列 Row / Column 结构清晰,属性简单
需要自动换行 Flex Row 不支持换行
需要剩余空间分配 Flex flexGrow/flexShrink 更灵活
需要多行对齐控制 Flex alignContent 提供行级控制
简单两端对齐 Row + Blank() 不需要引入 Flex 全部能力

七、最佳实践

  1. 优先使用 Flex 处理多行流式布局:标签云、筛选项、分类导航等场景。
  2. alignContent 仅在多行时生效:单行 Flex 设置 alignContent 无效。
  3. flexBasis 优先于 width/height:在主轴方向上,flexBasis 会覆盖对应尺寸属性。
  4. 间距控制两种方式:使用 space 属性需传入 LengthMetrics 类型值(如 LengthMetrics.vp(8)),或直接在子项上设置 margin 控制间距,后者兼容性更好。
  5. 配合 V2 状态管理使用@ObservedV2 + @Trace 能精确追踪子项状态变化,避免不必要的全量刷新。

八、总结

Flex 弹性布局是 ArkUI 中处理多方向、换行、空间分配场景的首选方案。掌握 directionwrapjustifyContentalignItems 四大核心属性,配合 flexGrowflexShrinkflexBasis 子项弹性控制,能够轻松应对绝大多数复杂排列需求。

在实际项目中,建议遵循"简单排列用 Row/Column,复杂排列用 Flex"的原则,既保持代码简洁,又充分利用 Flex 的强大能力。


参考文档弹性布局 (Flex)

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐