孜尊 发表于 2026-2-26 18:30:02

鸿蒙应用开发UI基础第十一节:弹性布局Flex核心讲解与实战演示

【学习目标】


[*]理解 Flex 布局的主轴、交叉轴、换行规则和方向反转,掌握其与线性布局(Row/Column)的区别与联系;
[*]掌握 flexGrow、flexShrink、flexBasis 三大核心属性的计算逻辑与使用场景;
[*]掌握 FlexOptions 中核心配置规则,以及 Flex 主轴/交叉轴间距的精准配置方法;
[*]能够使用 Flex 实现常见的自适应布局,如“顶部搜索栏+中间推荐标签+底部操作区”的搜索推荐结构。
一、Flex 布局基础与 FlexOptions 核心属性

Flex 布局的核心配置通过 FlexOptions 接口实现,它是控制 Flex 布局行为的唯一入口,包含方向、换行、对齐、间距四大类属性,是 Flex 与 Row/Column 最核心的区别所在。
1.1 FlexOptions 核心属性说明

属性名类型核心作用可选值/枚举生效条件默认值directionFlexDirection定义主轴方向Row、RowReverse、Column、ColumnReverse所有场景RowwrapFlexWrap定义主轴换行规则NoWrap(不换行)、Wrap(自动换行)、WrapReverse(反向换行)所有场景NoWrapspace{main: LengthMetrics, cross: LengthMetrics}双轴间距配置main:主轴间距;cross:交叉轴间距(仅换行生效)无(默认0)alignContentFlexAlign交叉轴多行整体对齐(Flex独有)Start、Center、End、SpaceBetween、SpaceAround、SpaceEvenlywrap = Wrap/WrapReverseStartalignItemsItemAlign交叉轴单行子项对齐Auto、Start、Center、End、Baseline、Stretch所有场景Center1.2 Flex 与 Row/Column 的核心区别(聚焦 FlexOptions 独有能力)

对比维度Flex 布局(FlexOptions)Row/Column 布局方向控制支持FlexOptions.direction动态切换,覆盖正序/反转、横向/纵向全组合方向固定(Row仅横向、Column仅纵向),无反转能力,无对应配置项换行能力支持FlexOptions.wrap配置自动/反向换行,原生适配流式布局无换行能力,子组件超容器时直接溢出/压缩,无对应配置项多行对齐能力独有FlexOptions.alignContent,支持交叉轴多行整体对齐+间距分配无多行对齐能力,仅支持单行子项对齐,无对应配置项单行对齐配置统一通过FlexOptions.alignItems配置,支持ItemAlign全枚举值,默认Center无统一配置项,Row用VerticalAlign、Column用HorizontalAlign,枚举值互通,Row默认Center、Column默认Start间距配置能力支持FlexOptions.space双轴间距,同时控制主轴/交叉轴间距仅支持主轴单一间距,交叉轴无原生间距配置,无对应双轴配置项1.3 选型建议


[*]简单布局(标题栏、按钮组、单行列表):优先用 Row/Column(轻量、高性能,无额外布局开销);
[*]复杂布局(流式标签、多行列表、需动态切换排列方向):必须用 Flex + FlexOptions 组合,发挥其配置化优势;
[*]性能敏感场景(长列表项、高频刷新组件):优先用 Row/Column,避免 Flex 因 FlexOptions 多配置项带来的二次布局性能损耗。
二、工程结构

本节创建新工程 FlexApplication(基于鸿蒙 API 12+ / Stage 模型),用于编写和运行 Flex 布局相关演示代码
FlexApplication/
├── AppScope/
├── entry/
│   ├── src/
│   │   ├── main/
│   │   │   ├── ets/
│   │   │   │   ├── entryability/
│   │   │   │   │   └── EntryAbility.ets
│   │   │   │   └── pages/
│   │   │   │       ├── Index.ets            # 演示入口页(按钮导航)
│   │   │   │       ├── FlexBasicPage.ets    # Flex基础语法+FlexOptions核心属性演示
│   │   │   │       ├── FlexGrowPage.ets   # flexGrow(剩余空间分配)演示
│   │   │   │       ├── FlexShrinkPage.ets   # flexShrink(空间不足压缩)演示
│   │   │   │       ├── FlexBasisPage.ets    # flexBasis(基准尺寸)演示
│   │   │   │       ├── FlexReversePage.ets# FlexOptions方向反转(direction)演示
│   │   │   │       └── FlexComplexPage.ets# 综合案例(搜索推荐布局)
│   │   │   ├── resources/
│   │   │   └── module.json5
│   └── build-profile.json5
└── build-profile.json52.2 演示入口(Index.ets)

import router from '@ohos.router';

// 定义按钮数据类型
interface ButtonItem {
name: string,// 按钮显示文本
url: string    // 跳转页面路径(需与pages目录下的文件路径一致)
}

