找回密码
 立即注册
首页 业界区 安全 鸿蒙应用开发UI基础第十一节:弹性布局Flex核心讲解与实 ...

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

孜尊 昨天 18:30
【学习目标】


  • 理解 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 布局相关演示代码
  1. FlexApplication/
  2. ├── AppScope/
  3. ├── entry/
  4. │   ├── src/
  5. │   │   ├── main/
  6. │   │   │   ├── ets/
  7. │   │   │   │   ├── entryability/
  8. │   │   │   │   │   └── EntryAbility.ets
  9. │   │   │   │   └── pages/
  10. │   │   │   │       ├── Index.ets            # 演示入口页(按钮导航)
  11. │   │   │   │       ├── FlexBasicPage.ets    # Flex基础语法+FlexOptions核心属性演示
  12. │   │   │   │       ├── FlexGrowPage.ets     # flexGrow(剩余空间分配)演示
  13. │   │   │   │       ├── FlexShrinkPage.ets   # flexShrink(空间不足压缩)演示
  14. │   │   │   │       ├── FlexBasisPage.ets    # flexBasis(基准尺寸)演示
  15. │   │   │   │       ├── FlexReversePage.ets  # FlexOptions方向反转(direction)演示
  16. │   │   │   │       └── FlexComplexPage.ets  # 综合案例(搜索推荐布局)
  17. │   │   │   ├── resources/
  18. │   │   │   └── module.json5
  19. │   └── build-profile.json5
  20. └── build-profile.json5
复制代码
2.2 演示入口(Index.ets)
  1. import router from '@ohos.router';
  2. // 定义按钮数据类型
  3. interface ButtonItem {
  4.   name: string,  // 按钮显示文本
  5.   url: string    // 跳转页面路径(需与pages目录下的文件路径一致)
  6. }
  7. @Entry  // 页面入口装饰器,标记当前组件为应用入口
  8. @Component  // 组件装饰器,标记当前结构体为UI组件
  9. struct Index {
  10.   // 按钮列表数据 - 状态变量
  11.   @State buttonList: ButtonItem[] = [
  12.     { name: "Flex 基础(FlexOptions)", url: "pages/FlexBasicPage" },
  13.     { name: "flexGrow 放大", url: "pages/FlexGrowPage" },
  14.     { name: "flexShrink 缩小", url: "pages/FlexShrinkPage" },
  15.     { name: "flexBasis 基准尺寸", url: "pages/FlexBasisPage" },
  16.     { name: "FlexOptions 方向反转", url: "pages/FlexReversePage" },
  17.     { name: "Flex 综合案例(搜索推荐)", url: "pages/FlexComplexPage" }
  18.   ];
  19.   build() {
  20.     // 外层Column:垂直排列所有按钮,space:20 表示子组件间距20vp
  21.     Column({ space: 20 }) {
  22.       // 页面标题
  23.       Text("Flex 布局(FlexOptions)演示")
  24.         .fontSize(28)
  25.         .fontWeight(FontWeight.Bold)
  26.         .fontColor($r('app.color.text_primary'))  // 引用color.json中的颜色资源
  27.         .margin({ bottom: 40 });
  28.       // 循环渲染按钮
  29.       ForEach(
  30.         this.buttonList,  // 数据源
  31.         (item: ButtonItem) => {  // 渲染函数:每个item生成一个按钮
  32.           Button(item.name)
  33.             .width('80%')
  34.             .height(50)
  35.             .backgroundColor($r('app.color.bg_blue_light'))
  36.             .fontColor($r('app.color.text_white'))
  37.             .borderRadius(8)
  38.             .onClick(() => {  // 点击事件:跳转到对应页面
  39.               router.pushUrl({ url: item.url });
  40.             });
  41.         },
  42.         (item:ButtonItem, index: number) => `${item.name}-${index}` // 唯一标识:name+索引
  43.       );
  44.     }
  45.     .width('100%')
  46.     .height('100%')
  47.     .justifyContent(FlexAlign.Center)  // 垂直居中所有子组件
  48.     .backgroundColor($r('app.color.bg_page'));
  49.   }
  50. }
