鸿蒙应用开发UI基础第十二节:Stack叠层布局核心讲解与实战演示

【学习目标】

  1. 理解Stack叠层布局的核心定位与能力,掌握其构造参数、层叠控制规则;
  2. 掌握Alignment枚举9个取值及对应对齐效果,熟练使用zIndex实现组件层级手动控制;
  3. 掌握offset轻量偏移、position绝对定位的语法与使用规则,实现Stack布局精准层叠定位;
  4. 通过基础示例落地核心知识点,结合移动端高频实战案例,实现从Stack基础到业务开发的转化。

一、Stack叠层布局核心基础

1.1 核心定位与适用场景

Stack是鸿蒙ArkTS中实现组件Z轴层叠排列 的基础布局容器,核心能力为让多个子组件在同一空间区域内上下叠加显示,支持子组件的统一对齐和层级灵活控制。

适用于消息角标、商品卡片、悬浮按钮、层叠遮罩、图标叠加等多组件共用同一空间且需要视觉层叠的开发场景,无需嵌套复杂线性布局即可实现空间复用。

1.2 核心构造参数

Stack布局仅提供一个构造参数,用于统一设置容器内所有子组件的默认对齐位置,语法如下:

javascript 复制代码
Stack(value?: { alignContent?: Alignment })

1.3 子组件全局对齐(Alignment)

  • 作用:设置Stack容器内所有子组件的默认对齐方位,决定子组件在容器内的初始布局位置;
  • 默认值:Alignment.Center(所有子组件默认在容器正中对齐);
  • 取值:鸿蒙标准Alignment枚举9个选项,覆盖所有对齐场景:
    • 顶部系列:TopStart(左上)、Top(上中)、TopEnd(右上)
    • 中部系列:Start(左中)、Center(正中)、End(右中)
    • 底部系列:BottomStart(左下)、Bottom(下中)、BottomEnd(右下)
  • 基础使用示例:
javascript 复制代码
Stack({ alignContent: Alignment.Start }) {
  Text("左中对齐子组件1")
    .backgroundColor("#E5F2FF")
    .padding({ top: 8, right: 8, bottom: 8, left: 8 });
  Text("左中对齐子组件2")
    .backgroundColor("#FFE5E5")
    .padding({ top: 8, right: 8, bottom: 8, left: 8 });
}
.width(300)
.height(200)
.backgroundColor("#F5F5F5")

1.4 层叠顺序控制规则

层叠顺序是Stack布局的核心能力,所有规则仅在当前Stack容器内生效,不影响其他容器/页面:

  • 默认层叠规则 :遵循后写覆盖先写原则,后编写的子组件默认显示在更上层,会覆盖先编写的子组件(若组件存在重叠区域);
  • 手动层叠规则(zIndex属性) :突破编写顺序限制,实现自定义层级,适用于复杂层叠场景:
    • 取值范围:支持正整数、负整数、0;
    • 核心原则:数值越大层级越高,显示在越上层;数值越小层级越低,显示在越下层;
    • 同值规则:多个子组件zIndex相同时,回归"后写覆盖先写"的默认规则;
  • 层叠控制示例:
javascript 复制代码
Stack({ alignContent: Alignment.Center }) {
  Text("先写组件\n(zIndex=1)")
    .width(180)
    .height(180)
    .backgroundColor("#FFE5E5")
    .zIndex(1); // 层级更高,显示在上方
  Text("后写组件\n(zIndex=0)")
    .width(120)
    .height(120)
    .backgroundColor("#80BFFF")
    .zIndex(0); // 层级更低,显示在下方
}
.width(240)
.height(240)
.backgroundColor("#F5F5F5")

1.5 偏移能力offset(相对定位)

纯视觉位置调整,基于自身原始位置偏移,不改变组件布局属性,不影响布局流,适用于细节贴边、位置校准:

  • 语法:
javascript 复制代码
.offset({ x: Length, y: Length })
// x:水平偏移,正数右移、负数左移;y:垂直偏移,正数下移、负数上移
  • 核心规则:
    1. 仅调整视觉显示位置,组件原有布局占位、兄弟组件位置均不变;
    2. 偏移基准为组件自身的默认布局位置(由Stack对齐规则决定);
    3. 交互区域仍保留在原始布局位置,不跟随视觉偏移;
    4. 适合小范围微调,如消息角标贴边、文字与图标对齐校准等场景。

1.6 绝对定位position

脱离布局流的悬浮定位,以直接父容器Stack为基准,适用于悬浮按钮、标签、角标等场景:

  • 语法:
