引言:為什麼需要層疊佈局?

在構建現代應用界面時,我們經常需要實現元素重疊的效果,比如懸浮按鈕圖片水印彈窗遮罩等。與線性佈局的順序排列不同,層疊佈局(Stack)允許子組件在Z軸方向上疊加顯示,為界面設計提供了更多可能性。

Stack佈局是HarmonyOS ArkUI框架中用於實現元素重疊的核心組件,它讓後添加的子組件自動覆蓋前面的組件,類似於一疊卡片的堆疊效果。這種佈局方式特別適合需要精確定位和層級控制的場景。

一、Stack佈局的核心原理與基礎用法

1.1 層疊佈局的基本概念

Stack佈局的核心思想是垂直堆疊。容器內的子組件按照代碼中的聲明順序依次入棧,後聲明的組件會覆蓋在先聲明的組件上方。

@Entry
@Component
struct BasicStackExample {
  build() {
    Stack() {
      Text('底層文本').fontSize(16)
      Text('中層文本').fontSize(16)
      Text('頂層文本').fontSize(16) // 顯示在最上層
    }
  }
}

這種默認的層疊行為使得Stack非常適合實現如背景圖加內容、浮動操作按鈕等常見UI模式。

1.2 Stack容器的基礎屬性

Stack作為容器組件,提供了一些關鍵屬性來控制子元素的整體行為:

  • alignContent:控制子組件在容器內的對齊方式
  • 寬度和高度:顯式定義Stack容器的尺寸
  • 邊距設置:通過margin屬性控制與其他組件的間距
Stack({ alignContent: Alignment.Center }) {
  // 子組件內容
}
.width('100%')
.height(200)
.margin({ top: 20 })

二、對齊方式與定位控制

2.1 九種對齊方式詳解

Stack通過alignContent屬性支持豐富的對齊選項,包括TopStartTopTopEndCenterStartCenterCenterEndBottomStartBottomBottomEnd等九種對齊模式。

Stack({ alignContent: Alignment.TopStart }) {
  Text('左上對齊').width(200).height(180)
  Text('小文本').width(130).height(100)
}

不同的對齊方式為子組件提供了靈活的定位基礎,特別是在需要將特定元素固定在容器某個角落時特別有用。

2.2 絕對定位實戰

Stack佈局最強大的功能之一是支持子組件的絕對定位。通過position屬性,可以精確控制子組件相對於Stack容器左上角的位置。

Stack() {
  Image($r('app.media.product_bg')).width('100%')
  
  Text('限時折扣')
    .backgroundColor('#FF3B30')
    .color('#FFFFFF')
    .fontSize(12)
    .padding({ left: 6, right: 6, top: 2, bottom: 2 })
    .position({ x: 12, y: 12 }) // 左上角定位
    .zIndex(1)
    
  Text('新品')
    .backgroundColor('#007AFF')
    .color('#FFFFFF')
    .fontSize(12)
    .padding(6)
    .position({ x: '90%', y: 12 }) // 右上角定位
    .zIndex(1)
}
.width('100%')
.height(200)

這種定位方式非常適合實現商品標籤、懸浮按鈕等需要精確定位的UI元素。

2.3 相對偏移技術

除了絕對定位,Stack還支持通過offset屬性實現相對偏移,讓組件相對於其原本佈局位置進行移動。

Stack() {
  Text('偏移文本')
    .offset({ x: 10, y: -5 }) // 向右偏移10vp,向上偏移5vp
}

offset屬性不影響父容器的佈局計算,只在繪製時調整位置,這在微調組件位置時非常實用。

三、層級控制與Z序管理

3.1 zIndex屬性深度解析

當默認的聲明順序無法滿足層級需求時,可以通過zIndex屬性手動控制組件的堆疊順序。zIndex值越大,組件顯示層級越高。

Stack({ alignContent: Alignment.BottomStart }) {
  Column() {
    Text('Stack子元素1').fontSize(20)
  }
  .width(100).height(100).backgroundColor(0xffd306).zIndex(2)
  
  Column() {
    Text('Stack子元素2').fontSize(20)
  }
  .width(150).height(150).backgroundColor(Color.Pink).zIndex(1)
  
  Column() {
    Text('Stack子元素3').fontSize(20)
  }
  .width(200).height(200).backgroundColor(Color.Grey) // 默認zIndex=0
}

在這個示例中,儘管子元素3尺寸最大且最後聲明,但由於zIndex值最小,它會被前兩個元素覆蓋。

3.2 動態層級管理

在實際應用中,經常需要根據用户交互動態改變組件層級。結合@State裝飾器,可以實現動態的zIndex控制。