@Entry// 页面入口装饰器,标记当前组件为应用入口
@Component// 组件装饰器,标记当前结构体为UI组件
struct Index {
// 按钮列表数据 - 状态变量
@State buttonList: ButtonItem[] = [
    { name: "Flex 基础(FlexOptions)", url: "pages/FlexBasicPage" },
    { name: "flexGrow 放大", url: "pages/FlexGrowPage" },
    { name: "flexShrink 缩小", url: "pages/FlexShrinkPage" },
    { name: "flexBasis 基准尺寸", url: "pages/FlexBasisPage" },
    { name: "FlexOptions 方向反转", url: "pages/FlexReversePage" },
    { name: "Flex 综合案例(搜索推荐)", url: "pages/FlexComplexPage" }
];

build() {
    // 外层Column:垂直排列所有按钮,space:20 表示子组件间距20vp
    Column({ space: 20 }) {
      // 页面标题
      Text("Flex 布局(FlexOptions)演示")
      .fontSize(28)
      .fontWeight(FontWeight.Bold)
      .fontColor($r('app.color.text_primary'))// 引用color.json中的颜色资源
      .margin({ bottom: 40 });

      // 循环渲染按钮
      ForEach(
      this.buttonList,// 数据源
      (item: ButtonItem) => {// 渲染函数:每个item生成一个按钮
          Button(item.name)
            .width('80%')
            .height(50)
            .backgroundColor($r('app.color.bg_blue_light'))
            .fontColor($r('app.color.text_white'))
            .borderRadius(8)
            .onClick(() => {// 点击事件:跳转到对应页面
            router.pushUrl({ url: item.url });
            });
      },
      (item:ButtonItem, index: number) => `${item.name}-${index}` // 唯一标识:name+索引
      );
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)// 垂直居中所有子组件
    .backgroundColor($r('app.color.bg_page'));
}
}运行效果


2.3 颜色资源配置(color.json)

文件路径为 entry/src/main/resources/base/color/color.json
{
"color": [
    { "name": "text_primary", "value": "#333333" },
    { "name": "text_secondary", "value": "#666666" },
    { "name": "text_white", "value": "#FFFFFF" },
    { "name": "bg_page", "value": "#F5F5F5" },
    { "name": "bg_white", "value": "#FFFFFF" },
    { "name": "bg_blue_light", "value": "#1677FF" },
    { "name": "bg_blue_light_ultra", "value": "#E8F3FF" },
    { "name": "bg_red_light", "value": "#FF4D4F" },
    { "name": "bg_green_light", "value": "#52C41A" },
    { "name": "bg_gray_light", "value": "#E0E0E0" },
    { "name": "bg_gray_ultra_light", "value": "#F0F0F0" },
    { "name": "bg_purple_light", "value": "#722ED1" }

]
}三、基础示例代码(FlexBasicPage.ets)

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

@Entry
@Component
struct FlexBasicPage {
// 演示子组件列表
private rowItems: string[] = ["元素1", "元素2", "元素3", "元素4"];
private colItems: string[] = ["元素A", "元素B", "元素C"];

build() {
    Column({ space: 15 }) {
      // 页面标题
      Text("FlexOptions 核心属性演示")
      .fontSize(24)
      .fontWeight(FontWeight.Bold)
      .width('100%')
      .textAlign(TextAlign.Center)
      .margin({ bottom: 10 });

      // 示例1:Flex核心能力 - 横向+自动换行+双轴间距(Row无此能力)
      Text("示例1:direction=Row + wrap=Wrap + 双轴space")
      .fontSize(14)
      .fontColor($r('app.color.text_secondary'))
      .width('90%')
      .margin({ bottom: 5 });

      Flex({
      direction: FlexDirection.Row,      // 主轴:横向正序
      wrap: FlexWrap.Wrap,               // 核心:自动换行(Flex独有)
      alignItems: ItemAlign.Center,      // 交叉轴单行居中
      alignContent: FlexAlign.Start,       // 交叉轴多行起始对齐
      space: {                           // 双轴间距
          main: LengthMetrics.vp(10),      // 主轴横向间距10vp
          cross: LengthMetrics.vp(10)      // 交叉轴纵向间距10vp(仅换行生效)
      }
      }) {
      // 4个120vp宽的子组件,总宽度超容器,触发自动换行
      ForEach(this.rowItems, (item: string) => {
          Text(item)
            .width(120)
            .height(60)
            .backgroundColor($r('app.color.bg_blue_light'))
            .textAlign(TextAlign.Center)
            .fontColor($r('app.color.text_white'))
            .borderRadius(4);
      }, (item:string, index: number) => `${item}-${index}`)
      }
      .width('90%')
      .backgroundColor($r('app.color.bg_gray_ultra_light'))
      .padding(10)
      .borderRadius(8);

      // 示例2:Flex替代Column - 纵向+精准间距(等价Column并增强)
      Text("示例2:direction=Column + 主轴space(等价Column)")
      .fontSize(14)
      .fontColor($r('app.color.text_secondary'))
      .width('90%')
      .margin({ top: 20, bottom: 5 });

      Flex({
      direction: FlexDirection.Column,    // 主轴:纵向(等价Column)
      wrap: FlexWrap.NoWrap,            // 纵向无需换行
      space: { main: LengthMetrics.vp(8) } // 纵向主轴间距8vp
      }) {
      ForEach(this.colItems, (item: string) => {
          Text(item)
            .width(120)
            .height(60)
            .backgroundColor($r('app.color.bg_blue_light'))
            .textAlign(TextAlign.Center)
            .fontColor($r('app.color.text_white'))
            .borderRadius(4);
      },(item:string, index: number) => `${item}-${index}`)
      }
      .width('90%')
      .height(100)
      .backgroundColor($r('app.color.bg_gray_ultra_light'))
      .padding(10)
      .borderRadius(8);
    }
    .width('100%')
    .height('100%')
    .padding(15)
    .backgroundColor($r('app.color.bg_page'));
}
}运行效果


