上一篇讲 Row / Column,解决的是一组组件按横向或纵向排列的问题。实际页面里还有另一类结构:组件不是排成一行或一列,而是叠在同一块区域里。
常见场景包括:
- 图片底部压一层标题和说明。
- 封面图上覆盖一个半透明遮罩。
- 页面内容上方临时盖一个加载层或空状态层。
- 单独一个角标贴在图片角落。
这类结构放到 ArkUI 里,优先看 Stack。
本文只解决一个问题:如何用 Stack 搭一个“封面图 + 底部信息浮层”的内容卡片。 不覆盖:右上角角标 + 中间播放按钮 + 底部说明同时存在的复杂封面卡片。这种多控件、多锚点布局更适合作为后续 RelativeContainer 的例子:外层用 Stack 放封面图,内部控件再交给 RelativeContainer 处理相对定位。
先看要搭的 UI 结构
先定一个基础场景:一个课程/文章卡片。
它由两层组成:
Stack:整张封面区域
Image:封面图,铺满底层
Column:底部标题和说明,覆盖在图片下方
Row / Column 关心“沿哪个方向排”;Stack 关心“哪几层叠在同一块区域里,以及这一层默认贴到哪里”。
ArkUI:用 Stack 写封面卡片
先看 ArkUI 版本:
@Component
struct CourseCoverCard {
build() {
Stack({ alignContent: Alignment.BottomStart }) {
Image($r('app.media.course_cover'))
.width('100%')
.height('100%')
.objectFit(ImageFit.Cover)
Column() {
Text('ArkUI 布局入门')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.fontColor(Color.White)
Text('Stack 适合底图、遮罩和底部信息浮层')
.fontSize(12)
.fontColor('#E5E7EB')
.margin({ top: 4 })
}
.width('100%')
.padding(12)
.backgroundColor('#00000080')
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.height(180)
.borderRadius(16)
.clip(true)
}
}
实现效果:

这段代码里,Stack 管的是封面区域,两个子组件共享同一个 180vp 高度的容器。
Image先写,作为底图。ImageFit.Cover让图片等比铺满,比例不一致时自动裁掉多余部分。Column后写,覆盖在图片上。Stack({ alignContent: Alignment.BottomStart })把未单独定位的浮层放到底部左侧。Column.width('100%')让底部浮层横向铺满整张封面。
写 Stack 时可以先按这三个问题拆: 1. 底层是什么:图片、色块,还是某个完整组件? 2. 上面盖什么:标题、遮罩、按钮,还是状态层? 3. 覆盖层整体贴到哪里:顶部、中心、底部,还是某个角?
alignContent:先管整组子组件的默认位置
Stack({ alignContent: Alignment.BottomStart }) 里的 alignContent 是容器级的默认对齐方式。没有额外定位的子组件,会按这个默认值摆放。
在这个例子里,底部说明没有写 position:
Column() {
Text('ArkUI 布局入门')
Text('Stack 适合底图、遮罩和底部信息浮层')
}
.width('100%')
.padding(12)
.backgroundColor('#00000080')
它会落在 Stack 的底部左侧,因为外层写了:
Stack({ alignContent: Alignment.BottomStart }) {
// ...
}
规则记忆要点:如果覆盖层只有一个主要位置,优先让 Stack 的 alignContent 解决。
单个角标可以用 Stack,复杂控件留给 RelativeContainer
如果只是“图片右上角放一个 NEW”,Stack 也可以胜任。这个场景只有一个覆盖层,写法应该保持简单:
Stack({ alignContent: Alignment.TopEnd }) {
Image($r('app.media.course_cover'))
.width('100%')
.height('100%')
.objectFit(ImageFit.Cover)
Text('NEW')
.fontSize(11)
.fontWeight(FontWeight.Bold)
.fontColor(Color.White)
.padding({ left: 8, right: 8, top: 3, bottom: 3 })
.backgroundColor('#EF4444')
.borderRadius(10)
.margin({ top: 10, right: 10 })
}
.width('100%')
.height(180)
呈现的效果:

但如果需求变成这样:
- 右上角有
NEW角标。 - 中间有播放按钮。
- 底部有标题和说明。
- 后面还可能加收藏、进度、标签、倒计时。
这时就不适合把所有控件都直接塞到 Stack 里各自找位置。更自然的结构是:
Stack:负责图片和整体覆盖区域
Image:底图
RelativeContainer:负责内部控件的相对定位
Text:右上角角标
Button:居中播放按钮
Column:底部标题说明
RelativeContainer 后面单独讲。这里先把 Stack 的心智模型立住:它擅长做层叠,不擅长承担复杂控件之间的相对约束。
zIndex:顺序不清楚时显式写出来
Stack 的子组件会形成层叠关系。简单页面里,按代码顺序写通常够用:底图先写,覆盖层后写。
但业务 UI 变复杂后,可以给关键层写 zIndex:
Image($r('app.media.course_cover'))
.zIndex(0)
Column() {
Text('ArkUI 布局入门')
}
.zIndex(1)
这样后面调整代码块顺序时,不容易把浮层压到图片下面。
Android XML:FrameLayout 是最接近的参照
传统 Android View 体系里,最接近 Stack 的容器是 FrameLayout。它也常用于“底图 + 底部浮层”:
<FrameLayout
android:layout_width="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="180dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:orientation="vertical"
android:padding="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ArkUI 布局入门" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Stack 适合底图、遮罩和底部信息浮层" />
</LinearLayout>
</FrameLayout>
对比 ArkUI,可以这样看:
FrameLayout↔Stackandroid:scaleType="centerCrop"↔.objectFit(ImageFit.Cover)android:layout_gravity="bottom"↔Stack({ alignContent: Alignment.BottomStart })- 子 View 的覆盖关系 ↔
Stack子组件顺序 /.zIndex(...)
如果读者没有 Android 背景,只记 ArkUI 这边也够:先放底层,再放覆盖层;覆盖层只有一个主要位置时,先用 alignContent。
Compose:Box 的名字不同,思路相同
Jetpack Compose 里对应的是 Box:
@Composable
fun CourseCoverCard() {
Box(
modifier = Modifier
.fillMaxWidth()
.height(180.dp)
.clip(RoundedCornerShape(16.dp))
) {
Image(
painter = painterResource(R.drawable.course_cover),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.matchParentSize()
)
Column(
modifier = Modifier
.align(Alignment.BottomStart)
.fillMaxWidth()
.background(Color.Black.copy(alpha = 0.5f))
.padding(12.dp)
) {
Text("ArkUI 布局入门")
Text("Stack 适合底图、遮罩和底部信息浮层")
}
}
}
Compose 的 Box 与 ArkUI 的 Stack 很像:容器负责叠放,覆盖层贴到底部、顶部或中心。差异主要在语法:Compose 把位置写进 Modifier,ArkUI 的这个例子用 Stack 的 alignContent 设默认位置。
什么时候用 Stack,什么时候不要用
适合用 Stack 的场景:
- 多层内容共享同一块区域。
- 需要底图、遮罩、底部信息浮层、简单角标。
- 子组件之间主要是覆盖关系,而不是线性排列或相对约束关系。
不适合优先只用 Stack 的场景:
- 组件只是横向或纵向排列,用
Row/Column更直接。 - 同一块封面里有多个控件要分别贴右上角、中心、底部、左右边缘,后面会讲
RelativeContainer。 - 页面级弹窗、半模态页面、Toast、全局浮层,优先看对应的弹窗/浮层能力,不要用普通
Stack硬盖整个应用。
布局容器的选择可以按这个顺序判断:
横着排 / 竖着排 -> Row / Column
叠在同一块区域 -> Stack
需要换行和弹性分布 -> Flex
组件之间互相约束 -> RelativeContainer
多列和断点适配 -> GridRow / GridCol
到这里,线性布局和层叠布局就能覆盖很多基础页面:列表卡片、封面卡片、个人中心头图、视频卡片、商品卡片都可以先从这两个容器组合起来。等讲到 RelativeContainer,再把右上角角标、居中播放按钮、底部说明放回同一个复杂封面卡片里。
参考素材
- HarmonyOS Developers:构建布局
- https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-build-layout
- HarmonyOS Developers:层叠布局(Stack)
- https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-layout-development-stack-layout
- Android Developers:FrameLayout
- https://developer.android.com/reference/android/widget/FrameLayout
- Android Developers:Compose layout basics(Box)
- https://developer.android.com/develop/ui/compose/layouts/basics
- HarmonyOS Samples:sample_in_harmonyos
- https://gitcode.com/HarmonyOS_Samples/sample_in_harmonyos
// Kai@CodeHubble
// 观测坐标:Android-HarmonyOS/2026-05-20