摘要

Stack 是 ArkUI 实现图层堆叠、层级覆盖布局的核心容器,依靠多层子组件上下叠加的特性,实现弹窗遮罩、头像角标、悬浮按钮、图片水印、图文叠加、浮层提示等页面效果,是复杂视觉交互必备组件。API Version23 重构 Stack 层级渲染管线、子组件坐标定位、遮罩透明度渲染、点击穿透判定逻辑,修复低版本层级错乱、定位偏移、下层组件无法点击、遮罩闪烁、角标裁剪失效等问题。低版本工程升级至 API23 后普遍出现:浮层被底层内容遮挡、position 坐标失效、遮罩拦截全部点击、角标超出边界被截断、多层 Stack 嵌套渲染卡顿等兼容故障。本文基于 DevEco Studio,适配 OpenHarmony API23 及以上标准,系统拆解 Stack 堆叠规则、对齐属性、position 绝对定位、zIndex 层级控制、遮罩浮层实现,结合头像角标、图文水印、页面悬浮按钮、全局弹窗遮罩、商品标签五大业务场景提供可直接运行代码,输出层级性能优化、多端定位适配规范,汇总版本升级兼容故障修复方案,为鸿蒙多层视觉布局开发提供标准化实操模板。

关键词

OpenHarmony;ArkUI;API Version23;Stack 堆叠容器;层级布局;position 定位;zIndex;浮层遮罩;角标标签

一、引言

1.1 Stack 组件开发背景

Row 横向、Column 纵向仅能实现单一层级线性排布,当页面需要多层元素叠加覆盖时,必须使用 Stack 容器。常见业务场景:头像右上角未读红点、商品图折扣标签、页面右下角悬浮新增按钮、弹窗半透明黑色遮罩、图片文字水印、视频播放按钮浮层等。

OpenHarmony API Version23 针对 Stack 容器做底层渲染升级,核心变更点:

  1. 重构子组件渲染顺序,代码后定义组件默认层级更高,覆盖前置组件;
  2. 新增 zIndex 显式层级权重,自由调整叠加顺序,修复旧版层级错乱;
  3. 标准化 position 绝对定位坐标计算逻辑,统一手机 / 平板坐标偏移规则;
  4. 优化半透明遮罩渲染,消除多层叠加闪烁、透明度失真;
  5. 完善点击穿透判定规则,区分遮罩拦截、底层组件可点击两种模式;
  6. 限制三层以上连续 Stack 嵌套,编译输出性能告警,减少页面重绘开销。

大量 API9~11 旧项目升级后,浮层元素被底层内容遮挡、角标跑出容器被裁剪、遮罩误拦截全部点击事件,因此掌握 API23 标准 Stack 层级开发规范是复杂 UI 实现核心技能。

1.2 开发环境与测试场景

开发工具:DevEco Studio 5.0 及以上 适配系统:OpenHarmony API Version23、HarmonyOS NEXT 开发语言:ArkTS 测试场景:头像红点角标、商品折扣标签、图片水印、悬浮操作按钮、弹窗全局遮罩、多层堆叠复合卡片

二、API23+ Stack 核心属性与版本变更说明

2.1 Stack 基础对齐属性

Stack 通过 alignContent 控制所有子组件默认对齐基准,默认值 Align.Center 居中堆叠:

  • Align.Center:所有子组件居中对齐(通用)
  • Align.TopStart:左上对齐
  • Align.TopEnd:右上对齐
  • Align.BottomStart:左下对齐
  • Align.BottomEnd:右下对齐

2.2 核心定位能力(API23 优化坐标计算)

  1. position ({x, y}):子组件绝对偏移坐标,脱离文档流,基于 Stack 容器左上角计算偏移;
  2. zIndex:层级权重,数值越大渲染层级越高,数值高的组件覆盖数值低组件,API23 新增独立层级排序逻辑,不受代码书写顺序限制。

2.3 点击控制属性

.clip (true):开启容器裁剪,超出 Stack 边界的子组件自动隐藏(角标、标签常用) .hitTestBehavior ():控制点击穿透

  • HitTestMode.Default:默认,当前组件拦截点击,下层无法响应
  • HitTestMode.Transparent:自身不拦截,点击穿透至下层组件

2.4 API23 废弃与约束规则

  1. 废弃旧版模糊层级排序逻辑,必须依靠 zIndex 显式控制叠加顺序;
  2. 禁止多层 Stack 循环嵌套,建议嵌套不超过两层;
  3. position 坐标仅支持 vp 单位,px 像素坐标计算偏移失真;
  4. 半透明遮罩禁止多层叠加,多层半透明会出现渲染黑斑;
  5. clip 裁剪开启后,超出边界元素彻底隐藏,无渐变溢出效果。

三、API23 标准基础示例代码

3.1 居中基础堆叠(图片 + 播放按钮)