[*]示例1(横向Flex):4个元素因总宽度超容器自动换行,每行元素交叉轴居中,行与行、元素与元素间距均为10vp,完美实现流式布局;
[*]示例2(纵向Flex):3个元素垂直排列,纵向间距8vp,因容器高度不足,子组件被正常截断(NoWrap生效)。

四、flexGrow:剩余空间分配

flexGrow 是 Flex 子组件专属属性,用于容器有剩余空间时,按权重分配剩余空间,仅当子组件总尺寸 < 容器主轴尺寸时生效,分配方向由 FlexOptions.direction 决定排列方向(横向分配宽度、纵向分配高度)。
4.1 核心计算逻辑


[*]剩余空间 = 容器主轴尺寸 - 所有子组件初始尺寸之和 - 主轴总间距
[*]总权重 = 所有设置 flexGrow 子组件的属性值之和
[*]单个组件新增尺寸 = 剩余空间 × (当前组件 flexGrow / 总权重)
[*]最终尺寸 = 初始尺寸 + 新增尺寸
实战计算示例

假设容器宽度 300vp(屏幕90%),主轴间距 5vp,两个子组件无初始宽度:

[*]剩余空间 = 300 - 0 - 5 = 295vp
[*]总权重 = 1(蓝色) + 2(红色) = 3
[*]蓝色组件新增宽度 = 295 × 1/3 ≈ 98.3vp → 最终宽度 ≈ 98.3vp
[*]红色组件新增宽度 = 295 × 2/3 ≈ 196.7vp → 最终宽度 ≈ 196.7vp
4.2 示例代码(FlexGrowPage.ets)

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

@Entry
@Component
struct FlexGrowPage {
build() {
    Column({ space: 15 }) {
      Text("flexGrow 剩余空间按权重分配")
      .fontSize(24)
      .fontWeight(FontWeight.Bold)
      .width('100%')
      .textAlign(TextAlign.Center)
      .margin({ bottom: 10 });

      Text("权重1:2 → 蓝色占1/3,红色占2/3(无初始宽度)")
      .fontSize(12)
      .fontColor($r('app.color.text_secondary'))
      .width('90%')
      .textAlign(TextAlign.Center);
      
      // Flex基础配置 + flexGrow实现空间分配
      Flex({
      direction: FlexDirection.Row,       // 横向布局
      space: { main: LengthMetrics.vp(5) } // 主轴间距5vp
      }) {
      Text("flexGrow: 1")
          .flexGrow(1)// 权重1:占剩余空间1/3
          .height(60)
          .backgroundColor($r('app.color.bg_blue_light'))
          .textAlign(TextAlign.Center)
          .fontColor($r('app.color.text_white'))
          .borderRadius(4);

      Text("flexGrow: 2")
          .flexGrow(2)// 权重2:占剩余空间2/3
          .height(60)
          .backgroundColor($r('app.color.bg_red_light'))
          .textAlign(TextAlign.Center)
          .fontColor($r('app.color.text_white'))
          .borderRadius(4);
      }
      .width('90%')
      .backgroundColor($r('app.color.bg_gray_ultra_light'))
      .padding(10)
      .borderRadius(8);
    }
    .width('100%')
    .height('100%')
    .padding(15)
    .backgroundColor($r('app.color.bg_page'));
}
}运行效果

父容器宽度为屏幕90%,两个子组件无初始宽度,剩余空间按1:2权重分配,视觉上蓝色组件占约1/3宽度,红色组件占约2/3宽度,完美实现空间均分/按比例分配。

关键注意点


[*]flexGrow 默认为0,即不参与剩余空间分配;
[*]仅当子组件总尺寸 < 容器尺寸时生效,若子组件总尺寸超出容器,需配合 wrap 或 flexShrink;
[*]纵向布局(direction=Column)时,flexGrow 分配的是容器剩余高度。
五、flexShrink:空间不足压缩(Flex子组件核心属性)