javascript 复制代码
.position({
  left?: Length,  // 距父容器左侧边界偏移
  top?: Length,   // 距父容器顶部边界偏移
  right?: Length, // 距父容器右侧边界偏移
  bottom?: Length // 距父容器底部边界偏移
})
  • 核心规则:
    1. 脱离布局流,不占用任何布局空间,不影响其他组件的布局和排列;
    2. 定位基准为直接父容器Stack 的物理边界,父容器必须显式设置固定宽高/明确尺寸,否则定位基准丢失,出现偏移/失效;
    3. 默认层级高于未定位组件,可通过zIndex调整层级;
    4. 支持同时设置多个方向偏移(如top:10+right:10),实现精准悬浮定位;
    5. 交互区域跟随视觉定位位置,与偏移后的显示位置一致。

1.7 offset与position核心差异

对比维度 offset 轻量偏移(相对定位) position 绝对定位
布局流影响 无影响,仅视觉调整,保留原占位 完全脱离布局流,无原始占位
定位基准 组件自身默认布局位置 直接父容器Stack物理边界
父容器要求 无强制宽高要求,自适应即可 必须显式设置固定宽高/明确尺寸
调整范围 小范围视觉微调,贴合原位置 全容器自由定位,无位置限制
交互区域位置 保留在原始布局位置 跟随视觉偏移位置
性能影响 无额外性能消耗 少量性能消耗,可忽略
适用场景 角标贴边、位置校准、细节微调 悬浮按钮、热销标签、固定角标

简单解释:offset只是修改了绘制显示位置,实际组件坐标并未修改。position修改了显示位置,同时坐标也不再是原坐标。

二、工程说明

2.1 工程结构

本节创建新工程 StackApplication(基于鸿蒙 API 12+ / Stage 模型),用于编写和运行叠层布局(Stack)相关演示代码,工程核心目录结构如下:

复制代码
StackApplication/
├── AppScope/
├── entry/
│   ├── src/
│   │   ├── main/
│   │   │   ├── ets/
│   │   │   │   ├── entryability/
│   │   │   │   │   └── EntryAbility.ets
│   │   │   │   └── pages/
│   │   │   │       ├── Index.ets       # 入口导航页面
│   │   │   │       ├── StackBasicPage.ets  # 基础能力示例页
│   │   │   │       ├── AppIconBadgePage.ets  # 实战案例1:消息角标
│   │   │   │       └── ProductCardPage.ets   # 实战案例2:商品卡片
│   │   │   ├── resources/
│   │   │   │   └── base/
│   │   │   │       └── media/            # 图片资源目录
│   │   │   └── module.json5
│   └── build-profile.json5
└── build-profile.json5

2.2 资源准备

需将图片资源放入 entry/src/main/resources/base/media 目录:

  1. startIcon.png:App图标资源(用于消息角标案例)
  2. huaweiTaplet.png:华为平板商品图片(用于商品卡片案例)

三、入口导航页面:Index.ets

javascript 复制代码
import { router } from '@kit.ArkUI';

