大家好,我是[晚风依旧似温柔],新人一枚,欢迎大家关注~

前言

说句大实话,很多鸿蒙界面之所以看起来“说不上来哪不对劲”,问题往往不在配色、不在图标,而在——布局没写明白
  Row/Column 到处乱套、Flex 啥都用 default, Grid 永远只是“平均分三列”,Stack 只用来“叠个悬浮按钮”,搞着搞着你会发现:

代码没多少,页面也不复杂,就是一股“移动网页 2009 年风格”的味儿。

ArkUI 本身其实是非常适合写好看界面的——关键是你要真正理解这几个主力布局容器:
Row、Column、Stack、Flex、Grid
它们不是“想用哪个用哪个”的关系,而是各司其职,有自己的“专长战场”。

今天这篇,就按你给的大纲,一口气把鸿蒙布局系统从底层逻辑到实战场景拆个干净:

5 大布局容器 → Flex 核心属性 → Grid 实战 → 响应式布局 → 常见 UI 设计模式

写完之后,你应该能做到:

  • 拿到一个设计稿,大致能在脑子里“翻译”为 Row/Column/Flex 组合
  • 不再对对齐方式一脸懵,不再靠来回调 margin() 试错
  • 列表、宫格、卡片、悬浮按钮、底部弹框,都有一套顺手的写法

一、5 大布局容器:先搞清谁是“砖”,谁是“水泥”

在 ArkUI 里,你基本离不开这五个布局:

  • Column:垂直排列
  • Row:水平排列
  • Stack:层叠摆放(前后覆盖)
  • Flex:弹性布局,更强的轴对齐和换行能力
  • Grid:栅格 / 宫格布局

可以这——么粗暴地理解:

容器 主战场
Column 一列上下排(设置页、列表、表单)
Row 一行左右排(标题 + 按钮、图标 + 文本)
Stack 叠东西(悬浮按钮、角标、蒙层)
Flex 复杂对齐 / 自适应 / 换行布局
Grid 宫格、九宫格、商品宫格、图片墙

下面先快速过一遍概念,再用代码说话。


1.1 Column:所有“从上往下”的布局都该从它开始

典型用法:

Column() {
  Text('主标题')
    .fontSize(24)
    .fontWeight(FontWeight.Bold)

  Text('副标题,介绍一下当前页面的用途')
    .fontSize(14)
    .opacity(0.6)
    .margin({ top: 4 })

  Button('下一步')
    .margin({ top: 20 })
}
.width('100%')
.padding(16)

特点:

  • 默认主轴:垂直方向

  • 默认交叉轴:水平居左

  • 常用属性:

    • .justifyContent():主轴方向的对齐(靠上/中/下、平均分布)
    • .alignItems():交叉轴的对齐(左/中/右)

1.2 Row:所有“左右排一行”的东西归它管

典型场景:左边 icon,右边标题 + 副标题:

Row() {
  Image($rawfile('ic_user.png'))
    .width(32).height(32)
    .margin({ right: 12 })

  Column() {
    Text('用户名').fontSize(16)
    Text('签名 / 描述文本').fontSize(12).opacity(0.6)
  }
}
.width('100%')
.padding(12)
.alignItems(VerticalAlign.Center)

Row 跟 Column 一样,也有:

  • .justifyContent(FlexAlign.Start/Center/End/SpaceBetween/SpaceAround)
  • .alignItems(VerticalAlign.Top/Center/Bottom)

1.3 Stack:当你想把东西叠起来时

比如一个卡片右上角有角标、右下角有悬浮按钮:

Stack() {
  // 底层卡片
  Column() {
    Text('VIP 会员').fontSize(20)
    Text('尊享更多特权').fontSize(14).opacity(0.6)
  }
  .padding(16)
  .width('100%')
  .backgroundColor('#FFFFFF')
  .borderRadius(12)

  // 右上角角标
  Text('推荐')
    .fontSize(10)
    .padding({ left: 6, right: 6, top: 2, bottom: 2 })
    .backgroundColor('#FF5555')
    .borderRadius(8)
    .align(Alignment.TopEnd)
    .margin({ top: 8, right: 8 })

  // 右下角悬浮按钮
  Button('开通')
    .align(Alignment.BottomEnd)
    .margin({ bottom: 12, right: 12 })
}
.width('100%')