复制代码
运行效果

1.jpeg

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

文件路径为 entry/src/main/resources/base/color/color.json
  1. {
  2.   "color": [
  3.     { "name": "text_primary", "value": "#333333" },
  4.     { "name": "text_secondary", "value": "#666666" },
  5.     { "name": "text_white", "value": "#FFFFFF" },
  6.     { "name": "bg_page", "value": "#F5F5F5" },
  7.     { "name": "bg_white", "value": "#FFFFFF" },
  8.     { "name": "bg_blue_light", "value": "#1677FF" },
  9.     { "name": "bg_blue_light_ultra", "value": "#E8F3FF" },
  10.     { "name": "bg_red_light", "value": "#FF4D4F" },
  11.     { "name": "bg_green_light", "value": "#52C41A" },
  12.     { "name": "bg_gray_light", "value": "#E0E0E0" },
  13.     { "name": "bg_gray_ultra_light", "value": "#F0F0F0" },
  14.     { "name": "bg_purple_light", "value": "#722ED1" }
  15.   ]
  16. }
复制代码
三、基础示例代码(FlexBasicPage.ets)
  1. import { LengthMetrics } from '@kit.ArkUI';
  2. @Entry
  3. @Component
  4. struct FlexBasicPage {
  5.   // 演示子组件列表
  6.   private rowItems: string[] = ["元素1", "元素2", "元素3", "元素4"];
  7.   private colItems: string[] = ["元素A", "元素B", "元素C"];
  8.   build() {
  9.     Column({ space: 15 }) {
  10.       // 页面标题
  11.       Text("FlexOptions 核心属性演示")
  12.         .fontSize(24)
  13.         .fontWeight(FontWeight.Bold)
  14.         .width('100%')
  15.         .textAlign(TextAlign.Center)
  16.         .margin({ bottom: 10 });
  17.       // 示例1:Flex核心能力 - 横向+自动换行+双轴间距(Row无此能力)
  18.       Text("示例1:direction=Row + wrap=Wrap + 双轴space")
  19.         .fontSize(14)
  20.         .fontColor($r('app.color.text_secondary'))
  21.         .width('90%')
  22.         .margin({ bottom: 5 });
  23.       Flex({
  24.         direction: FlexDirection.Row,        // 主轴:横向正序
  25.         wrap: FlexWrap.Wrap,                 // 核心:自动换行(Flex独有)
  26.         alignItems: ItemAlign.Center,        // 交叉轴单行居中
  27.         alignContent: FlexAlign.Start,       // 交叉轴多行起始对齐
  28.         space: {                             // 双轴间距
  29.           main: LengthMetrics.vp(10),        // 主轴横向间距10vp
  30.           cross: LengthMetrics.vp(10)        // 交叉轴纵向间距10vp(仅换行生效)
  31.         }
  32.       }) {
  33.         // 4个120vp宽的子组件,总宽度超容器,触发自动换行
  34.         ForEach(this.rowItems, (item: string) => {
  35.           Text(item)
  36.             .width(120)
  37.             .height(60)
  38.             .backgroundColor($r('app.color.bg_blue_light'))
  39.             .textAlign(TextAlign.Center)
  40.             .fontColor($r('app.color.text_white'))
  41.             .borderRadius(4);
  42.         }, (item:string, index: number) => `${item}-${index}`)
  43.       }
  44.       .width('90%')
  45.       .backgroundColor($r('app.color.bg_gray_ultra_light'))
  46.       .padding(10)
  47.       .borderRadius(8);
  48.       // 示例2:Flex替代Column - 纵向+精准间距(等价Column并增强)
  49.       Text("示例2:direction=Column + 主轴space(等价Column)")
  50.         .fontSize(14)
  51.         .fontColor($r('app.color.text_secondary'))
  52.         .width('90%')
  53.         .margin({ top: 20, bottom: 5 });
  54.       Flex({
  55.         direction: FlexDirection.Column,    // 主轴:纵向(等价Column)
  56.         wrap: FlexWrap.NoWrap,              // 纵向无需换行
  57.         space: { main: LengthMetrics.vp(8) } // 纵向主轴间距8vp
  58.       }) {
  59.         ForEach(this.colItems, (item: string) => {
  60.           Text(item)
  61.             .width(120)
  62.             .height(60)
  63.             .backgroundColor($r('app.color.bg_blue_light'))
  64.             .textAlign(TextAlign.Center)
  65.             .fontColor($r('app.color.text_white'))
  66.             .borderRadius(4);
  67.         },  (item:string, index: number) => `${item}-${index}`)
  68.       }
  69.       .width('90%')
  70.       .height(100)
  71.       .backgroundColor($r('app.color.bg_gray_ultra_light'))
  72.       .padding(10)
  73.       .borderRadius(8);
  74.     }
  75.     .width('100%')
  76.     .height('100%')
  77.     .padding(15)
  78.     .backgroundColor($r('app.color.bg_page'));
  79.   }
  80. }