@Entry
@Component
struct Index {
  build() {
    Column({ space: 20 }) {
      // 页面标题
      Text("Stack叠层布局实战教程")
        .fontSize(28)
        .fontWeight(FontWeight.Bold)
        .width('100%')
        .textAlign(TextAlign.Center)
        .margin({ bottom: 40 });

      // 导航按钮1:Stack基础能力演示
      Button("1. Stack基础能力演示")
        .fontSize(18)
        .fontWeight(FontWeight.Medium)
        .fontColor($r('sys.color.white'))
        .backgroundColor('#1890FF')
        .padding({ top: 12, right: 40, bottom: 12, left: 40 })
        .borderRadius(8)
        .onClick(() => {
          router.pushUrl({ url: 'pages/StackBasicPage' });
        })

      // 导航按钮2:实战案例-App图标+消息角标
      Button("2. 实战案例:App图标+消息角标")
        .fontSize(18)
        .fontWeight(FontWeight.Medium)
        .fontColor($r('sys.color.white'))
        .backgroundColor('#13C2C2')
        .padding({ top: 12, right: 40, bottom: 12, left: 40 })
        .borderRadius(8)
        .onClick(() => {
          router.pushUrl({ url: 'pages/AppIconBadgePage' });
        })

      // 导航按钮3:实战案例-商品销售卡片
      Button("3. 实战案例:商品销售卡片")
        .fontSize(18)
        .fontWeight(FontWeight.Medium)
        .fontColor($r('sys.color.white'))
        .backgroundColor('#722ED1')
        .padding({ top: 12, right: 40, bottom: 12, left: 40 })
        .borderRadius(8)
        .onClick(() => {
          router.pushUrl({ url: 'pages/ProductCardPage' });
        })
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
    .backgroundColor('#F5F7FA');
  }
}

运行效果

四、基础示例演示:StackBasicPage.ets

javascript 复制代码
@Entry
@Component
struct StackBasicPage {
  build() {
    Column({ space: 40 }) {
      // 页面标题
      Text("Stack核心基础能力演示")
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .width('100%')
        .textAlign(TextAlign.Center);
      
      Text("示例1:叠层布局对齐方式: Alignment.TopStart")
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .width('100%')
        .textAlign(TextAlign.Start);

      StackComponent()

      Text("示例2:Z序控制演示(zIndex调整层级)")
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .width('100%')
        .textAlign(TextAlign.Start);

      StackComponent({z:1})

    }
    .width('100%')
    .height('100%')
    .padding(20)
    .justifyContent(FlexAlign.Center)
    .backgroundColor("#F8F9FA");
  }
}

// 自定义可复用Stack组件
@Component
struct StackComponent{
  @Prop z: number = 0
  build(){
    Stack({ alignContent: Alignment.TopStart }) {
      Text("元素1")
        .backgroundColor("#80BFFF")
        .borderRadius(4)
        .width('80%')
        .height('80%')
        .zIndex(this.z)
        .textAlign(TextAlign.End)
        .fontSize(14);

      Text("元素2")
        .backgroundColor("#E5F2FF")
        .borderRadius(4)
        .textAlign(TextAlign.Center)
        .width('50%')
        .height('50%')
        .fontSize(14);
    }
    .width(240)
    .height(180)
    .backgroundColor(Color.Pink)
    .borderRadius(8)
  }
}

运行效果

五、实战案例一:App图标+消息角标(AppIconBadgePage.ets)

javascript 复制代码
@Entry
@Component
struct AppIconBadgePage {
  build() {
    Column({ space: 30 }) {
      Text("App图标 + 消息角标")
        .fontSize(22)
        .fontWeight(FontWeight.Bold);

      // 核心:Stack层叠 + Alignment对齐 + offset微调 实现消息角标效果
      Stack({ alignContent: Alignment.TopEnd }) {
        // 底层:App图标(基础元素)
        Image($r('app.media.startIcon'))
          .width(80)
          .height(80)
          .onAreaChange((oldValue: Area, newValue: Area) => {
            const  x = newValue.position.x as number; // 直接获取vp单位坐标
            const y = newValue.position.y as number;
            console.log(`Imagex轴:${x},y轴:${y}`)
          })
          .borderRadius(16);

        // 上层:消息角标(offset负偏移实现精准贴边)
        Text("5")
          .width(24)
          .height(24)
          .backgroundColor("#FF3B30")
          .fontColor(Color.White)
          .fontSize(12)
          .textAlign(TextAlign.Center)
          .borderRadius(12)
          .onAreaChange((oldValue: Area, newValue: Area) => {
            const  x = newValue.position.x as number; // 直接获取vp单位坐标
            const y = newValue.position.y as number;
            console.log(`Textx轴:${x},y轴:${y}`)
          })
          .offset({ x: 6, y: -6 });
      }
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .justifyContent(FlexAlign.Center)
    .backgroundColor("#F8F9FA");
  }
}

5.1 运行效果

5.2 offset的总结验证

日志显示 Imagex轴:0,y轴:0,Textx轴:56,y轴:0

Image宽高80vp,即Stack宽高80vp ,消息角标右上角对齐;Text组件宽高24vp;

Text相对于Stack起始坐标 80 - 24 = 56 符合日志输出56。

设置Text.offset({ x: 6, y: -6 }); 和日志对比 y坐标0可以看出修改的只是绘制显示位置,实际坐标未变化。

六、实战案例二:商品销售卡片(ProductCardPage.ets)

javascript 复制代码
@Entry
@Component
struct ProductCardPage {
  build() {
    Column({ space: 30 }) {
      Text("商品销售卡片")
        .fontSize(22)
        .fontWeight(FontWeight.Bold);

      // 核心:Stack+position 实现复杂多层级商品卡片底部居中对齐
      Stack({ alignContent: Alignment.Bottom }) {
        // 底层:商品背景图(铺满容器,保持比例无变形)
        Image($r('app.media.huaweiTaplet'))
          .width('100%')
          .height('100%')
          .objectFit(ImageFit.Cover);

        // 中层:价格半透明遮罩(底部居中对齐,原生支持无需额外调整)
        Text("¥3299 限时特价")
          .width("100%")
          .padding({ top: 8, bottom: 8})
          .backgroundColor("rgba(0, 0, 0, 0.5)")
          .fontColor(Color.White)
          .fontSize(14)
          .textAlign(TextAlign.Center);

        // 上层:热销标签(position绝对定位,悬浮右上角,脱离布局流)
        Text("热销")
          .padding({ top: 4, right: 10, bottom: 4, left: 10 })
          .backgroundColor("#FF3B30")
          .fontColor(Color.White)
          .fontSize(12)
          .borderRadius(4)
          .position({ top: 10, right: 10 });
      }
      .width("100%")
      .aspectRatio(4/3) // 4:3宽高比,明确容器尺寸(position定位核心前提)
      .borderRadius(12)
    }
    .width("100%")
    .height("100%")
    .padding(20)
    .justifyContent(FlexAlign.Center)
    .backgroundColor("#F8F9FA");
  }
}

6.1 核心知识点落地