flexShrink 是 Flex 子组件专属属性,用于容器空间不足时,按规则压缩子组件尺寸,必须配合 FlexOptions 的 wrap: NoWrap 才生效(若 wrap: Wrap,子组件会自动换行,不会触发压缩)。
5.1 核心计算逻辑(与flexGrow不同,需结合初始尺寸)


[*]超出空间 = 所有子组件初始尺寸之和 + 子组件间总间距 - 容器主轴尺寸
[*]总权重 = ∑(子组件 flexShrink × 子组件 flexBasis(基准尺寸))(flexBasis 为 auto 时等价初始尺寸)
[*]单个组件压缩尺寸 = 超出空间 × (当前组件 flexShrink × 子组件 flexBasis(基准尺寸) / 总权重)
[*]最终尺寸 = 初始尺寸 - 压缩尺寸
实战计算示例

容器宽度 300vp,主轴间距 5vp,两个子组件初始宽度均为200vp,wrap: NoWrap:

[*]超出空间 = (200+200) + 5 - 300 = 105vp
[*]总权重 = (1×200)「蓝色」 + (0×200)「红色」 = 200
[*]蓝色组件压缩尺寸 = 105 × (1×200/200) = 105vp → 最终宽度 95vp
[*]红色组件压缩尺寸 = 105 × (0×200/200) = 0 → 最终宽度 200vp
5.2 示例代码(FlexShrinkPage.ets)

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

@Entry
@Component
struct FlexShrinkPage {
build() {
    Column({ space: 15 }) {
      Text("flexShrink 空间不足时压缩(依赖wrap: NoWrap)")
      .fontSize(24)
      .fontWeight(FontWeight.Bold)
      .width('100%')
      .textAlign(TextAlign.Center)
      .margin({ bottom: 10 });

      // 核心配置:wrap: NoWrap 才会触发压缩,Wrap则换行
      Flex({
      direction: FlexDirection.Row,
      wrap: FlexWrap.NoWrap,// 必须关闭换行!否则压缩失效
      space: { main: LengthMetrics.vp(5) }
      }) {
      Text("flexShrink: 1")
          .width(200)
          .flexShrink(1)// 参与压缩:承担全部压缩量
          .height(60)
          .backgroundColor($r('app.color.bg_blue_light'))
          .textAlign(TextAlign.Center)
          .fontColor($r('app.color.text_white'))
          .borderRadius(4);

      Text("flexShrink: 0")
          .width(200)
          .flexShrink(0)// 不压缩:保留原始200vp宽度
          .height(60)
          .backgroundColor($r('app.color.bg_red_light'))
          .textAlign(TextAlign.Center)
          .fontColor($r('app.color.text_white'))
          .borderRadius(4);
      }
      .width('90%')
      .backgroundColor($r('app.color.bg_gray_ultra_light'))
      .padding(10)
      .borderRadius(8);

      Text("flexShrink:0 保留原尺寸,flexShrink:1 承担全部压缩(修改wrap为Wrap则换行)")
      .fontSize(12)
      .fontColor($r('app.color.text_secondary'))
      .width('90%')
      .textAlign(TextAlign.Center);
    }
    .width('100%')
    .height('100%')
    .padding(15)
    .backgroundColor($r('app.color.bg_page'));
}
}运行效果


[*]红色组件(flexShrink:0)完全不压缩,保留200vp原始宽度,几乎占满整个容器;
[*]蓝色组件(flexShrink:1)承担全部压缩量,被压缩至约95vp,仅占剩余空间;
[*]若将 wrap 改为 Wrap,两个组件会自动换行,压缩效果完全消失,恢复200vp原始宽度。

关键注意点


[*]flexShrink 默认为1,即所有子组件默认参与压缩,按「flexShrink×初始尺寸」比例承担压缩量;
[*]设置 flexShrink: 0 可让子组件完全不压缩,保留原始尺寸(实战常用:固定尺寸组件不被压缩);
[*]压缩仅在 wrap: NoWrap 时生效,这是Flex与Row的核心共性(Column、Row无换行能力,默认压缩)。
六、flexBasis:基准尺寸

flexBasis 设置组件在父容器(Flex/Row/Column)主轴方向的初始尺寸基准,是 flexGrow/flexShrink 计算剩余空间/压缩空间的 唯一基准 ,优先级高于同方向的 width/height,生效维度由 direction 决定(横向控宽、纵向控高)。换行(wrap: Wrap)时 flexBasis 仍为初始基准,系统优先换行而非压缩,最终尺寸由 flexBasis、内容尺寸、行剩余空间共同调整。
6.1 仅支持2种取值(无百分比)

取值类型说明示例生效维度(direction决定)数值固定基准尺寸(单位:vp),优先级最高flexBasis(100)Row→宽度;Column→heightauto由子组件width/height决定初始基准尺寸(默认值)flexBasis('auto')Row→width;Column→height重要:flexBasis 不支持百分比字符串(如flexBasis('50%')),如需百分比尺寸,直接使用width/height即可,flexBasis会自动识别为auto并沿用width/height值。
6.2 核心特性