复制代码
运行效果


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

四、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)
  1. import { LengthMetrics } from '@kit.ArkUI';
  2. @Entry
  3. @Component
  4. struct FlexGrowPage {
  5.   build() {
  6.     Column({ space: 15 }) {
  7.       Text("flexGrow 剩余空间按权重分配")
  8.         .fontSize(24)
  9.         .fontWeight(FontWeight.Bold)
  10.         .width('100%')
  11.         .textAlign(TextAlign.Center)
  12.         .margin({ bottom: 10 });
  13.       Text("权重1:2 → 蓝色占1/3,红色占2/3(无初始宽度)")
  14.         .fontSize(12)
  15.         .fontColor($r('app.color.text_secondary'))
  16.         .width('90%')
  17.         .textAlign(TextAlign.Center);
  18.       
  19.       // Flex基础配置 + flexGrow实现空间分配
  20.       Flex({
  21.         direction: FlexDirection.Row,       // 横向布局
  22.         space: { main: LengthMetrics.vp(5) } // 主轴间距5vp
  23.       }) {
  24.         Text("flexGrow: 1")
  25.           .flexGrow(1)  // 权重1:占剩余空间1/3
  26.           .height(60)
  27.           .backgroundColor($r('app.color.bg_blue_light'))
  28.           .textAlign(TextAlign.Center)
  29.           .fontColor($r('app.color.text_white'))
  30.           .borderRadius(4);
  31.         Text("flexGrow: 2")
  32.           .flexGrow(2)  // 权重2:占剩余空间2/3
  33.           .height(60)
  34.           .backgroundColor($r('app.color.bg_red_light'))
  35.           .textAlign(TextAlign.Center)
  36.           .fontColor($r('app.color.text_white'))
  37.           .borderRadius(4);
  38.       }
  39.       .width('90%')
  40.       .backgroundColor($r('app.color.bg_gray_ultra_light'))
  41.       .padding(10)
  42.       .borderRadius(8);
  43.     }
  44.     .width('100%')
  45.     .height('100%')
  46.     .padding(15)
  47.     .backgroundColor($r('app.color.bg_page'));
  48.   }
  49. }
复制代码
运行效果

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