  • 明确尺寸基准 :Stack通过width("100%")+aspectRatio(4/3)设置明确的宽高比例尺寸,为position绝对定位的热销标签提供精准物理边界,保证定位无偏移(position使用的核心前提);
  • 多层级自然覆盖 :按"背景图→价格遮罩→热销标签"的编写顺序,实现符合视觉逻辑的层级覆盖,满足电商卡片"背景-内容-标识"的设计要求,无需额外设置zIndex
  • 底部对齐原生支持 :通过Stack的alignContent: Alignment.Bottom直接实现价格遮罩底部居中对齐,无需额外的alignSelfoffset微调,简化代码;
  • position悬浮定位 :热销标签通过position绝对定位脱离布局流,以Stack为基准通过top:10、right:10实现精准悬浮,不影响其他组件的布局和层叠,交互区域跟随悬浮位置;
  • 图片适配优化 :为图片添加width('100%')+height('100%'),配合objectFit(ImageFit.Cover)保证商品图片在固定比例尺寸内不变形、不拉伸,自动裁剪中心核心区域,适配不同比例的商品图片;
  • 半透明遮罩规范 :价格遮罩使用rgba(0,0,0,0.5)半透明背景,既保证文字可读性,又不遮挡底层商品图片的细节,符合电商UI设计规范。

6.2 运行效果

七、内容总结

  1. 核心定位:Z轴层叠布局容器,核心能力是多组件同一空间层叠显示,是移动端角标、卡片、悬浮元素的必备布局;
  2. 唯一构造参数alignContent,控制子组件全局对齐,取值为Alignment枚举9个选项,默认Center
  3. 层叠两大规则 :默认"后写覆盖先写",手动通过zIndex控制(数值越大层级越高),规则仅在当前Stack内生效;
  4. 定位两大能力
    • offset:相对定位,纯视觉微调,不影响布局流,保留原占位和原始交互区域,偏移基准为组件自身默认位置,适用于贴边、校准(如消息角标);
    • position:绝对定位,脱离布局流无占位,交互区域跟随视觉位置,定位基准为直接父容器Stack,使用前提是父容器设置固定宽高/明确尺寸,适用于悬浮定位(如热销标签);
  5. 实战组合技巧
    • Stack+Alignment+offset:适合"层叠+细节贴边"场景(如消息角标),不改变布局流,适配性强;
    • Stack+明确尺寸+position:适合"层叠+悬浮定位"场景(如商品卡片热销标签),精准锚定父容器位置;
    • 三者组合:可实现复杂多层级的移动端常见布局(如商品卡片、弹窗、悬浮按钮等);
  6. 核心避坑点:position绝对定位必须保证父容器有明确尺寸,否则会定位失效;offset偏移后交互区域仍在原始位置,避免因视觉偏移导致交互异常。

八、代码仓库

九、下节预告

本节我们系统学习了 Stack 叠层布局 的核心用法,掌握了 Z 轴层叠、Alignment 对齐、zIndex 层级控制、offset 相对偏移与 position 绝对定位五大关键能力,并通过消息角标、商品卡片两大实战案例,完成了从基础语法到移动端高频场景的落地转化。

下一节,我们将进入鸿蒙 ArkTS 中更适合复杂界面、扁平化开发的高级布局 ------RelativeContainer 相对布局。它可以让组件直接以父容器或兄弟组件为锚点进行精准定位,告别传统 Column/Row 多层嵌套,实现更灵活、更简洁、更易维护的页面结构。