🌟 引言:二維佈局的強大力量

在移動應用界面設計中,網格佈局是組織內容的經典模式。無論是照片牆、商品網格還是儀表盤,網格都能提供清晰的視覺結構和高效的空間利用。ArkUI的Grid組件專為這類場景設計,它不同於線性佈局的單維度排列,而是真正的二維佈局系統,可以同時在行和列方向上精確控制子組件的位置和大小。

一、Grid組件基礎:構建規則網格系統

Grid是ArkUI中用於創建網格佈局的核心容器組件,它通過定義行和列的模板來構建規則的網格結構。

1. 基本結構與關鍵屬性

@Entry
@Component
struct BasicGridExample {
  build() {
    Column() {
      Grid() {
        ForEach(Array.from({ length: 9 }), (_, index) => {
          GridItem() {
            Text(`Item ${index + 1}`)
              .fontSize(16)
              .textAlign(TextAlign.Center)
          }
          .backgroundColor(0xF5F5F5)
          .borderRadius(8)
        })
      }
      .columnsTemplate('1fr 1fr 1fr') // 3等分列
      .rowsTemplate('1fr 1fr 1fr')    // 3等分行
      .columnsGap(12)                 // 列間距
      .rowsGap(12)                   // 行間距
      .height(300)
    }
    .padding(20)
  }
}

關鍵屬性解析:

  • columnsTemplate/rowsTemplate:定義網格的列和行結構,支持多種單位
  • columnsGap/rowsGap:設置網格項之間的水平和垂直間距
  • scrollBar:控制滾動條顯示策略,支持BarState.Off/BarState.Auto/BarState.On

2. 靈活的模板定義方式

Grid支持多種模板定義方式,適應不同佈局需求:

// 等分列:3列等寬
.columnsTemplate('1fr 1fr 1fr')

// 混合單位:固定寬度與彈性寬度結合  
.columnsTemplate('100px 1fr 2fr')

// 重複模式:使用repeat函數簡化定義
.columnsTemplate('repeat(4, 1fr)')

// 響應式斷點:根據屏幕尺寸動態調整
.columnsTemplate(this.getColumnsTemplate())

// 根據屏幕寬度動態返回模板
getColumnsTemplate(): string {
  const screenWidth = display.getDefaultDisplaySync().width
  if (screenWidth < 600) {
    return '1fr' // 小屏幕單列
  } else if (screenWidth < 840) {
    return '1fr 1fr' // 中屏幕雙列
  } else {
    return 'repeat(3, 1fr)' // 大屏幕三列
  }
}
二、GridItem的高級用法:不規則網格與跨列布局

在實際應用中,經常需要創建不規則的網格佈局,如某些項跨多列或多行。ArkUI提供了多種方式實現這種效果。

1. 傳統的跨列布局方式

@Component
struct IrregularGridExample {
  @State items: number[] = Array.from({ length: 10 }, (_, i) => i + 1)
  
  build() {
    Grid() {
      ForEach(this.items, (item: number, index: number) => {
        GridItem() {
          Text(`項目 ${item}`)
            .fontSize(14)
            .textAlign(TextAlign.Center)
        }
        .backgroundColor(this.getBackgroundColor(index))
        // 實現不規則佈局:每行的第一個項目跨兩列
        .columnStart(this.getColumnStart(index))
        .columnEnd(this.getColumnEnd(index))
      })
    }
    .columnsTemplate('1fr 1fr 1fr') // 3列基礎網格
    .columnsGap(10)
    .rowsGap(10)
  }
  
  // 計算列起始位置
  getColumnStart(index: number): number {
    return (index % 3 === 0) ? 0 : (index % 3)
  }
  
  // 計算列結束位置
  getColumnEnd(index: number): number {
    return (index % 3 === 0) ? 2 : (index % 3)
  }
  
  // 根據位置設置不同背景色
  getBackgroundColor(index: number): number {
    const colors = [0xEF9A9A, 0x90CAF9, 0xA5D6A7, 0xFFFF72]
    return colors[index % colors.length]
  }
}

2. 性能優化:GridLayoutOptions方案