@Entry
@Component
struct DynamicZIndexExample {
  @State activeIndex: number = 0
  
  build() {
    Stack() {
      TabContent(index: 0).zIndex(this.activeIndex === 0 ? 2 : 1)
      TabContent(index: 1).zIndex(this.activeIndex === 1 ? 2 : 1)
      TabContent(index: 2).zIndex(this.activeIndex === 2 ? 2 : 1)
    }
  }
}

這種技術常見於標籤頁切換、輪播圖等交互場景。

四、實戰應用場景

4.1 彈窗與遮罩實現

Stack佈局最典型的應用就是實現彈窗效果。通過結合條件渲染和絕對定位,可以創建專業的彈窗組件。

@Entry
@Component
struct ModalExample {
  @State isVisible: boolean = false
  
  build() {
    Stack() {
      // 頁面主內容
      Column() {
        Button('打開彈窗')
          .onClick(() => { this.isVisible = true })
      }
      
      // 條件渲染彈窗
      if (this.isVisible) {
        // 遮罩層
        Rectangle()
          .fill(Color.Black.alpha(0.5))
          .width('100%').height('100%')
          .onClick(() => { this.isVisible = false })
        
        // 彈窗內容
        Column() {
          Text('彈窗標題').fontSize(18).fontWeight(FontWeight.Bold)
          Text('這裏是詳細內容...')
          Button('關閉').onClick(() => { this.isVisible = false })
        }
        .width(300).height(200)
        .backgroundColor('#fff')
        .padding(20)
        .position({ x: '50%', y: '50%' })
        .offset({ x: -150, y: -100 }) // 居中偏移
      }
    }
  }
}

4.2 卡片層疊效果

電商類應用經常需要使用卡片層疊效果來展示商品信息或促銷活動。

Stack({ alignContent: Alignment.Center }) {
  // 底層卡片
  Card().width('90%').height(200).backgroundColor('#fff')
        .shadow({ color: '#000', offset: { x: 0, y: 4 }, blur: 8 })
  
  // 中間卡片
  Card().width('80%').height(180).backgroundColor('#fff')
        .offset({ top: -20 }).opacity(0.9)
  
  // 頂層按鈕
  Button('立即購買').width(120).height(40).backgroundColor('#007AFF')
        .position({ bottom: 30, right: 20 })
}

4.3 圖片水印與標籤

Stack佈局非常適合為圖片添加水印、標籤等裝飾性元素。

Stack() {
  Image($r('app.media.goods_img'))
    .width('100%').height(200).objectFit(ImageFit.Cover)
  
  Text('熱門推薦')
    .backgroundColor('#FF3B30').color('#FFFFFF')
    .fontSize(12).padding(6)
    .position({ x: 12, y: 12 })
    
  Text('新品')
    .backgroundColor('#007AFF').color('#FFFFFF')
    .fontSize(12).padding(6)
    .position({ x: '85%', y: 12 })
}

五、性能優化與最佳實踐

5.1 避免過度嵌套

雖然Stack佈局功能強大,但過度嵌套會影響渲染性能。建議將相鄰的Stack容器合併,減少嵌套層級。

// 不推薦:多層嵌套
Stack() {
  Stack() {
    Image()
    Text()
  }
  Button()
}

// 推薦:扁平化結構
Stack() {
  Image()
  Text()
  Button()
}

5.2 合理使用條件渲染

對於需要頻繁顯示/隱藏的組件,使用條件渲染(if語句)而不是設置visibility屬性,可以減少不必要的渲染開銷。

// 推薦:使用條件渲染
if (this.showDialog) {
  DialogComponent()
}

// 不推薦:使用visibility屬性
DialogComponent().visibility(this.showDialog ? Visibility.Visible : Visibility.None)

5.3 尺寸與邊界處理

使用Stack佈局時需要注意容器尺寸和溢出處理:

Stack() {
  // 子組件內容
}
.width('100%') // 顯式設置寬度
.height('100%') // 顯式設置高度
.clip(true) // 裁剪超出部分

結語

Stack層疊佈局是HarmonyOS ArkUI框架中極具表現力的佈局工具,通過掌握其層疊原理、對齊方式和定位技術,開發者可以創建出豐富多樣的界面效果。關鍵在於根據具體場景選擇合適的定位策略,並遵循性能優化最佳實踐。

在實際開發中,建議結合DevEco Studio的實時預覽功能,實時調試Stack佈局的層疊效果和定位位置,確保在不同設備上都能獲得一致的視覺體驗。

思考題:在你的項目實踐中,哪些場景下Stack佈局解決了傳統線性佈局無法解決的問題?你是如何處理複雜層疊場景下的性能優化挑戰的?