关键注意点


  • 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)
  1. import { LengthMetrics } from '@kit.ArkUI';
  2. @Entry
  3. @Component
  4. struct FlexShrinkPage {
  5.   build() {
  6.     Column({ space: 15 }) {
  7.       Text("flexShrink 空间不足时压缩(依赖wrap: NoWrap)")
  8.         .fontSize(24)
  9.         .fontWeight(FontWeight.Bold)
  10.         .width('100%')
  11.         .textAlign(TextAlign.Center)
  12.         .margin({ bottom: 10 });
  13.       // 核心配置:wrap: NoWrap 才会触发压缩,Wrap则换行
  14.       Flex({
  15.         direction: FlexDirection.Row,
  16.         wrap: FlexWrap.NoWrap,  // 必须关闭换行!否则压缩失效
  17.         space: { main: LengthMetrics.vp(5) }
  18.       }) {
  19.         Text("flexShrink: 1")
  20.           .width(200)
  21.           .flexShrink(1)  // 参与压缩:承担全部压缩量
  22.           .height(60)
  23.           .backgroundColor($r('app.color.bg_blue_light'))
  24.           .textAlign(TextAlign.Center)
  25.           .fontColor($r('app.color.text_white'))
  26.           .borderRadius(4);
  27.         Text("flexShrink: 0")
  28.           .width(200)
  29.           .flexShrink(0)  // 不压缩:保留原始200vp宽度
  30.           .height(60)
  31.           .backgroundColor($r('app.color.bg_red_light'))
  32.           .textAlign(TextAlign.Center)
  33.           .fontColor($r('app.color.text_white'))
  34.           .borderRadius(4);
  35.       }
  36.       .width('90%')
  37.       .backgroundColor($r('app.color.bg_gray_ultra_light'))
  38.       .padding(10)
  39.       .borderRadius(8);
  40.       Text("flexShrink:0 保留原尺寸,flexShrink:1 承担全部压缩(修改wrap为Wrap则换行)")
  41.         .fontSize(12)
  42.         .fontColor($r('app.color.text_secondary'))
  43.         .width('90%')
  44.         .textAlign(TextAlign.Center);
  45.     }
  46.     .width('100%')
  47.     .height('100%')
  48.     .padding(15)
  49.     .backgroundColor($r('app.color.bg_page'));
  50.   }
  51. }
复制代码
运行效果


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