ets

Stack({ alignContent: Align.Center }) {
  Image($r('app.media.goods'))
    .width(220)
    .height(140)
    .borderRadius(10)
    .objectFit(ImageFit.Cover)
  Text("▶")
    .fontSize(40)
    .fontColor(Color.White)
}

3.2 右上角角标定位(position+clip 裁剪)

ets

Stack({ alignContent: Align.TopStart }) {
  Image($r('app.media.avatar'))
    .width(80)
    .height(80)
    .borderRadius(40)
    .clipShape(Circle())
  Text("3")
    .width(24)
    .height(24)
    .borderRadius(12)
    .fontSize(12)
    .fontColor(Color.White)
    .textAlign(TextAlign.Center)
    .backgroundColor("#f56c6c")
    .position({ x: 60, y: -5 })
    .zIndex(10)
}
.clip(false)
.width(80)
.height(80)

3.3 半透明遮罩基础写法

ets

Stack() {
  // 底层页面内容
  Column() { Text("页面底层内容") }
  // 遮罩浮层
  Rect()
    .width("100%")
    .height("100%")
    .fill("#00000066")
    .zIndex(99)
}
.width("100%")
.height("100%")

四、五大业务完整实战案例(完全兼容 API23)

4.1 实战一:头像未读消息红点角标

业务需求:圆形头像右上角显示数字角标,角标部分超出头像边界正常显示,不会被裁剪遮挡。

ets

