鴻蒙Next實(shí)現(xiàn)瀑布流布局

鴻蒙Next實(shí)現(xiàn)瀑布流布局 #鴻蒙影音娛樂(lè)類應(yīng)用 #拍攝美化 #HarmonyOS

一、環(huán)境準(zhǔn)備與項(xiàng)目創(chuàng)建

在開(kāi)始實(shí)現(xiàn)瀑布流布局前,需確保已安裝好 DevEco Studio,且已配置好鴻蒙開(kāi)發(fā)環(huán)境。打開(kāi) DevEco Studio,新建一個(gè)鴻蒙應(yīng)用項(xiàng)目,選擇合適的模板(如 Empty Feature Ability),設(shè)置項(xiàng)目名稱、包名等信息,完成項(xiàng)目創(chuàng)建。

二、布局設(shè)計(jì)思路

鴻蒙 Next 的瀑布流布局可以通過(guò)自定義組件結(jié)合 Column、Row 等容器組件實(shí)現(xiàn)。其核心思路是將數(shù)據(jù)分成若干列,每列獨(dú)立滾動(dòng)展示,且根據(jù)數(shù)據(jù)項(xiàng)高度動(dòng)態(tài)調(diào)整布局,以達(dá)到類似瀑布自然流動(dòng)的效果。

三、基礎(chǔ)實(shí)現(xiàn)

創(chuàng)建一個(gè)自定義組件 MasonryLayout,接收?qǐng)D片數(shù)據(jù)數(shù)組作為參數(shù),并根據(jù)列數(shù)將數(shù)據(jù)分配到不同列中展示:

@Component
export struct  MasonryLayout {
  @Prop data: string[];

  @State cols: number[] = Array.from<number>({ length: 2 }).fill(0);

  build() {
    Row({}) {
      ForEach(this.cols, (_col: number, cIndex) => {
        Column({  }) {
          ForEach(this.data, (item: string, i) => {
            if(i % this.cols.length === cIndex) {
              Image(item).width(`${100 / this.cols.length}%`);
            }
          })
        }
      })
    }.alignItems(VerticalAlign.Top)
  }
}

四、引用 MasonryLayout 瀑布流組件

build() {
  MasonryLayout({
    data: ["img1.png", "img2.png", "img3.png", "img4.png", "img5.png"],
  });
}
pbl.png

五、優(yōu)化與擴(kuò)展

1. 響應(yīng)式布局

通過(guò) MediaQuery 組件根據(jù)屏幕寬度動(dòng)態(tài)調(diào)整瀑布流的列數(shù),以適配不同設(shè)備:

在 UIAbility 的 onWindowStageCreate 生命周期回調(diào)中,通過(guò)窗口對(duì)象獲取啟動(dòng)時(shí)的應(yīng)用窗口寬度并注冊(cè)回調(diào)函數(shù)監(jiān)聽(tīng)窗口尺寸變化。將窗口尺寸的長(zhǎng)度單位由 px 換算為 vp 后,即可基于前文中介紹的規(guī)則得到當(dāng)前斷點(diǎn)值,此時(shí)可以使用狀態(tài)變量記錄當(dāng)前的斷點(diǎn)值方便后續(xù)使用

  • MainAbility.ts
import { window, display } from "@kit.ArkUI";
import { UIAbility } from "@kit.AbilityKit";

export default class MainAbility extends UIAbility {
  private windowObj?: window.Window;
  private col: number = 2;
  //...
  // 根據(jù)當(dāng)前窗口尺寸更新斷點(diǎn)
  private updateBreakpoint(windowWidth: number): void {
    // 將長(zhǎng)度的單位由px換算為vp
    let windowWidthVp = windowWidth / display.getDefaultDisplaySync().densityPixels;
    let col: number = this.col;
    if (windowWidthVp < 320) {
      // "xs";
      col = 1;
    } else if (windowWidthVp < 600) {
      // "sm";
      col = 2;
    } else if (windowWidthVp < 840) {
      // "md";
      col = 3;
    } else {
      // "lg";
      col = 4;
    }
    if (this.col !== col) {
      this.col = col;
    }
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    windowStage.getMainWindow().then((windowObj) => {
      this.windowObj = windowObj;
      // 獲取應(yīng)用啟動(dòng)時(shí)的窗口尺寸
      this.updateBreakpoint(windowObj.getWindowProperties().windowRect.width);
      // 注冊(cè)回調(diào)函數(shù),監(jiān)聽(tīng)窗口尺寸變化
      windowObj.on("windowSizeChange", (windowSize) => {
        this.updateBreakpoint(windowSize.width);
      });
    });
    // ...
  }