Stack 的核心就两个字:叠 + 对齐
.align(Alignment.XXX),谁就跑到哪个角落。


1.4 Flex:Row/Column 的“超进化”

Row/Column 解决的是“一维排列”,Flex 在此基础上给了你:

  • 更强的 轴对齐:主轴、交叉轴细粒度控制
  • 子元素按比例分配空间(flexGrow/flexShrink)
  • 自动换行

语法大概长这样:

Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap }) {
  // 一串 tag,超过一行自动换行
  tags.forEach(tag => {
    Text(tag)
      .padding({ left: 8, right: 8, top: 4, bottom: 4 })
      .borderRadius(12)
      .backgroundColor('#F0F0F0')
      .margin({ right: 8, bottom: 8 })
  })
}
.width('100%')
.justifyContent(FlexAlign.Start)
.alignItems(ItemAlign.Center)

当你发现 Row/Column 开始写得很吃力、对齐非常难调时,往往就是 Flex 上场的信号。


1.5 Grid:宫格你总不能用 9 个 Row 吧?

Grid 的典型场景:

  • 首页功能宫格:4 列 * 2 行
  • 图片九宫格
  • 商品列表(左图右文、多列宫格)

ArkUI 提供了 Grid + GridItem 这样的组合:

Grid() {
  ForEach(this.menus, (item) => {
    GridItem() {
      Column() {
        Image(item.icon).width(32).height(32)
        Text(item.title).fontSize(12).margin({ top: 6 })
      }
      .width('100%')
      .height(80)
      .justifyContent(FlexAlign.Center)
      .alignItems(HorizontalAlign.Center)
    }
  }, item => item.id.toString())
}
.columnsTemplate('1fr 1fr 1fr 1fr') // 四列
.rowsTemplate('80vp 80vp')          // 行高(可选)
.columnsGap(12)
.rowsGap(12)

Grid 的精髓在于:用模板字符串描述列宽 / 行高分配规则


二、Flex 布局核心属性:真的很像前端 Flexbox,但别全照搬

如果你有 Web 前端经验,那 Flex 这块学习成本会低很多,说白了就是 ArkUI 版 Flexbox。

2.1 FlexDirection:主轴方向

Flex({ direction: FlexDirection.Row }) { ... }   // 横排
Flex({ direction: FlexDirection.Column }) { ... } // 竖排

选错方向,一切对齐都跟着乱。


2.2 主轴对齐:justifyContent

值基本一眼就懂:

.justifyContent(FlexAlign.Start)         // 靠头
.justifyContent(FlexAlign.Center)        // 居中
.justifyContent(FlexAlign.End)           // 靠尾
.justifyContent(FlexAlign.SpaceBetween)  // 两端对齐,元素间等距
.justifyContent(FlexAlign.SpaceAround)   // 元素间及两侧都留间距

示例——标签云居中展示:

Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap }) {
  tags.forEach(tag => {
    Text(tag)
      .padding({ left: 10, right: 10, top: 4, bottom: 4 })
      .backgroundColor('#F5F5F5')
      .borderRadius(12)
      .margin({ bottom: 8, right: 8 })
  })
}
.width('100%')
.justifyContent(FlexAlign.Center)

2.3 交叉轴对齐:alignItems / alignContent

  • alignItems:一行内的“垂直对齐”
  • alignContent:多行整体在交叉轴上的对齐方式

常见写法:

.alignItems(ItemAlign.Center)
.alignItems(ItemAlign.Start)
.alignItems(ItemAlign.End)

例如:

Flex({ direction: FlexDirection.Row }) {
  Image($rawfile('icon.png')).width(24).height(24)
  Text('带图标的文本').fontSize(16)
}
.alignItems(ItemAlign.Center) // 图片 & 文本竖直居中

2.4 子元素弹性:flexGrow、flexShrink、flexBasis