关键注意点


  • 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)
  1. import { LengthMetrics } from '@kit.ArkUI';
  2. @Entry
  3. @Component
  4. struct FlexBasisPage {
  5.   build() {
  6.     // 外层Column:弹性布局,顶部排列避免高度溢出
  7.     Column({ space: 10 }) {
  8.       Text(`flexBasis核心规则(Row方向Flex,无换行)
  9.             1. 主轴(横向):flexBasis优先级 > width
  10.             2. 仅支持:数值(vp) / 'auto',不支持百分比`)
  11.         .fontSize(14)
  12.         .fontWeight(FontWeight.Bold)
  13.         .width('100%')
  14.         .textAlign(TextAlign.Start)
  15.         .margin({ bottom: 8 })
  16.         .lineHeight(22);
  17.       // ========== 场景1:数值型flexBasis(覆盖width) ==========
  18.       Flex({
  19.         direction: FlexDirection.Row,
  20.         space: {
  21.           main: LengthMetrics.vp(8)      // 横向主轴间距
  22.         }
  23.       }) {
  24.         // 核心规则:数值型flexBasis优先级 > width,width=200vp被覆盖
  25.         Text("flexBasis:150vp\nwidth:200vp(被覆盖)")
  26.           .flexBasis(150)               // 主轴宽度强制150vp(生效)
  27.           .width(200)                   // 被flexBasis覆盖(无效)
  28.           .height(70)                   // 交叉轴高度(不受flexBasis影响,生效)
  29.           .backgroundColor($r('app.color.bg_blue_light'))
  30.           .textAlign(TextAlign.Center)
  31.           .fontColor($r('app.color.text_white'))
  32.           .fontSize(11)
  33.           .borderRadius(4);
  34.         // 参考组件:直观对比flexBasis=150vp的实际宽度
  35.         Text("width=150vp(参考)")
  36.           .width(150)
  37.           .height(70)
  38.           .backgroundColor($r('app.color.bg_gray_light'))
  39.           .textAlign(TextAlign.Center)
  40.           .fontColor($r('app.color.text_primary'))
  41.           .fontSize(11)
  42.           .borderRadius(4);
  43.       }
  44.       .width('90%')
  45.       .backgroundColor($r('app.color.bg_gray_ultra_light'))
  46.       .padding(10)
  47.       .borderRadius(8)
  48.       .margin({ bottom: 10 });
  49.       // ========== 场景2:auto型flexBasis(沿用width) ==========
  50.       Flex({
  51.         direction: FlexDirection.Row,
  52.         space: {
  53.           main: LengthMetrics.vp(8)      // 横向主轴间距
  54.         }
  55.       }) {
  56.         // 核心规则:flexBasis=auto时,沿用width的设置值
  57.         Text("flexBasis:auto\nwidth:100vp(生效)")
  58.           .flexBasis('auto')            // 等效使用width的值(不覆盖)
  59.           .width(100)                   // 生效,主轴宽度=100vp
  60.           .height(70)
  61.           .backgroundColor($r('app.color.bg_red_light'))
  62.           .textAlign(TextAlign.Center)
  63.           .fontColor($r('app.color.text_white'))
  64.           .fontSize(11)
  65.           .borderRadius(4);
  66.         // 参考组件:直观对比width=100vp的实际宽度
  67.         Text("width=100vp(参考)")
  68.           .width(100)
  69.           .height(70)
  70.           .backgroundColor($r('app.color.bg_gray_light'))
  71.           .textAlign(TextAlign.Center)
  72.           .fontColor($r('app.color.text_primary'))
  73.           .fontSize(11)
  74.           .borderRadius(4);
  75.       }
  76.       .width('90%')
  77.       .backgroundColor($r('app.color.bg_gray_ultra_light'))
  78.       .padding(10)
  79.       .borderRadius(8)
  80.       .margin({ bottom: 10 });
  81.       // ========== 场景3:flexBasis百分比(无效,等效auto) ==========
  82.       Flex({
  83.         direction: FlexDirection.Row,
  84.         space: {
  85.           main: LengthMetrics.vp(8)      // 横向主轴间距
  86.         }
  87.       }) {
  88.         // 核心规则:flexBasis不支持百分比,设为'50%'等效auto,沿用width
  89.         Text("flexBasis:'50%'(无效)\nwidth:90vp(生效)")
  90.           .flexBasis('50%')             // 百分比无效,等效auto
  91.           .width(90)                    // 生效,主轴宽度=90vp
  92.           .height(70)
  93.           .backgroundColor($r('app.color.bg_green_light'))
  94.           .textAlign(TextAlign.Center)
  95.           .fontColor($r('app.color.text_white'))
  96.           .fontSize(11)
  97.           .borderRadius(4);
  98.         // 参考组件:直观对比width=90vp的实际宽度
  99.         Text("width=90vp(参考)")
  100.           .width(90)
  101.           .height(70)
  102.           .backgroundColor($r('app.color.bg_gray_light'))
  103.           .textAlign(TextAlign.Center)
  104.           .fontColor($r('app.color.text_primary'))
  105.           .fontSize(11)
  106.           .borderRadius(4);
  107.       }
  108.       .width('90%')
  109.       .backgroundColor($r('app.color.bg_gray_ultra_light'))
  110.       .padding(10)
  111.       .borderRadius(8)
  112.       .margin({ bottom: 10 });
  113.       // ========== 场景4:flexGrow+flexBasis(弹性伸缩) ==========
  114.       Flex({
  115.         direction: FlexDirection.Row,
  116.         space: {
  117.           main: LengthMetrics.vp(8)      // 横向主轴间距
  118.         }
  119.       }) {
  120.         // 核心规则:flexBasis为基准尺寸,flexGrow占满剩余空间,width被覆盖
  121.         Text("flexGrow:1\nflexBasis:100vp(基准)")
  122.           .flexBasis(100)               // 基准宽度100vp(覆盖width)
  123.           .flexGrow(1)                  // 占满容器剩余横向空间
  124.           .width(300)                   // 被flexBasis覆盖(无效)
  125.           .height(70)
  126.           .backgroundColor($r('app.color.bg_purple_light')) // 修正颜色,避免与场景3重复
  127.           .textAlign(TextAlign.Center)
  128.           .fontColor($r('app.color.text_white'))
  129.           .fontSize(11)
  130.           .borderRadius(4);
  131.         // 参考组件:固定宽度,对比弹性伸缩效果
  132.         Text("width=80vp(固定)")
  133.           .width(80)
  134.           .height(70)
  135.           .backgroundColor($r('app.color.bg_gray_light'))
  136.           .textAlign(TextAlign.Center)
  137.           .fontColor($r('app.color.text_primary'))
  138.           .fontSize(11)
  139.           .borderRadius(4);
  140.       }
  141.       .width('90%')
  142.       .backgroundColor($r('app.color.bg_gray_ultra_light'))
  143.       .padding(10)
  144.       .borderRadius(8);
  145.       // ========== 实战场景:固定+弹性布局(无换行) ==========
  146.       Text("实战:Flex(Row无换行)+ 固定+弹性布局")
  147.         .fontSize(13)
  148.         .fontColor($r('app.color.text_secondary'))
  149.         .width('90%')
  150.         .textAlign(TextAlign.Center)
  151.         .margin({ top: 10, bottom: 5 })
  152.         .lineHeight(20);
  153.       Flex({
  154.         direction: FlexDirection.Row,
  155.         space: {
  156.           main: LengthMetrics.vp(5)     // 横向主轴间距
  157.         }
  158.       }) {
  159.         // 固定宽度元素:业务场景中的操作按钮
  160.         Text("按钮1")
  161.           .width(60)
  162.           .height(50)
  163.           .backgroundColor($r('app.color.bg_blue_light'))
  164.           .textAlign(TextAlign.Center)
  165.           .fontColor($r('app.color.text_white'))
  166.           .borderRadius(4);
  167.         // 核心规则:flexGrow=1占满剩余空间,flexBasis=auto沿用默认尺寸
  168.         Text("弹性占满")
  169.           .flexGrow(1)                  // 占满容器剩余横向空间
  170.           .flexShrink(1)                // 空间不足时自动收缩
  171.           .flexBasis('auto')            // 基准尺寸为auto
  172.           .height(50)
  173.           .backgroundColor($r('app.color.bg_blue_light_ultra'))
  174.           .textAlign(TextAlign.Center)
  175.           .fontColor($r('app.color.text_primary'))
  176.           .borderRadius(4);
  177.       }
  178.       .width('90%')
  179.       .backgroundColor($r('app.color.bg_gray_ultra_light'))
  180.       .padding(8)
  181.       .borderRadius(8);
  182.     }
  183.     .width('100%')
  184.     .height('100%')
  185.     .padding(12)
  186.     .backgroundColor($r('app.color.bg_page'))
  187.     .justifyContent(FlexAlign.Start);
  188.   }
  189. }
