鸿蒙原生 ArkTS 布局方式之 Column + Expanded 自适应布局

在这里插入图片描述

一、引言

布局是构建用户界面的基石。HarmonyOS NEXT 提供了 ArkTS 声明式 UI 框架,其中 Column、Row、Flex、Stack 等布局容器构成了灵活多变的布局体系。Column + Expanded 是最基础、最能体现弹性设计思想的组合之一。

本文将以其完整示例为线索,深入剖析布局原理、核心属性、最佳实践及常见陷阱,帮助开发者从零掌握这一布局方式。


二、Column 布局容器

2.1 什么是 Column

Column 是 ArkTS 中最基础的垂直方向布局容器,将子组件沿垂直方向(从上到下)依次排列,类似于 CSS Flexbox 的 flex-direction: column。Column 属于线性布局,底层基于 Flex 弹性盒模型实现。

2.2 基本使用

@Entry
@Component
struct ColumnExample {
  build() {
    Column() {
      Text('子组件 1').fontSize(16)
      Text('子组件 2').fontSize(16)
      Text('子组件 3').fontSize(16)
    }
    .width('100%').height('100%')
  }
}

2.3 核心属性

属性 类型 说明
alignItems HorizontalAlign 子组件水平对齐方式
justifyContent FlexAlign 子组件垂直分布方式
width/height Length 容器宽高
padding Padding 内边距
space Length 子组件间距

注意:使用 Expanded 时 justifyContent 失效,因为 Expanded 会自动填充剩余空间。

2.4 适用场景

页面整体结构划分、表单垂直排列、列表项单列布局、与 Scroll 配合实现可滚动内容。


三、Expanded 组件

3.1 作用与原理

Expanded 是 ArkTS 中实现弹性伸缩的核心组件,包裹在 Column 或 Row 的子组件外层,使被包裹的子组件自动撑满容器中剩余的空白空间。Expanded 不渲染任何视觉内容,只负责分配空间。

3.2 核心属性:layoutWeight

layoutWeight 用于在多个 Expanded 子组件之间按比例分配剩余空间。

Column() {
  Expanded() { /* 区域 A */ }.layoutWeight(1)
  Expanded() { /* 区域 B */ }.layoutWeight(2)
  Expanded() { /* 区域 C */ }.layoutWeight(1)
}

权重比 1:2:1。设两个固定组件共 140vp,屏幕高 852vp,则剩余 712vp 分配:

  • 区域 A:712 × (1÷4) = 178vp
  • 区域 B:712 × (2÷4) = 356vp
  • 区域 C:712 × (1÷4) = 178vp

3.3 默认值与注意事项

未显式设置时默认值为 1。混合使用时,不带权重的也按 1 参与计算。关键注意点:

注意点 说明
必须配合 Column/Row 在其他容器中无效
父容器必须有明确高度 height('100%')
内部组件需设 100% 宽高 否则无法视觉填满
不与 justifyContent 混用 Expanded 会覆盖其效果

四、完整示例代码分析

4.1 示例代码

@Entry
@Component
struct Index {
  @State pageTitle: string = 'Column + Expanded 自适应布局';

  build() {
    Column() {
      // 1. 顶部标题 —— 固定高度 60vp
      Text(this.pageTitle)
        .fontSize(20).fontWeight(FontWeight.Bold).fontColor(Color.White)
        .textAlign(TextAlign.Center).width('100%').height(60)
        .backgroundColor('#2E4E7E').borderRadius({ bottomLeft: 12, bottomRight: 12 }).padding(10)

      // 2. 区域 A —— layoutWeight = 1
      Expanded() {
        Row() {
          Text('layoutWeight = 1').fontSize(18).fontColor(Color.White).fontWeight(FontWeight.Medium)
        }.width('100%').height('100%').justifyContent(FlexAlign.Center).backgroundColor('#43A047')
      }.layoutWeight(1)

      // 3. 区域 B —— layoutWeight = 2
      Expanded() {
        Column() {
          Text('layoutWeight = 2').fontSize(18).fontColor(Color.White).fontWeight(FontWeight.Medium)
          Text('权重是左右两块的 2 倍').fontSize(14).fontColor('#FFFFFFCC').margin({ top: 8 })
        }.width('100%').height('100%').justifyContent(FlexAlign.Center)
          .alignItems(HorizontalAlign.Center).backgroundColor('#F57C00')
      }.layoutWeight(2)

      // 4. 区域 C —— layoutWeight = 1
      Expanded() {
        Row() {
          Text('layoutWeight = 1').fontSize(18).fontColor(Color.White).fontWeight(FontWeight.Medium)
        }.width('100%').height('100%').justifyContent(FlexAlign.Center).backgroundColor('#8E24AA')
      }.layoutWeight(1)

      // 5. 底部说明 —— 固定高度 80vp
      Column() {
        Text('布局说明').fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333333')
        Text('Column + Expanded + layoutWeight').fontSize(13).fontColor('#666666')
        Text('Expanded 子项按 layoutWeight 比例撑满剩余空间').fontSize(13).fontColor('#666666')
      }.width('100%').height(80).backgroundColor('#F5F5F5')
        .justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center).padding(10)
    }
    .width('100%').height('100%').alignItems(HorizontalAlign.Center)
  }
}