當處理大量數據時,傳統的columnStart/columnEnd方式可能導致性能問題。ArkUI API 12引入了GridLayoutOptions來優化這種情況。

class GridLayoutConfig {
  regularSize: [number, number] = [1, 1] // 默認每個項佔1行1列
  irregularIndexes: number[] = []         // 不規則項的索引數組
}

@Entry
@Component
struct OptimizedGridExample {
  private dataSource: MyDataSource = new MyDataSource()
  private irregularIndexes: number[] = []
  private layoutOptions: GridLayoutOptions = {
    regularSize: [1, 1],
    irregularIndexes: this.irregularIndexes
  }
  
  aboutToAppear() {
    // 預計算不規則項:每4箇中的第1個跨2列
    for (let i = 0; i < 1000; i++) {
      if (i % 4 === 0) {
        this.irregularIndexes.push(i)
      }
    }
  }
  
  build() {
    Grid(new Scroller(), this.layoutOptions) {
      LazyForEach(this.dataSource, (item: string, index: number) => {
        GridItem() {
          OptimizedGridItem({ content: item })
        }
      }, (item: string) => item)
    }
    .columnsTemplate('1fr 1fr 1fr')
    .cachedCount(1) // 預加載優化
  }
}

@Component
struct OptimizedGridItem {
  @Prop content: string
  
  build() {
    Column() {
      Text(this.content)
        .fontSize(16)
      Text('高性能網格項')
        .fontSize(12)
        .fontColor(Color.Gray)
    }
    .padding(10)
    .backgroundColor(0xF5F5F5)
    .borderRadius(8)
    .width('100%')
    .height(80)
  }
}
三、性能優化深度解析

1. 懶加載與緩存策略

對於大數據集的網格佈局,必須採用懶加載機制避免內存溢出。

class ProductDataSource implements IDataSource {
  private productList: Product[] = []
  
  totalCount(): number {
    return this.productList.length
  }
  
  getData(index: number): Product {
    return this.productList[index]
  }
  
  registerDataChangeListener(listener: DataChangeListener): void {}
  unregisterDataChangeListener(listener: DataChangeListener): void {}
}

@Entry
@Component
struct ProductGrid {
  private data: ProductDataSource = new ProductDataSource()
  private scroller: Scroller = new Scroller()
  
  build() {
    Grid(this.scroller) {
      LazyForEach(this.data, (product: Product) => {
        GridItem() {
          ProductCard({ product: product })
        }
      }, (product: Product) => product.id.toString())
    }
    .columnsTemplate('1fr 1fr')
    .cachedCount(2) // 前後各緩存2屏內容
    .onScrollIndex((first: number) => {
      // 滾動索引變化監聽,可用於分頁加載
      console.info(`當前首項索引: ${first}`)
    })
  }
}

2. 組件複用機制

通過@Reusable裝飾器實現組件複用,進一步提升滾動性能。

@Reusable
@Component
struct ProductCard {
  @State product: Product | undefined
  
  aboutToReuse(params: Record<string, Object>): void {
    this.product = params.product as Product
  }
  
  build() {
    Column() {
      Image(this.product?.image)
        .objectFit(ImageFit.Contain)
        .height(60)
      Text(this.product?.name)
        .fontSize(14)
        .margin({ top: 5 })
      Text(`¥${this.product?.price}`)
        .fontSize(12)
        .fontColor(Color.Red)
    }
    .padding(8)
    .backgroundColor(Color.White)
    .borderRadius(12)
    .shadow(ShadowStyle.OUTER_DEFAULT)
  }
}
四、複雜網格佈局實戰

1. 瀑布流佈局實現

雖然Grid主要針對規則網格,但通過巧妙配置可以實現類瀑布流效果。

@Component
struct WaterfallGrid {
  @State items: WaterfallItem[] = []
  
  build() {
    Grid() {
      ForEach(this.items, (item: WaterfallItem) => {
        GridItem() {
          WaterfallCard({ item: item })
        }
        .rowStart(item.rowStart)
        .rowEnd(item.rowEnd)
        .columnStart(item.columnStart) 
        .columnEnd(item.columnEnd)
      })
    }
    .columnsTemplate('1fr 1fr') // 雙列瀑布流
    .rowsTemplate('repeat(auto-fill, 100px)') // 自動行高
  }
}