复制代码
运行效果


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

七、方向反转

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

取值主轴方向子组件排列顺序核心适用场景Row(默认)横向从左到右常规横向布局RowReverse横向从右到左逆向横向布局(如右对齐操作栏)Column纵向从上到下常规纵向布局(等价Column)ColumnReverse纵向从下到上底部弹出列表、逆向滚动布局、倒序排列7.2 示例代码(FlexReversePage.ets)
  1. import { LengthMetrics } from '@kit.ArkUI';
  2. @Entry
  3. @Component
  4. struct FlexReversePage {
  5.   build() {
  6.     Column({ space: 20 }) {
  7.       Text("FlexOptions.direction 方向反转演示")
  8.         .fontSize(24)
  9.         .fontWeight(FontWeight.Bold)
  10.         .width('100%')
  11.         .textAlign(TextAlign.Center)
  12.         .margin({ bottom: 10 });
  13.       // 示例1:横向反转 - RowReverse
  14.       Text("示例1:direction = RowReverse(横向从右到左)")
  15.         .fontSize(14)
  16.         .fontColor($r('app.color.text_secondary'))
  17.         .width('90%');
  18.       Flex({
  19.         direction: FlexDirection.RowReverse,  // 核心:横向反转
  20.         space: { main: LengthMetrics.vp(10) } // 间距规则不变,沿反转后主轴生效
  21.       }) {
  22.         Text("元素1")
  23.           .width(80)
  24.           .height(60)
  25.           .backgroundColor($r('app.color.bg_blue_light'))
  26.           .textAlign(TextAlign.Center)
  27.           .fontColor($r('app.color.text_white'))
  28.           .borderRadius(4);
  29.         Text("元素2")
  30.           .width(80)
  31.           .height(60)
  32.           .backgroundColor($r('app.color.bg_red_light'))
  33.           .textAlign(TextAlign.Center)
  34.           .fontColor($r('app.color.text_white'))
  35.           .borderRadius(4);
  36.         Text("元素3")
  37.           .width(80)
  38.           .height(60)
  39.           .backgroundColor($r('app.color.bg_green_light'))
  40.           .textAlign(TextAlign.Center)
  41.           .fontColor($r('app.color.text_white'))
  42.           .borderRadius(4);
  43.       }
  44.       .width('90%')
  45.       .backgroundColor($r('app.color.bg_gray_ultra_light'))
  46.       .padding(10)
  47.       .borderRadius(8);
  48.       // 示例2:纵向反转 - ColumnReverse(底部起始布局)
  49.       Text("示例2:direction = ColumnReverse(纵向从下到上)")
  50.         .fontSize(14)
  51.         .fontColor($r('app.color.text_secondary'))
  52.         .width('90%')
  53.         .margin({ top: 20 });
  54.       Flex({
  55.         direction: FlexDirection.ColumnReverse,  // 核心:纵向反转
  56.         space: { main: LengthMetrics.vp(8) }     // 纵向间距沿反转后主轴生效
  57.       }) {
  58.         Text("元素A")
  59.           .height(40)
  60.           .backgroundColor($r('app.color.bg_blue_light'))
  61.           .textAlign(TextAlign.Center)
  62.           .fontColor($r('app.color.text_white'))
  63.           .borderRadius(4);
  64.         Text("元素B")
  65.           .height(40)
  66.           .backgroundColor($r('app.color.bg_red_light'))
  67.           .textAlign(TextAlign.Center)
  68.           .fontColor($r('app.color.text_white'))
  69.           .borderRadius(4);
  70.       }
  71.       .width('90%')
  72.       .height(120)
  73.       .backgroundColor($r('app.color.bg_gray_ultra_light'))
  74.       .padding(10)
  75.       .borderRadius(8);
  76.     }
  77.     .width('100%')
  78.     .height('100%')
  79.     .padding(15)
  80.     .backgroundColor($r('app.color.bg_page'));
  81.   }
  82. }