4.2 布局结构拆解

┌──────────────────────────────────┐
│      Column + Expanded 自适应布局  │  ← 固定 60vp,深蓝
├──────────────────────────────────┤
│         layoutWeight = 1          │  ← 权重 1,绿色,弹性区 1/4
├──────────────────────────────────┤
│         layoutWeight = 2          │  ← 权重 2,橙色,弹性区 2/4
│      权重是左右两块的 2 倍          │
├──────────────────────────────────┤
│         layoutWeight = 1          │  ← 权重 1,紫色,弹性区 1/4
├──────────────────────────────────┤
│  布局说明 · Column+Expanded+…     │  ← 固定 80vp,浅灰
└──────────────────────────────────┘

4.3 代码设计要点

(1)视觉分区明确: 每个区域使用不同背景色(深蓝、绿、橙、紫、浅灰)区分边界。

(2)固定与弹性分离: 标题 60vp 和底栏 80vp 固定高度,弹性区在 Expanded 内。这种「固定 — 弹性 — 固定」结构是经典应用模式。

(3)权重比例可观察: 区域 A 和 C 权重同为 1,B 权重为 2,运行时橙色区高度恰为绿/紫区的两倍。

(4)显式指定权重: 虽默认值为 1,但显式写出 .layoutWeight(1/2) 提高了代码可读性。


五、自适应布局的核心价值

5.1 什么是自适应布局

鸿蒙生态中设备形态多样:折叠屏展开前后、平板横竖屏切换、不同手机分辨率、车机屏幕各异。自适应布局(Adaptive Layout)使页面能根据屏幕空间自动调整。Column + Expanded 是实现垂直自适应的最直接手段。

5.2 解决的问题

问题 传统做法 Column + Expanded 方案
不同屏幕高度下内容断层 固定 px 硬编码 按比例自动填充
横竖屏切换布局失衡 两套布局文件 一套布局自动适应
折叠屏展开后空白 额外适配 Expanded 自动拉伸
多端公用页面 多套代码维护 一套代码自适应

5.3 布局容器选型

布局 适合场景 不适合
Column + Expanded 垂直按比例分配 二维网格、层叠
Row + Expanded 水平按比例分配 垂直布局
Stack 层叠覆盖 线性排列
Flex 灵活轴线+换行 简单垂直
RelativeContainer 锚定对齐 比例分配
Grid 二维网格 非规则伸缩
List 长列表 固定比例

六、layoutWeight 进阶用法

6.1 非整数权重

支持浮点数实现精细比例控制。

Expanded() { /* 左 */ }.layoutWeight(1)
Expanded() { /* 右 */ }.layoutWeight(1.618)  // 黄金比例

6.2 嵌套使用

Expanded 内可继续嵌套 Column/Row 和 Expanded,实现多级比例分配。

Column() {
  Expanded() {
    Row() {
      Expanded() { /* 左 30% */ }.layoutWeight(1)
      Expanded() { /* 右 70% */ }.layoutWeight(2.33)
    }
  }.layoutWeight(2)
  Expanded() { /* 下方区域 */ }.layoutWeight(3)
}

6.3 与固定尺寸混合

Column() {
  ToolBar().height(56)
  Expanded() { ContentArea() }.layoutWeight(1)
  StatusBar().height(32)
}

七、实际项目应用模式

7.1 经典三段式

Column() {
  TitleBar().height(56)
  Expanded() { ContentArea() }.layoutWeight(1)
  BottomTabBar().height(64)
}

7.2 等分与主次

// 等分卡片
Column() {
  Expanded() { CardA() }.layoutWeight(1)
  Expanded() { CardB() }.layoutWeight(1)
}

// 主次区域(聊天界面)
Column() {
  Expanded() { MessageList() }.layoutWeight(3)
  Expanded() { InputArea() }.layoutWeight(1)
}

7.3 横纵结合

Column() {
  Expanded() {
    Row() {
      Expanded() { LeftPanel() }.layoutWeight(1)
      Divider().width(1).height('100%').backgroundColor('#CCC')
      Expanded() { RightPanel() }.layoutWeight(2)
    }.width('100%').height('100%')
  }.layoutWeight(1)
  StatusBar().height(32)
}

7.4 配合 Scroll 和 List

// Scroll(Expanded 在外,Scroll 在内)
Column() {
  Expanded() {
    Scroll() {
      Column() { ForEach(this.data, (item) => { ListItem(item) }) }
    }.scrollBar(BarState.Off)
  }.layoutWeight(1)
  FixedBar().height(60)
}

// List
Column() {
  Expanded() {
    List() {
      ForEach(this.list, (item) => { ListItem() { ItemCard(item) } })
    }.width('100%').height('100%')
  }.layoutWeight(1)
}

八、性能分析

8.1 布局计算

单次线性遍历,时间复杂度 O(n)。RelativeContainer(锚点约束更高)、Flex 带 wrap(换行计算更高)、Grid(二维计算更高)。

8.2 建议