@Entry
@Component
struct StackAvatarBadgeDemo {
  build() {
    Column({ space: 30 }) {
      Text("Stack头像角标实战 API23")
        .fontSize(22)
        .fontWeight(FontWeight.Bold)
      Stack({ alignContent: Align.TopStart }) {
        Image("https://picsum.photos/150/150")
          .width(90)
          .height(90)
          .clipShape(Circle())
          .objectFit(ImageFit.Cover)
        // 角标浮层,层级更高
        Text("5")
          .width(26)
          .height(26)
          .textAlign(TextAlign.Center)
          .fontSize(13)
          .fontColor(Color.White)
          .backgroundColor("#f56c6c")
          .borderRadius(13)
          .position({ x: 65, y: -3 })
          .zIndex(20)
      }
      .width(90)
      .height(90)
      .clip(false)
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}

4.2 实战二:商品卡片左上角折扣标签

业务需求:商品图片左上角叠加红色折扣标签,固定位置不随图片尺寸偏移,超出图片部分自动裁剪。

ets

@Entry
@Component
struct StackDiscountTagDemo {
  build() {
    Column() {
      Text("商品折扣标签堆叠布局")
        .fontSize(20)
        .margin({ bottom: 15 })
      Stack({ alignContent: Align.TopStart }) {
        Image("https://picsum.photos/400/260")
          .width("100%")
          .height(170)
          .borderRadius(12)
          .objectFit(ImageFit.Cover)
        Text("限时9折")
          .padding({ left: 10, right: 10 })
          .height(28)
          .fontSize(14)
          .fontColor(Color.White)
          .backgroundColor("#f56c6c")
          .position({ x: 0, y: 0 })
          .zIndex(10)
      }
      .width("90%")
      .clip(true)
      .borderRadius(12)
    }
    .padding(20)
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}

4.3 实战三:页面右下角悬浮功能按钮

业务需求:页面列表底层内容,右下角永久悬浮圆形新增按钮,按钮层级高于列表,不会被条目遮挡。

ets

@Entry
@Component
struct StackFloatBtnDemo {
  private dataList: string[] = ["笔记条目1","笔记条目2","笔记条目3","笔记条目4"]
  build() {
    Stack() {
      List({ space: 10 }) {
        ForEach(this.dataList, (item:string)=>{
          ListItem() {
            Text(item).width("100%").padding(20).backgroundColor(Color.White).borderRadius(8)
          }
        })
      }
      .width("100%")
      .height("100%")
      .padding(15)

      // 悬浮按钮,最高层级
      Button("+")
        .width(56)
        .height(56)
        .borderRadius(28)
        .backgroundColor("#007DFF")
        .fontColor(Color.White)
        .fontSize(24)
        .position({ x: "82%", y: "84%" })
        .zIndex(99)
    }
    .width("100%")
    .height("100%")
  }
}

4.4 实战四:弹窗全局半透明遮罩浮层

业务需求:底层页面完全保留,上层黑色半透明遮罩 + 居中弹窗,遮罩点击可关闭弹窗,弹窗层级高于遮罩。

ets

@Entry
@Component
struct StackDialogMaskDemo {
  @State showDialog: boolean = true
  build() {
    Stack() {
      // 底层主页面
      Column() {
        Text("底层首页内容")
          .fontSize(24)
      }
      .width("100%")
      .height("100%")
      .justifyContent(FlexAlign.Center)

      if(this.showDialog){
        // 半透明遮罩
        Rect()
          .width("100%")
          .height("100%")
          .fill("#00000060")
          .zIndex(10)
          .onClick(()=>{this.showDialog = false})
        // 弹窗主体,层级高于遮罩
        Column({ space: 20 }) {
          Text("提示弹窗").fontSize(22).fontWeight(FontWeight.Bold)
          Text("此为Stack实现全局遮罩弹窗")
          Button("关闭弹窗").width(180).height(44).onClick(()=>{this.showDialog = false})
        }
        .width(320)
        .padding(25)
        .backgroundColor(Color.White)
        .borderRadius(16)
        .position({ x: "50%", y: "50%" })
        .translate({ x: -160, y: -120 })
        .zIndex(20)
      }
    }
    .width("100%")
    .height("100%")
  }
}

4.5 实战五:图片水印文字叠加

业务需求:图片底部叠加半透明文字水印,不遮挡主体画面,层级固定低于播放按钮。

ets

@Entry
@Component
struct StackWaterMarkDemo {
  build() {
    Column({ space: 20 }) {
      Text("图片水印堆叠布局")
        .fontSize(22)
        .fontWeight(FontWeight.Bold)
      Stack({ alignContent: Align.Center }) {
        Image("https://picsum.photos/500/300")
          .width("90%")
          .height(200)
          .borderRadius(12)
          .objectFit(ImageFit.Cover)
        // 底部水印
        Text("OpenHarmony API23 水印示例")
          .fontSize(14)
          .fontColor("#ffffff88")
          .position({ x: 0, y: 160 })
          .width("100%")
          .textAlign(TextAlign.Center)
          .zIndex(5)
        // 播放按钮,层级高于水印
        Text("▶")
          .fontSize(42)
          .fontColor(Color.White)
          .zIndex(10)
      }
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}

五、API23+ Stack 层级适配与性能优化规范

5.1 多端定位适配规范

  1. position 坐标统一使用 vp 单位,禁止 px,平板大屏坐标偏移失真;
  2. 百分比坐标搭配 translate 偏移实现屏幕居中弹窗,适配不同宽度设备;
  3. 角标、标签固定数值偏移,优先基于容器尺寸计算,不使用超大偏移值;
  4. 悬浮按钮使用百分比 x/y 定位,自动适配手机、平板屏幕宽高。

5.2 层级渲染性能优化准则

  1. Stack 嵌套层级控制在两层以内,禁止多层循环嵌套;
  2. 页面常驻浮层(悬浮按钮)仅渲染一次,不使用 if 频繁销毁重建;
  3. 半透明遮罩仅单层渲染,杜绝多层 #000 叠加,减少 GPU 渲染压力;
  4. List 内部 Stack 简化子组件数量,每条 ListItem 内 Stack 子组件不超过 3 个;
  5. 不可见浮层使用 if 条件销毁,而非单纯透明度隐藏,减少后台渲染。

5.3 点击交互规范

  1. 遮罩弹窗默认使用 HitTestMode.Default 拦截底层点击;
  2. 水印、背景装饰浮层设置 HitTestMode.Transparent,点击穿透;
  3. 角标、标签仅视觉展示,不绑定点击事件,减少触摸判定计算。

六、API23 升级高频兼容问题与解决方案

问题 1:浮层组件被底层内容遮挡,无法显示在顶层 解决:给浮层设置更大 zIndex 数值,API23 层级优先级完全由 zIndex 控制,不受代码顺序影响。

问题 2:position 定位在平板设备偏移严重,位置错乱 解决:全部坐标替换为 vp,禁止 px;大屏适配优先使用百分比定位。

问题 3:角标超出容器直接被隐藏,无法露出一小部分 解决:Stack 添加.clip(false)关闭边界裁剪,clip (true) 会截断超出元素。

问题 4:遮罩弹窗弹出后,列表按钮、输入框无法点击 解决:遮罩组件不设置 HitTestMode.Transparent,如需穿透仅水印、装饰层使用该属性。

问题 5:多层 Stack 嵌套页面滑动卡顿、帧率下降 解决:重构布局,拆分多层 Stack,改用单层 Stack 搭配 position 实现多层堆叠。

问题 6:半透明遮罩出现黑斑、透明度叠加异常 解决:仅保留一层黑色半透明遮罩,移除多余重叠 Rect 半透明组件。

七、总结

Stack 是 ArkUI 唯一支持多层元素叠加的层级容器,依托对齐基准、position 绝对定位、zIndex 层级权重三大核心能力,实现角标、标签、悬浮按钮、遮罩弹窗、水印等所有复合视觉效果。API Version23 全面重构 Stack 底层渲染与坐标计算逻辑,层级控制、定位精度、点击判定相比低版本大幅优化,同时增加嵌套层级、单位使用强制约束。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