  //...
}
  • MasonryLayout.ets
interface IBpMapCol {
  xs: number;
  sm: number;
  md: number;
  lg: number;
}

const bpMapCol = new Map<string, number>();

bpMapCol.set('xs', 1)
bpMapCol.set('sm', 2)
bpMapCol.set('md', 3)
bpMapCol.set('lg', 4)

@Component
export struct  MasonryLayout {
  @StorageProp('currentBreakpoint') curBp: keyof IBpMapCol = 'sm';
  @Prop data: string[];

  @State cols: number[] = Array.from<number>({ length: bpMapCol.get(this.curBp) || 2 }).fill(0);

  build() {
    Row({}) {
      ForEach(this.cols, (_col: number, cIndex) => {
        Column({  }) {
          ForEach(this.data, (item: string, i) => {
            if(i % this.cols.length === cIndex) {
              Image(item).width(`${100 / this.cols.length}%`);
              Text(this.curBp)
            }
          })
        }
      })
    }.alignItems(VerticalAlign.Top)
  }
}

注:鴻蒙 next 中無(wú)法使用索引訪問(wèn)對(duì)象屬性,如 const obj = { a: 1 } 無(wú)法使用 obj[a],這種情況可以用 Map

2. 動(dòng)態(tài)加載數(shù)據(jù)

為了實(shí)現(xiàn)類似真實(shí)瀑布流不斷加載新數(shù)據(jù)的效果,可以結(jié)合鴻蒙的 LazyForEach 組件,在滾動(dòng)到列表底部時(shí)觸發(fā)數(shù)據(jù)加載邏輯

六、網(wǎng)絡(luò)權(quán)限

// config.json
{
  "module": {
    "reqPermissions": [
      {
        "name": "ohos.permission.INTERNET",
        "reason": "需要網(wǎng)絡(luò)權(quán)限來(lái)加載圖片"
      }
    ]
  }
}

七、常見(jiàn)問(wèn)題與解決方案

1. 圖片加載后布局跳動(dòng)

  • 解決方案:使用預(yù)估高度占位,圖片加載完成后更新高度

2. 大數(shù)據(jù)量性能問(wèn)題

  • 解決方案:實(shí)現(xiàn)虛擬列表,只渲染可視區(qū)域內(nèi)的元素

3. 滾動(dòng)卡頓

  • 解決方案
    • 使用防抖/節(jié)流處理滾動(dòng)事件
    • 避免在滾動(dòng)回調(diào)中執(zhí)行復(fù)雜計(jì)算
    • 使用鴻蒙的 Canvas 組件替代部分布局組件

4. 不同設(shè)備適配問(wèn)題

  • 解決方案
    • 使用響應(yīng)式布局動(dòng)態(tài)調(diào)整列數(shù)
    • 基于設(shè)備類型設(shè)置不同的默認(rèn)列數(shù)

八、最佳實(shí)踐總結(jié)

  1. 優(yōu)先使用固定高度:如果業(yè)務(wù)場(chǎng)景允許,盡量使用固定高度或?qū)捀弑龋瑴p少動(dòng)態(tài)測(cè)量開(kāi)銷
  2. 合理實(shí)現(xiàn)懶加載:對(duì)于非首屏內(nèi)容或圖片資源,一定要實(shí)現(xiàn)懶加載
  3. 漸進(jìn)式增強(qiáng)體驗(yàn):先確?;A(chǔ)功能可用,再添加動(dòng)畫(huà)和交互效果
  4. 測(cè)試與優(yōu)化:在不同設(shè)備上測(cè)試性能表現(xiàn),針對(duì)卡頓問(wèn)題進(jìn)行專項(xiàng)優(yōu)化
  5. 遵循鴻蒙設(shè)計(jì)規(guī)范:保持與鴻蒙系統(tǒng)一致的視覺(jué)風(fēng)格和交互體驗(yàn)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容