[*]优先级:flexBasis(数值)> width/height > flexBasis('auto');
[*]基准作用:flexGrow/flexShrink仅基于flexBasis的结果计算,与原始width/height无关;
[*]维度匹配:始终与主轴方向匹配,横向布局控制宽度,纵向布局控制高度,无需手动调整。
6.3 示例代码(FlexBasisPage.ets)

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

@Entry
@Component
struct FlexBasisPage {
build() {
    // 外层Column:弹性布局,顶部排列避免高度溢出
    Column({ space: 10 }) {
      Text(`flexBasis核心规则(Row方向Flex,无换行)
            1. 主轴(横向):flexBasis优先级 > width
            2. 仅支持:数值(vp) / 'auto',不支持百分比`)
      .fontSize(14)
      .fontWeight(FontWeight.Bold)
      .width('100%')
      .textAlign(TextAlign.Start)
      .margin({ bottom: 8 })
      .lineHeight(22);

      // ========== 场景1:数值型flexBasis(覆盖width) ==========
      Flex({
      direction: FlexDirection.Row,
      space: {
          main: LengthMetrics.vp(8)      // 横向主轴间距
      }
      }) {
      // 核心规则:数值型flexBasis优先级 > width,width=200vp被覆盖
      Text("flexBasis:150vp\nwidth:200vp(被覆盖)")
          .flexBasis(150)               // 主轴宽度强制150vp(生效)
          .width(200)                   // 被flexBasis覆盖(无效)
          .height(70)                   // 交叉轴高度(不受flexBasis影响,生效)
          .backgroundColor($r('app.color.bg_blue_light'))
          .textAlign(TextAlign.Center)
          .fontColor($r('app.color.text_white'))
          .fontSize(11)
          .borderRadius(4);

      // 参考组件:直观对比flexBasis=150vp的实际宽度
      Text("width=150vp(参考)")
          .width(150)
          .height(70)
          .backgroundColor($r('app.color.bg_gray_light'))
          .textAlign(TextAlign.Center)
          .fontColor($r('app.color.text_primary'))
          .fontSize(11)
          .borderRadius(4);
      }
      .width('90%')
      .backgroundColor($r('app.color.bg_gray_ultra_light'))
      .padding(10)
      .borderRadius(8)
      .margin({ bottom: 10 });

      // ========== 场景2:auto型flexBasis(沿用width) ==========
      Flex({
      direction: FlexDirection.Row,
      space: {
          main: LengthMetrics.vp(8)      // 横向主轴间距
      }
      }) {
      // 核心规则:flexBasis=auto时,沿用width的设置值
      Text("flexBasis:auto\nwidth:100vp(生效)")
          .flexBasis('auto')            // 等效使用width的值(不覆盖)
          .width(100)                   // 生效,主轴宽度=100vp
          .height(70)
          .backgroundColor($r('app.color.bg_red_light'))
          .textAlign(TextAlign.Center)
          .fontColor($r('app.color.text_white'))
          .fontSize(11)
          .borderRadius(4);

      // 参考组件:直观对比width=100vp的实际宽度
      Text("width=100vp(参考)")
          .width(100)
          .height(70)
          .backgroundColor($r('app.color.bg_gray_light'))
          .textAlign(TextAlign.Center)
          .fontColor($r('app.color.text_primary'))
          .fontSize(11)
          .borderRadius(4);
      }
      .width('90%')
      .backgroundColor($r('app.color.bg_gray_ultra_light'))
      .padding(10)
      .borderRadius(8)
      .margin({ bottom: 10 });

      // ========== 场景3:flexBasis百分比(无效,等效auto) ==========
      Flex({
      direction: FlexDirection.Row,
      space: {
          main: LengthMetrics.vp(8)      // 横向主轴间距
      }
      }) {
      // 核心规则:flexBasis不支持百分比,设为'50%'等效auto,沿用width
      Text("flexBasis:'50%'(无效)\nwidth:90vp(生效)")
          .flexBasis('50%')             // 百分比无效,等效auto
          .width(90)                  // 生效,主轴宽度=90vp
          .height(70)
          .backgroundColor($r('app.color.bg_green_light'))
          .textAlign(TextAlign.Center)
          .fontColor($r('app.color.text_white'))
          .fontSize(11)
          .borderRadius(4);

      // 参考组件:直观对比width=90vp的实际宽度
      Text("width=90vp(参考)")
          .width(90)
          .height(70)
          .backgroundColor($r('app.color.bg_gray_light'))
          .textAlign(TextAlign.Center)
          .fontColor($r('app.color.text_primary'))
          .fontSize(11)
          .borderRadius(4);
      }
      .width('90%')
      .backgroundColor($r('app.color.bg_gray_ultra_light'))
      .padding(10)
      .borderRadius(8)
      .margin({ bottom: 10 });

      // ========== 场景4:flexGrow+flexBasis(弹性伸缩) ==========
      Flex({
      direction: FlexDirection.Row,
      space: {
          main: LengthMetrics.vp(8)      // 横向主轴间距
      }
      }) {
      // 核心规则:flexBasis为基准尺寸,flexGrow占满剩余空间,width被覆盖
      Text("flexGrow:1\nflexBasis:100vp(基准)")
          .flexBasis(100)               // 基准宽度100vp(覆盖width)
          .flexGrow(1)                  // 占满容器剩余横向空间
          .width(300)                   // 被flexBasis覆盖(无效)
          .height(70)
          .backgroundColor($r('app.color.bg_purple_light')) // 修正颜色,避免与场景3重复
          .textAlign(TextAlign.Center)
          .fontColor($r('app.color.text_white'))
          .fontSize(11)
          .borderRadius(4);

      // 参考组件:固定宽度,对比弹性伸缩效果
      Text("width=80vp(固定)")
          .width(80)
          .height(70)
          .backgroundColor($r('app.color.bg_gray_light'))
          .textAlign(TextAlign.Center)
          .fontColor($r('app.color.text_primary'))
          .fontSize(11)
          .borderRadius(4);
      }
      .width('90%')
      .backgroundColor($r('app.color.bg_gray_ultra_light'))
      .padding(10)
      .borderRadius(8);

      // ========== 实战场景:固定+弹性布局(无换行) ==========
      Text("实战:Flex(Row无换行)+ 固定+弹性布局")
      .fontSize(13)
      .fontColor($r('app.color.text_secondary'))
      .width('90%')
      .textAlign(TextAlign.Center)
      .margin({ top: 10, bottom: 5 })
      .lineHeight(20);

      Flex({
      direction: FlexDirection.Row,
      space: {
          main: LengthMetrics.vp(5)   // 横向主轴间距
      }
      }) {
      // 固定宽度元素:业务场景中的操作按钮
      Text("按钮1")
          .width(60)
          .height(50)
          .backgroundColor($r('app.color.bg_blue_light'))
          .textAlign(TextAlign.Center)
          .fontColor($r('app.color.text_white'))
          .borderRadius(4);

      // 核心规则:flexGrow=1占满剩余空间,flexBasis=auto沿用默认尺寸
      Text("弹性占满")
          .flexGrow(1)                  // 占满容器剩余横向空间
          .flexShrink(1)                // 空间不足时自动收缩
          .flexBasis('auto')            // 基准尺寸为auto
          .height(50)
          .backgroundColor($r('app.color.bg_blue_light_ultra'))
          .textAlign(TextAlign.Center)
          .fontColor($r('app.color.text_primary'))
          .borderRadius(4);
      }
      .width('90%')
      .backgroundColor($r('app.color.bg_gray_ultra_light'))
      .padding(8)
      .borderRadius(8);
    }
    .width('100%')
    .height('100%')
    .padding(12)
    .backgroundColor($r('app.color.bg_page'))
    .justifyContent(FlexAlign.Start);
}
}运行效果