指标 建议
Expanded 数量 不超过 10 个
嵌套层级 不超过 5 层
动态更新 支持响应式,频繁更新会触发重排

8.3 优化建议

  • 使用 @Builder 提取可复用布局
  • 使用 LazyForEach 懒加载
  • Expanded 内避免大量同级组件,优先用 List
  • 合理使用 @Prop/@Link 控制更新范围

九、常见陷阱与解决方案

9.1 Expanded 不生效

原因: 父容器 Column 未设置 height('100%')

解决:

Column() { Expanded() { Text('内容') } }.width('100%').height('100%')

9.2 内部子组件未占满

原因: 子组件未设置 100% 宽高。

解决: 设置 .width('100%').height('100%')

9.3 Scroll 内使用 Expanded

原因: Scroll 提供无限空间,Expanded 无法确定边界。

解决: Expanded 在外,Scroll 在内。见 7.4 节。

9.4 不同设备高度不一致

原因: 安全区域高度不同。

解决: 使用 expandSafeArea()

Column().width('100%').height('100%').expandSafeArea()

9.5 嵌套权重混乱

原因: 内外层权重独立分层计算,外层决定整屏比例,内层决定容器内比例。二者互不干扰。


十、与状态管理联动

10.1 响应式权重

layoutWeight 可绑定 @State 变量。

@Entry
@Component
struct DynamicPage {
  @State left: number = 1
  @State right: number = 1

  build() {
    Column() {
      Expanded() {
        Text('左侧').fontSize(24).fontColor(Color.White)
          .width('100%').height('100%').textAlign(TextAlign.Center)
          .backgroundColor('#1565C0')
      }.layoutWeight(this.left)

      Expanded() {
        Text('右侧').fontSize(24).fontColor(Color.White)
          .width('100%').height('100%').textAlign(TextAlign.Center)
          .backgroundColor('#C62828')
      }.layoutWeight(this.right)
    }.width('100%').height('100%')
  }
}

10.2 动态隐藏/展开

@Entry
@Component
struct CollapsiblePanel {
  @State expanded: boolean = true

  build() {
    Column() {
      Expanded() {
        Text('主内容').width('100%').height('100%').backgroundColor('#E8F5E9')
      }.layoutWeight(1)

      if (this.expanded) {
        Expanded() {
          Column() {
            Text('折叠面板').fontSize(20).fontColor(Color.White)
            Text('额外信息').fontSize(14).fontColor('#FFFFFFCC')
          }.width('100%').height('100%')
            .justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)
            .backgroundColor('#37474F')
        }.layoutWeight(1)
      }
    }.width('100%').height('100%')
  }
}

十一、不同设备形态

设备形态 表现 建议
手机竖屏 最自然,高度充足 默认布局
手机横屏 弹性区自动缩小,不断裂 可配合媒体查询
平板/折叠屏展开 弹性区等比例拉伸 零修改适配
车机/智慧屏 大屏内容过度拉伸 配合 constraintSize 限制宽度
Column() {
  Expanded() { ContentArea().constraintSize({ maxWidth: 600 }) }.layoutWeight(1)
}.alignItems(HorizontalAlign.Center).width('100%').height('100%')

十二、编码规范

12.1 命名与注释

  • 组件后缀使用 PagePanelSection
  • 注释标注权重比例
// 主内容 : 侧边栏 = 2 : 1
Expanded() { MainContent() }.layoutWeight(2)
Expanded() { Sidebar() }.layoutWeight(1)

12.2 组件抽象

Expanded 子组件逻辑复杂时抽取为独立组件。

// ✅ 推荐
Expanded() { ProfileCard() }.layoutWeight(1)

12.3 测试建议

  • 不同屏幕尺寸模拟器验证比例
  • 横竖屏切换验证稳定性
  • 测试动态修改 layoutWeight 的过渡
  • 确保 Expanded 内无固定高度组件覆盖弹性行为

十三、总结

Column+Expanded 是鸿蒙 ArkTS 最实用的布局组合之一,以简单规则实现灵活空间分配,自适应多种设备形态。

核心要义:

  1. Column 提供垂直排列框架,.width('100%').height('100%') 确保容器有明确边界。
  2. ExpandedlayoutWeight 权重比例分配剔除固定组件后的剩余空间。
  3. layoutWeight 支持整数和浮点数,实现任意比例分配,支持响应式更新。
  4. 与固定尺寸组件混用是核心设计模式,顶栏+弹性区+底栏的三段式是最经典应用。
  5. 注意陷阱:父容器必须有明确高度、内部子组件需设 100% 宽高、Scroll 内不能直接使用 Expanded。

在 HarmonyOS NEXT 推进多端协同的背景下,掌握 Column + Expanded 是理解鸿蒙设计哲学的重要一步——以弹性应对变化,以比例连接万物


附录:参考资料

  • 华为开发者官网:HarmonyOS NEXT ArkTS 布局开发指南
  • @Component@Entry 装饰器使用说明
  • Column 组件 API 参考文档
  • Expanded 组件 API 参考文档
  • layoutWeight 属性详解
  • 鸿蒙自适应布局最佳实践
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