复制代码
7.3 运行效果


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

7.4 方向反转关键注意点


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

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

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

  • 顶部搜索栏:固定顶部,左右留间距,输入框占满剩余宽度,搜索按钮固定尺寸;
  • 中间标签区:支持流式自动换行(核心,Row无法实现),标签横向/纵向间距均匀,占满页面中间剩余高度;
  • 整体适配:使用Flex独有能力实现,无冗余布局嵌套。
8.2 示例代码(FlexComplexPage.ets)
  1. import { LengthMetrics } from '@kit.ArkUI';
  2. import router from '@ohos.router';
  3. @Entry
  4. @Component
  5. struct FlexComplexPage {
  6.   // 1. 定义标签数据源
  7.   // 猜你想搜标签数组
  8.   private guessTags: string[] = [
  9.     "鸿蒙开发", "Flex布局", "ArkTS教程",
  10.     "自适应布局", "多设备适配", "Stack布局"
  11.   ];
  12.   // 历史搜索标签数组
  13.   private historyTags: string[] = ["弹性布局", "鸿蒙组件"];
  14.   build() {
  15.     Column() {
  16.       // 顶部搜索栏
  17.       Row() {
  18.         SymbolGlyph($r('sys.symbol.chevron_left_circle'))
  19.           .fontSize(30)
  20.           .fontColor([$r('app.color.text_primary')])
  21.           .onClick(() => {
  22.             router.back()
  23.           });
  24.         Blank();
  25.         Search({ placeholder: "输入搜索关键词" })
  26.           .backgroundColor($r('app.color.bg_gray_ultra_light'))
  27.           .placeholderColor($r('app.color.text_primary'))
  28.           .constraintSize({ minWidth: 200, maxWidth: 400 })
  29.           .width('calc(100% - 120vp)')
  30.         Button("搜索")
  31.           .height(32)
  32.           .backgroundColor($r('app.color.bg_red_light'))
  33.           .fontColor($r('app.color.text_white'))
  34.           .borderRadius(16)
  35.           .margin({ left: 15 })
  36.           .fontSize(14);
  37.       }
  38.       .justifyContent(FlexAlign.SpaceBetween)
  39.       .width('100%')
  40.       .height(50)
  41.       .backgroundColor($r('app.color.bg_white'))
  42.       .padding({ left: 15, right: 15 })
  43.       .alignItems(VerticalAlign.Center);
  44.       // 中间内容区(可滚动)
  45.       Scroll() {
  46.         Column({space:15}) {
  47.           // 猜你想搜区域
  48.           Text("猜你想搜")
  49.             .fontSize(16)
  50.             .fontWeight(FontWeight.Medium)
  51.             .fontColor($r('app.color.text_primary'));
  52.           // 流式标签:Flex + ForEach 动态渲染
  53.           Flex({
  54.             wrap: FlexWrap.Wrap, // 自动换行(核心)
  55.             space: { // 双轴间距
  56.               main: LengthMetrics.vp(10), // 主轴(横向)间距
  57.               cross: LengthMetrics.vp(8)  // 交叉轴(纵向)间距
  58.             }
  59.           }) {
  60.             ForEach(
  61.               this.guessTags,
  62.               (tag: string) => {
  63.                 Text(tag)
  64.                   .padding({ left: 12, right: 12, top: 6, bottom: 6 })
  65.                   .backgroundColor($r('app.color.bg_gray_ultra_light'))
  66.                   .fontColor($r('app.color.text_primary'))
  67.                   .borderRadius(12)
  68.                   .fontSize(14);
  69.               },
  70.               (tag: string, index: number) => `${tag}-${index}`
  71.             )
  72.           }
  73.           .width('100%');
  74.           // 历史搜索区域标题栏
  75.           Row() {
  76.             Text("历史搜索")
  77.               .fontSize(16)
  78.               .fontWeight(FontWeight.Medium)
  79.               .fontColor($r('app.color.text_primary'));
  80.             Button("清空历史")
  81.               .width(80)
  82.               .height(28)
  83.               .backgroundColor($r('app.color.bg_gray_light'))
  84.               .fontColor($r('app.color.text_primary'))
  85.               .borderRadius(6)
  86.               .fontSize(12)
  87.               .margin({ left: 10 });
  88.           }
  89.           .width('100%')
  90.           .justifyContent(FlexAlign.SpaceBetween);
  91.           // 历史搜索流式标签
  92.           Flex({
  93.             wrap: FlexWrap.Wrap, // 自动换行
  94.             space: {
  95.               main: LengthMetrics.vp(10),
  96.               cross: LengthMetrics.vp(8)
  97.             }
  98.           }) {
  99.             ForEach(
  100.               this.historyTags,
  101.               (tag: string) => {
  102.                 Text(tag)
  103.                   .padding({ left: 12, right: 12, top: 6, bottom: 6 })
  104.                   .backgroundColor($r('app.color.bg_blue_light_ultra'))
  105.                   .fontColor($r('app.color.text_primary'))
  106.                   .borderRadius(12)
  107.                   .fontSize(14);
  108.               },
  109.               (tag: string, index: number) => `${tag}-${index}`
  110.             )
  111.           }
  112.           .width('100%');
  113.         }
  114.         .width('100%')
  115.         .height('100%')
  116.         .padding(15)
  117.         .alignItems(HorizontalAlign.Start);
  118.       }
  119.       .layoutWeight(1) // 占满剩余空间
  120.       .width('100%')
  121.       .backgroundColor($r('app.color.bg_white'));
  122.     }
  123.     .width('100%')
  124.     .height('100%')
  125.     .alignItems(HorizontalAlign.Start);
  126.   }
  127. }
复制代码
8.3 运行效果

7.jpeg

九、核心总结


  • 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 图标消息角标商品卡片层叠遮罩与悬浮标签,掌握层叠布局的精准定位与交互适配技巧。

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

相关推荐

昨天 21:28

举报

您需要登录后才可以回帖 登录 | 立即注册