2. 交互增強:拖拽排序與動畫

@Component
struct DraggableGrid {
  @State items: GridItemData[] = []
  @State draggedIndex: number = -1
  
  build() {
    Grid() {
      ForEach(this.items, (item: GridItemData, index: number) => {
        GridItem() {
          DraggableItem({ 
            item: item,
            index: index,
            onDragStart: this.onDragStart.bind(this),
            onDragEnd: this.onDragEnd.bind(this)
          })
        }
        .height(100)
    })
    .columnsTemplate('repeat(3, 1fr)')
  }
  
  // 拖拽開始處理
  onDragStart(index: number): void {
    this.draggedIndex = index
    // 添加拖拽視覺反饋
  }
  
  // 拖拽結束處理
  onDragEnd(targetIndex: number): void {
    if (this.draggedIndex >= 0) {
      // 執行數據重排序
      this.reorderItems(this.draggedIndex, targetIndex)
      this.draggedIndex = -1
    }
  }
}
五、多設備適配策略

1. 響應式斷點系統

@Entry
@Component
struct ResponsiveGrid {
  @State currentLayout: LayoutType = LayoutType.COMPACT
  
  aboutToAppear() {
    this.updateLayout(display.getDefaultDisplaySync().width)
  }
  
  build() {
    Grid() {
      // 網格內容
    }
    .columnsTemplate(this.getColumnsTemplate())
    .rowsTemplate(this.getRowsTemplate())
    .onAreaChange((oldValue, newValue) => {
      // 監聽窗口變化,動態調整佈局
      this.updateLayout(newValue.width)
    })
  }
  
  getColumnsTemplate(): string {
    switch (this.currentLayout) {
      case LayoutType.COMPACT: return '1fr'
      case LayoutType.MEDIUM: return '1fr 1fr'  
      case LayoutType.EXPANDED: return 'repeat(3, 1fr)'
      default: return '1fr'
    }
  }
  
  updateLayout(screenWidth: number): void {
    if (screenWidth < 600) {
      this.currentLayout = LayoutType.COMPACT
    } else if (screenWidth < 840) {
      this.currentLayout = LayoutType.MEDIUM
    } else {
      this.currentLayout = LayoutType.EXPANDED
    }
  }
}

2. 橫豎屏適配

@Component
struct AdaptiveGrid {
  @StorageLink('orientation') orientation: number = 0
  
  build() {
    Grid() {
      // 網格內容
    }
    .columnsTemplate(this.getLandscapeTemplate())
  }
  
  getLandscapeTemplate(): string {
    // 橫屏時增加列數,豎屏時減少列數
    return this.orientation === 1 ? 'repeat(4, 1fr)' : 'repeat(2, 1fr)'
  }
}
六、調試與性能監控

1. 佈局檢查器使用

通過DevEco Studio的佈局檢查器分析網格佈局性能:

  • 檢查網格線對齊情況
  • 監控組件創建和銷燬頻率
  • 分析重排和重繪性能

2. 性能打點分析

import hiTraceMeter from '@ohos.hiTraceMeter'

@Component
struct MeasuredGrid {
  build() {
    Grid() {
      // 網格內容
    }
    .onAppear(() => {
      hiTraceMeter.startTrace('grid_rendering', 1)
    })
    .onDisAppear(() => {
      hiTraceMeter.finishTrace('grid_rendering', 1)
    })
  }
}
💎 總結

Grid網格佈局是鴻蒙應用開發中處理二維佈局場景的利器。通過掌握基礎網格構建、不規則佈局實現、性能優化策略和多設備適配技巧,開發者可以創建出既美觀又高效的網格界面。關鍵在於根據具體場景選擇合適的實現方案,平衡功能需求與性能表現。

本系列下一篇文章將探討《自定義組件實戰:構建可複用的UI組件與組件間通信》,深入分析如何創建高複用性的自定義組件體系。

進一步學習建議:在實際項目中,建議從簡單網格開始,逐步添加複雜功能,並使用性能分析工具持續監控優化效果。官方文檔中的Grid組件詳解提供了完整的API參考。