有时你会遇到这种需求:

  • 左边是标题,右边是按钮,想让标题自动占满剩余空间
  • 或者几个块按比例分配宽度(比如 2:1:1)

ArkUI 对子项可以用 .flexGrow() 之类的方式(不同版本 API 细节可能略有变化,这里用语义说明):

Flex({ direction: FlexDirection.Row }) {
  Text('一个很长很长的标题')
    .flexGrow(1) // 占剩余空间

  Button('操作')
}
.width('100%')

再比如:三列按比例分配宽度:

Flex({ direction: FlexDirection.Row }) {
  Column() { Text('A') }.flexGrow(2) // 占 2 份
  Column() { Text('B') }.flexGrow(1) // 占 1 份
  Column() { Text('C') }.flexGrow(1) // 占 1 份
}
.width('100%')

这里的数字是“相对权重”,而不是实际像素。


2.5 wrap:是否自动换行

FlexWrap.NoWrap(默认) / FlexWrap.Wrap / FlexWrap.WrapReverse

比如标签云、选中标签、多选按钮区域,典型写法:

Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap }) {
  this.options.forEach(opt => {
    Text(opt.label)
      .padding({ left: 10, right: 10, top: 4, bottom: 4 })
      .margin({ right: 8, bottom: 8 })
      .backgroundColor(opt.selected ? '#333333' : '#F0F0F0')
      .fontColor(opt.selected ? '#FFFFFF' : '#333333')
      .borderRadius(16)
  })
}
.width('100%')

Row/Column 要做这种效果就比较勉强,而 Flex 做起来非常自然。


三、Grid 布局实践:不是只能“平均分三列”的

Grid 的威力完全不比 Flex 小,尤其是宫格类界面。

3.1 基本语法:Grid + GridItem

以一个“功能入口宫格”为例:

@Entry
@Component
struct MenuGridExample {
  private menus = [
    { id: 1, title: '扫描', icon: $rawfile('ic_scan.png') },
    { id: 2, title: '收款', icon: $rawfile('ic_pay.png') },
    { id: 3, title: '账单', icon: $rawfile('ic_bill.png') },
    { id: 4, title: '会员', icon: $rawfile('ic_vip.png') },
    { id: 5, title: '设置', icon: $rawfile('ic_setting.png') },
  ];

  build() {
    Grid() {
      ForEach(this.menus, item => {
        GridItem() {
          Column() {
            Image(item.icon).width(32).height(32)
            Text(item.title).fontSize(12).margin({ top: 6 })
          }
          .width('100%')
          .height(80)
          .justifyContent(FlexAlign.Center)
          .alignItems(HorizontalAlign.Center)
        }
      }, item => item.id.toString())
    }
    .columnsTemplate('1fr 1fr 1fr 1fr')
    .rowsTemplate('80vp 80vp')
    .columnsGap(12)
    .rowsGap(12)
    .padding(12)
  }
}

解释一下几个关键:

  • columnsTemplate('1fr 1fr 1fr 1fr'):四列,每列等宽(fraction)
  • rowsTemplate('80vp 80vp'):两行,每行高度 80vp;如果菜单多,还会自动往下加
  • columnsGap / rowsGap:列间距 / 行间距

3.2 不规则宽度:如 1 + 3 栅格

有时你想做类似“左边一个大卡片,右边两个小卡片”的布局:

Grid() {
  // 左边大卡片:跨两行
  GridItem() {
    BigCard()
  }.rowSpan(2) // 跨两行

  // 右上小卡片
  GridItem() {
    SmallCard('A')
  }

  // 右下小卡片
  GridItem() {
    SmallCard('B')
  }
}
.columnsTemplate('2fr 1fr')   // 左边宽一点,右边窄一点
.rowsTemplate('120vp 120vp')
.columnsGap(12)
.rowsGap(12)

这类栅格在首页 Banner / 推荐位上特别好用。


3.3 图片九宫格:自动计算行列

假设最多 3 列,小于 3 时自动裁;ArkUI Grid 本身就会根据 items 数量自动铺,你只要重点写好 columnsTemplate 即可:

Grid() {
  ForEach(this.photos, (src, index) => {
    GridItem() {
      Image(src)
        .objectFit(ImageFit.Cover)
        .borderRadius(6)
    }
  }, idx => idx.toString())
}
.columnsTemplate('1fr 1fr 1fr')
.rowsGap(4)
.columnsGap(4)

剩下就是按业务决定:

  • 1 张图 → 你可能用 Row/Column 做大图
  • 2–3 张 → 平均分
  • 4 张以上 → Grid 九宫格

四、响应式布局技巧:让你的页面在不同尺寸下不“散架”

鸿蒙现在涉及的设备越来越多:手机、平板、车机、甚至大屏。
如果你还是写死 300vp400vp 到处乱用,界面在不同尺寸上会非常怪。

4.1 百分比布局:width('100%') 不是摆设

能用相对,就尽量不用绝对。

  • 宽度优先用 '100%''50%',少用写死 350vp
  • 外边距 / 内边距可以有一些固定值,但核心结构尽量宽度自适应
Column() {
  Text('标题').fontSize(20)
  Text('内容文本...').fontSize(14).margin({ top: 8 })
}
.width('100%')
.padding({ left: 16, right: 16, top: 12, bottom: 12 })

4.2 Flex + wrap 解决“小屏挤不下的问题”

比如顶部一排操作按钮:

Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap }) {
  ['筛选', '排序', '导出', '分享', '更多'].forEach(text => {
    Button(text)
      .margin({ right: 8, bottom: 8 })
  })
}
.width('100%')

小屏幕上会自动折行,大屏幕上则一行排完。


4.3 Grid 动态列数:根据屏宽算列数

你可以根据屏幕宽度动态生成 columnsTemplate

function getColumnsTemplate(screenWidth: number): string {
  const minItemWidth = 100; // 每个卡片最小宽度
  const colCount = Math.max(2, Math.floor(screenWidth / minItemWidth));
  return Array(colCount).fill('1fr').join(' ');
}

aboutToAppear 里算一次,或者监听尺寸变化。


4.4 Breakpoint 思路:用条件分支控制布局方式

比如在窄屏上用单列,宽屏上用左右两列:

@if (this.isWideScreen) {
  Row() {
    LeftPanel().width('40%')
    RightPanel().width('60%')
  }.width('100%')
} @else {
  Column() {
    LeftPanel().width('100%')
    Divider().margin({ top: 12, bottom: 12 })
    RightPanel().width('100%')
  }
}

isWideScreen 可以根据 ScreenUtils.getWidth() 或窗口宽度来判断。


五、常见 UI 设计模式示例:直接给你几套可以搬进项目的

光讲概念不爽,咱们来搞几个实战模板。


5.1 设置页条目:左文右箭头 / 开关

@Component
struct SettingsItem {
  @Prop title: string = '';
  @Prop desc: string = '';
  @Prop showSwitch: boolean = false;

  @State checked: boolean = false;

  build() {
    Row() {
      Column() {
        Text(this.title)
          .fontSize(16)
        if (this.desc) {
          Text(this.desc)
            .fontSize(12)
            .opacity(0.6)
            .margin({ top: 4 })
        }
      }
      .flexGrow(1)

      if (this.showSwitch) {
        Toggle({ type: ToggleType.Switch, isOn: this.checked })
          .onChange((v) => this.checked = v)
      } else {
        Image($rawfile('ic_arrow_right.png'))
          .width(16).height(16)
      }
    }
    .width('100%')
    .padding({ left: 16, right: 16, top: 12, bottom: 12 })
    .alignItems(VerticalAlign.Center)
  }
}

这里有几个布局点:

  • Row + 左 Column + 右控件
  • 中间 Column flexGrow(1) 撑开,把右侧推到最右边

5.2 卡片信息流:图片 + 文本 + 底部操作

@Component
struct ArticleCard {
  @Prop title: string = '';
  @Prop summary: string = '';
  @Prop cover: Resource;