[*]场景1:蓝色组件宽度固定150vp(flexBasis(150)覆盖width(200),优先级更高);
[*]场景2:红色组件宽度100vp(flexBasis('auto'),沿用width的100vp作为基准);
[*]场景3:绿色组件宽度90vp(flexBasis('50%')无效,自动转为auto,沿用width(90));
[*]场景4:紫色组件以100vp为基准占满剩余空间,width(300)被覆盖;
[*]实战场景:按钮固定60vp宽度,右侧元素弹性占满剩余空间。

七、方向反转

direction 是 FlexOptions 最核心的属性之一,支持主轴方向正序/反转,是实现逆向UI布局的核心方案,无需手动调整子组件编写顺序,仅修改一个属性即可实现方向切换。
7.1 direction 完整取值与适用场景

取值主轴方向子组件排列顺序核心适用场景Row(默认)横向从左到右常规横向布局RowReverse横向从右到左逆向横向布局(如右对齐操作栏)Column纵向从上到下常规纵向布局(等价Column)ColumnReverse纵向从下到上底部弹出列表、逆向滚动布局、倒序排列7.2 示例代码(FlexReversePage.ets)

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

@Entry
@Component
struct FlexReversePage {
build() {
    Column({ space: 20 }) {
      Text("FlexOptions.direction 方向反转演示")
      .fontSize(24)
      .fontWeight(FontWeight.Bold)
      .width('100%')
      .textAlign(TextAlign.Center)
      .margin({ bottom: 10 });

      // 示例1:横向反转 - RowReverse
      Text("示例1:direction = RowReverse(横向从右到左)")
      .fontSize(14)
      .fontColor($r('app.color.text_secondary'))
      .width('90%');

      Flex({
      direction: FlexDirection.RowReverse,// 核心:横向反转
      space: { main: LengthMetrics.vp(10) } // 间距规则不变,沿反转后主轴生效
      }) {
      Text("元素1")
          .width(80)
          .height(60)
          .backgroundColor($r('app.color.bg_blue_light'))
          .textAlign(TextAlign.Center)
          .fontColor($r('app.color.text_white'))
          .borderRadius(4);
      Text("元素2")
          .width(80)
          .height(60)
          .backgroundColor($r('app.color.bg_red_light'))
          .textAlign(TextAlign.Center)
          .fontColor($r('app.color.text_white'))
          .borderRadius(4);
      Text("元素3")
          .width(80)
          .height(60)
          .backgroundColor($r('app.color.bg_green_light'))
          .textAlign(TextAlign.Center)
          .fontColor($r('app.color.text_white'))
          .borderRadius(4);
      }
      .width('90%')
      .backgroundColor($r('app.color.bg_gray_ultra_light'))
      .padding(10)
      .borderRadius(8);

      // 示例2:纵向反转 - ColumnReverse(底部起始布局)
      Text("示例2:direction = ColumnReverse(纵向从下到上)")
      .fontSize(14)
      .fontColor($r('app.color.text_secondary'))
      .width('90%')
      .margin({ top: 20 });

      Flex({
      direction: FlexDirection.ColumnReverse,// 核心:纵向反转
      space: { main: LengthMetrics.vp(8) }   // 纵向间距沿反转后主轴生效
      }) {
      Text("元素A")
          .height(40)
          .backgroundColor($r('app.color.bg_blue_light'))
          .textAlign(TextAlign.Center)
          .fontColor($r('app.color.text_white'))
          .borderRadius(4);
      Text("元素B")
          .height(40)
          .backgroundColor($r('app.color.bg_red_light'))
          .textAlign(TextAlign.Center)
          .fontColor($r('app.color.text_white'))
          .borderRadius(4);
      }
      .width('90%')
      .height(120)
      .backgroundColor($r('app.color.bg_gray_ultra_light'))
      .padding(10)
      .borderRadius(8);
    }
    .width('100%')
    .height('100%')
    .padding(15)
    .backgroundColor($r('app.color.bg_page'));
}
}7.3 运行效果