  build() {
    Column() {
      // 封面
      Image(this.cover)
        .width('100%')
        .height(180)
        .objectFit(ImageFit.Cover)
        .borderRadius({ topLeft: 12, topRight: 12 })

      // 文本部分
      Column() {
        Text(this.title)
          .fontSize(16)
          .fontWeight(FontWeight.Medium)
          .maxLines(2)
          .textOverflow({ overflow: TextOverflow.Ellipsis })

        Text(this.summary)
          .fontSize(13)
          .opacity(0.7)
          .maxLines(2)
          .textOverflow({ overflow: TextOverflow.Ellipsis })
          .margin({ top: 4 })

        Row() {
          Text('2.3k 阅读').fontSize(11).opacity(0.6)
          Blank().flexGrow(1)
          Text('刚刚').fontSize(11).opacity(0.6)
        }
        .margin({ top: 8 })
      }
      .padding(12)
    }
    .backgroundColor('#FFFFFF')
    .borderRadius(12)
    .shadow({ radius: 4, color: Color.fromARGB(30, 0, 0, 0) })
  }
}

这里就用到了:

  • Column 包裹整体
  • 内部 Row 做左右对齐 + Blank().flexGrow(1) 撑开中间空白
  • 图片用上边缘圆角,整体卡片再包一层圆角

5.3 Tab + 内容区:顶部标签 + Flex 均分

@Entry
@Component
struct TabLayoutExample {
  @State current: number = 0;
  private tabs: string[] = ['推荐', '热门', '最新'];

  build() {
    Column() {
      // Tab 条
      Row() {
        this.tabs.forEach((t, index) => {
          Column() {
            Text(t)
              .fontSize(16)
              .fontWeight(this.current === index ? FontWeight.Bold : FontWeight.Normal)
            if (this.current === index) {
              // 底部高亮条
              Rect()
                .width('60%').height(2).backgroundColor('#007DFF')
                .margin({ top: 4 })
            }
          }
          .flexGrow(1)
          .alignItems(HorizontalAlign.Center)
          .onClick(() => this.current = index)
        })
      }
      .width('100%')
      .padding({ left: 16, right: 16, top: 10, bottom: 10 })

      // 内容区
      if (this.current === 0) {
        RecommendList()
      } else if (this.current === 1) {
        HotList()
      } else {
        LatestList()
      }
    }
    .width('100%')
    .height('100%')
  }
}

Row + 每个 Tab flexGrow(1),自然平分宽度。


5.4 底部操作栏 + 内容区域:经典 ButtonBar 布局

@Entry
@Component
struct BottomBarLayout {
  build() {
    Stack() {
      // 内容区
      Column() {
        // 这里放主内容
        Text('页面内容...').margin({ top: 20 })
        Blank()
      }
      .width('100%')
      .height('100%')
      .backgroundColor('#F5F5F5')

      // 底部操作栏
      Row() {
        Text('合计:¥ 299.00')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
        Blank().flexGrow(1)
        Button('去结算')
          .width(120)
      }
      .padding({ left: 16, right: 16, top: 10, bottom: 10 })
      .backgroundColor('#FFFFFF')
      .align(Alignment.Bottom)
    }
    .width('100%')
    .height('100%')
  }
}

Stack 把底部条“压”在内容上面:

  • 内容区填满
  • 底部 Row .align(Alignment.Bottom) 固定贴底

小结:布局不是“多会几个容器”,而是“知道用谁合适”

写 ArkUI 布局这件事,真的可以一句话概括:

Row/Column 打基础,Stack 解决叠放,Flex 处理复杂对齐,Grid 管好宫格。

当你遇到问题时,可以试着问自己:

  • “这是一维的还是二维的?” → 一维大多用 Row/Column/Flex,二维通常 Grid
  • “需要换行吗?” → 需要的话 Flex(Grid) 更合适
  • “这个状态是叠加的还是平铺的?” → 叠加就 Stack
  • “需要按比例划分空间吗?” → Flex 的 flexGrow、Grid 的 fr

只要心里有这几条判断线,你的 ArkUI 布局就不会再陷入那种——
“我先随便写个 Column + 一堆 margin,能看就行” 的阶段了。

如果觉得有帮助,别忘了点个赞+关注支持一下~
喜欢记得关注,别让好内容被埋没~

Logo

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

更多推荐