[*]横向反转区域:子组件显示顺序为「元素3 → 元素2 → 元素1」,从容器右侧向左侧排列,主轴间距10vp正常生效,无需调整子组件编写顺序;
[*]纵向反转区域:子组件显示顺序为「元素B → 元素A」,从容器底部向顶部排列,在120vp高的容器内从底部开始排布,纵向间距8vp正常生效。

7.4 方向反转关键注意点


[*]反转仅改变子组件排列顺序和主轴起始方向,space 间距、justifyContent/alignItems 对齐逻辑会自动适配(Start/End 随反转方向动态变化,无需手动修改);
[*]反转布局的性能损耗极低,仅为布局顺序调整,无额外渲染开销;
[*]无需手动调整子组件的编写顺序,仅通过修改 direction 即可实现正序/反转切换,符合“一次编写,多端适配”原则;
[*]反转后,flexGrow/flexShrink/flexBasis 的计算逻辑不变,仍基于反转后的主轴方向生效。
八、Flex 综合实战案例:移动端搜索推荐布局

8.1 需求说明(典型移动端业务布局)

实现“顶部搜索栏 + 中间流式推荐标签”的核心布局,要求:

[*]顶部搜索栏:固定顶部,左右留间距,输入框占满剩余宽度,搜索按钮固定尺寸;
[*]中间标签区:支持流式自动换行(核心,Row无法实现),标签横向/纵向间距均匀,占满页面中间剩余高度;
[*]整体适配:使用Flex独有能力实现,无冗余布局嵌套。
8.2 示例代码(FlexComplexPage.ets)

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

@Entry
@Component
struct FlexComplexPage {
// 1. 定义标签数据源
// 猜你想搜标签数组
private guessTags: string[] = [
    "鸿蒙开发", "Flex布局", "ArkTS教程",
    "自适应布局", "多设备适配", "Stack布局"
];
// 历史搜索标签数组
private historyTags: string[] = ["弹性布局", "鸿蒙组件"];

build() {
    Column() {
      // 顶部搜索栏
      Row() {
      SymbolGlyph($r('sys.symbol.chevron_left_circle'))
          .fontSize(30)
          .fontColor([$r('app.color.text_primary')])
          .onClick(() => {
            router.back()
          });

      Blank();

      Search({ placeholder: "输入搜索关键词" })
          .backgroundColor($r('app.color.bg_gray_ultra_light'))
          .placeholderColor($r('app.color.text_primary'))
          .constraintSize({ minWidth: 200, maxWidth: 400 })
          .width('calc(100% - 120vp)')

      Button("搜索")
          .height(32)
          .backgroundColor($r('app.color.bg_red_light'))
          .fontColor($r('app.color.text_white'))
          .borderRadius(16)
          .margin({ left: 15 })
          .fontSize(14);
      }
      .justifyContent(FlexAlign.SpaceBetween)
      .width('100%')
      .height(50)
      .backgroundColor($r('app.color.bg_white'))
      .padding({ left: 15, right: 15 })
      .alignItems(VerticalAlign.Center);

      // 中间内容区(可滚动)
      Scroll() {
      Column({space:15}) {
          // 猜你想搜区域
          Text("猜你想搜")
            .fontSize(16)
            .fontWeight(FontWeight.Medium)
            .fontColor($r('app.color.text_primary'));

          // 流式标签:Flex + ForEach 动态渲染
          Flex({
            wrap: FlexWrap.Wrap, // 自动换行(核心)
            space: { // 双轴间距
            main: LengthMetrics.vp(10), // 主轴(横向)间距
            cross: LengthMetrics.vp(8)// 交叉轴(纵向)间距
            }
          }) {
            ForEach(
            this.guessTags,
            (tag: string) => {
                Text(tag)
                  .padding({ left: 12, right: 12, top: 6, bottom: 6 })
                  .backgroundColor($r('app.color.bg_gray_ultra_light'))
                  .fontColor($r('app.color.text_primary'))
                  .borderRadius(12)
                  .fontSize(14);
            },
            (tag: string, index: number) => `${tag}-${index}`
            )
          }
          .width('100%');

          // 历史搜索区域标题栏
          Row() {
            Text("历史搜索")
            .fontSize(16)
            .fontWeight(FontWeight.Medium)
            .fontColor($r('app.color.text_primary'));

            Button("清空历史")
            .width(80)
            .height(28)
            .backgroundColor($r('app.color.bg_gray_light'))
            .fontColor($r('app.color.text_primary'))
            .borderRadius(6)
            .fontSize(12)
            .margin({ left: 10 });
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween);

          // 历史搜索流式标签
          Flex({
            wrap: FlexWrap.Wrap, // 自动换行
            space: {
            main: LengthMetrics.vp(10),
            cross: LengthMetrics.vp(8)
            }
          }) {
            ForEach(
            this.historyTags,
            (tag: string) => {
                Text(tag)
                  .padding({ left: 12, right: 12, top: 6, bottom: 6 })
                  .backgroundColor($r('app.color.bg_blue_light_ultra'))
                  .fontColor($r('app.color.text_primary'))
                  .borderRadius(12)
                  .fontSize(14);
            },
            (tag: string, index: number) => `${tag}-${index}`
            )
          }
          .width('100%');
      }
      .width('100%')
      .height('100%')
      .padding(15)
      .alignItems(HorizontalAlign.Start);
      }
      .layoutWeight(1) // 占满剩余空间
      .width('100%')
      .backgroundColor($r('app.color.bg_white'));
    }
    .width('100%')
    .height('100%')
    .alignItems(HorizontalAlign.Start);
}
}8.3 运行效果


九、核心总结


[*]Flex 布局的核心价值是支持自动换行(wrap:Wrap)和方向反转(RowReverse/ColumnReverse),这是Row/Column无法实现的关键能力;
[*]Flex 子组件三大核心属性:flexGrow(剩余空间按权重分配)、flexShrink(空间不足按规则压缩,需wrap:NoWrap)、flexBasis(基准尺寸,优先级>width/height,仅支持数值/auto);
[*]FlexOptions 核心配置:direction 控制主轴方向/反转,wrap 控制换行规则,space实现主轴/交叉轴双轴精准间距,alignContent 实现交叉轴多行对齐;
[*]布局选型原则:简单单行/单列用Row/Column(轻量高性能),复杂流式/反转/多行布局用Flex(功能强大),性能敏感场景优先Row/Column;
十、代码仓库


[*]工程名称:FlexApplication
[*]仓库地址:https://gitee.com/HarmonyOS-UI-Basics/harmony-os-ui-basics.git
十一、下节预告

本节我们系统掌握了 Flex 布局的核心配置、三大弹性属性(flexGrow/flexShrink/flexBasis)与实战用法,实现了流式标签复杂布局。下一节,我们将学习鸿蒙 ArkTS 中实现Z轴层叠排列的核心布局——Stack 叠层布局,重点掌握:

[*]Stack 布局的核心定位与适用场景,理解其与 Flex/Row/Column 的核心区别;
[*]Alignment 全局对齐、zIndex 层级控制、offset 相对偏移、position 绝对定位四大核心能力;
[*]实战落地移动端高频场景:App 图标消息角标、商品卡片层叠遮罩与悬浮标签,掌握层叠布局的精准定位与交互适配技巧。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

吁寂 发表于 2026-2-26 21:28:37

很好很强大我过来先占个楼 待编辑

栓州 发表于 2026-3-6 23:14:28

感谢分享,下载保存了,貌似很强大

少琼 发表于 2026-3-11 08:05:36

懂技术并乐意极积无私分享的人越来越少。珍惜

裒噎 发表于 2026-3-12 04:36:18

前排留名,哈哈哈

呶募妙 发表于 昨天 11:37

过来提前占个楼
页: [1]
查看完整版本: 鸿蒙应用开发UI基础第十一节:弹性布局Flex核心讲解与实战演示