鴻蒙學習實戰之路 - 瀑布流操作實現

官方文檔永遠是你的好夥伴,請收藏!

華為開發者聯盟 - 瀑布流最佳實踐 華為開發者聯盟 - WaterFlow 組件參考文檔

關於本文

本文主要介紹在 HarmonyOS 中如何實現高性能、高體驗的瀑布流佈局,包含基礎實現和高級優化技巧

  • 本文並不能代替官方文檔,所有內容基於官方文檔+實踐記錄
  • 所有代碼示例都有詳細註釋,建議自己動手嘗試
  • 基本所有關鍵功能都會附上對應的文檔鏈接,強烈建議你點看看看

概述

瀑布流(WaterFlow)佈局是移動應用中常見的佈局方式,用於展示高度不一的內容塊,形成錯落有致的視覺效果。在 HarmonyOS 中,我們可以使用 WaterFlow 組件快速實現瀑布流佈局,但要實現高性能、流暢的瀑布流效果,還需要掌握一些優化技巧。

鴻蒙學習實戰之路 - 瀑布流操作實現_數據

實現原理

關鍵技術

瀑布流佈局主要通過以下核心技術實現:

  1. WaterFlow 組件 - 實現瀑布流的基礎容器
  2. LazyForEach - 實現數據的懶加載,提升性能
  3. GridItem - 定義瀑布流中的每個內容項
  4. 異步加載 - 實現圖片等資源的異步加載
  5. 狀態管理 - 管理瀑布流的加載狀態和數據

重要提醒! 實現高性能瀑布流需要注意:

  • 內容項的高度要準確計算,避免佈局抖動
  • 圖片資源需要懶加載,避免一次性加載過多資源
  • 合理設置緩存策略,提升重複訪問的性能
  • 考慮大數據量下的性能優化和內存管理

開發流程

實現瀑布流佈局的基本步驟:

  1. 創建 WaterFlow 組件作為佈局容器
  2. 配置瀑布流參數(列數、間距等)
  3. 使用 LazyForEach 綁定數據源
  4. 實現內容項的佈局和樣式
  5. 添加異步加載和性能優化

華為開發者聯盟 - 列表懶加載參考文檔

基礎瀑布流實現

場景描述

在應用中展示圖片列表,每張圖片高度不一,形成錯落有致的瀑布流效果。

鴻蒙學習實戰之路 - 瀑布流操作實現_加載_02

開發步驟

1. 創建基礎的瀑布流佈局

首先創建一個基本的 WaterFlow 組件作為瀑布流容器:

@Entry
@Component
struct BasicWaterFlowPage {
  // 圖片數據
  @State images: ImageItem[] = [];

  // 頁面加載時初始化數據
  aboutToAppear() {
    this.initData();
  }

  // 初始化圖片數據
  initData() {
    // 模擬圖片數據
    this.images = [
      { id: 1, url: 'https://example.com/image1.jpg', height: 300 },
      { id: 2, url: 'https://example.com/image2.jpg', height: 250 },
      { id: 3, url: 'https://example.com/image3.jpg', height: 350 },
      { id: 4, url: 'https://example.com/image4.jpg', height: 280 },
      { id: 5, url: 'https://example.com/image5.jpg', height: 320 },
      { id: 6, url: 'https://example.com/image6.jpg', height: 260 }
    ];
  }

  build() {
    Column() {
      // 瀑布流組件
      WaterFlow() {
        // 使用LazyForEach實現數據懶加載
        LazyForEach(this.images, (item: ImageItem) => {
          // 瀑布流中的每個item
          GridItem() {
            // 圖片組件
            Image(item.url)
              .width('100%') // 寬度佔滿
              .height(item.height) // 設置圖片高度
              .objectFit(ImageFit.Cover) // 圖片填充方式
              .borderRadius(12) // 圓角處理
              .margin(8) // 外邊距
          }
        }, (item: ImageItem) => item.id.toString()) // 設置唯一key
      }
      .columnsTemplate('1fr 1fr') // 設置2列布局
      .columnsGap(16) // 列間距
      .rowsGap(16) // 行間距
      .padding(16) // 內邊距
    }
    .width('100%')
    .height('100%')
  }
}

// 圖片數據模型
export interface ImageItem {
  id: number; // 唯一標識
  url: string; // 圖片地址
  height: number; // 圖片高度
}
2. 添加加載狀態和錯誤處理

為了提升用户體驗,我們可以添加加載狀態和錯誤處理:

@Entry
@Component
struct WaterFlowWithLoadingPage {
  // 圖片數據
  @State images: ImageItem[] = [];
  // 加載狀態
  @State isLoading: boolean = true;
  // 錯誤信息
  @State errorMessage: string = '';

  // 頁面加載時初始化數據
  aboutToAppear() {
    this.loadData();
  }

  // 加載數據
  async loadData() {
    try {
      this.isLoading = true;
      this.errorMessage = '';

      // 模擬網絡請求延遲
      await new Promise(resolve => setTimeout(resolve, 1500));

      // 模擬圖片數據
      this.images = [
        { id: 1, url: 'https://example.com/image1.jpg', height: 300 },
        { id: 2, url: 'https://example.com/image2.jpg', height: 250 },
        { id: 3, url: 'https://example.com/image3.jpg', height: 350 },
        { id: 4, url: 'https://example.com/image4.jpg', height: 280 },
        { id: 5, url: 'https://example.com/image5.jpg', height: 320 },
        { id: 6, url: 'https://example.com/image6.jpg', height: 260 }
      ];
    } catch (error) {
      this.errorMessage = '加載失敗,請稍後重試';
      console.error('加載圖片數據失敗:', error);
    } finally {
      this.isLoading = false;
    }
  }

  build() {
    Column() {
      // 加載狀態顯示
      if (this.isLoading) {
        Column() {
          LoadingProgress() // 加載進度條
            .color('#007DFF')
            .size({ width: 40, height: 40 })
          Text('加載中...') // 加載文本
            .fontSize(14)
            .color('#666666')
            .margin({ top: 12 })
        }
        .height('100%')
        .justifyContent(FlexAlign.Center)
      }
      // 錯誤狀態顯示
      else if (this.errorMessage) {
        Column() {
          Text('❌') // 錯誤圖標
            .fontSize(48)
          Text(this.errorMessage) // 錯誤文本
            .fontSize(14)
            .color('#FF4D4F')
            .margin({ top: 12 })
          Button('重試') // 重試按鈕
            .type(ButtonType.Capsule)
            .margin({ top: 20 })
            .onClick(() => {
              this.loadData(); // 重新加載數據
            })
        }
        .height('100%')
        .justifyContent(FlexAlign.Center)
      }
      // 正常狀態顯示瀑布流
      else {
        WaterFlow() {
          LazyForEach(this.images, (item: ImageItem) => {
            GridItem() {
              Image(item.url)
                .width('100%')
                .height(item.height)
                .objectFit(ImageFit.Cover)
                .borderRadius(12)
                .margin(8)
            }
          }, (item: ImageItem) => item.id.toString())
        }
        .columnsTemplate('1fr 1fr')
        .columnsGap(16)
        .rowsGap(16)
        .padding(16)
      }
    }
    .width('100%')
    .height('100%')
  }
}

高級瀑布流實現

場景描述

實現一個帶有下拉刷新、上拉加載更多功能的瀑布流,支持圖片懶加載和點擊查看詳情。

開發步驟

1. 添加下拉刷新功能

使用 Refresh 組件實現下拉刷新功能:

@Entry
@Component
struct WaterFlowWithRefreshPage {
  // 圖片數據
  @State images: ImageItem[] = [];
  // 加載狀態
  @State isLoading: boolean = true;
  // 下拉刷新狀態
  @State refreshing: boolean = false;
  // 當前頁碼
  private currentPage: number = 1;
  // 每頁數量
  private pageSize: number = 12;

  // 頁面加載時初始化數據
  aboutToAppear() {
    this.loadData();
  }

  // 加載數據
  async loadData(isRefresh: boolean = false) {
    try {
      if (isRefresh) {
        this.refreshing = true;
        this.currentPage = 1;
      } else {
        this.isLoading = true;
      }

      // 模擬網絡請求延遲
      await new Promise(resolve => setTimeout(resolve, 1500));

      // 模擬新數據
      const newData: ImageItem[] = Array.from({ length: this.pageSize }, (_, index) => ({
        id: (this.currentPage - 1) * this.pageSize + index + 1,
        url: `https://example.com/image${(this.currentPage - 1) * this.pageSize + index + 1}.jpg`,
        height: Math.floor(Math.random() * 200) + 200 // 隨機高度200-400
      }));

      // 更新數據
      if (isRefresh) {
        this.images = newData;
      } else {
        this.images = [...this.images, ...newData];
      }

      this.currentPage++;
    } catch (error) {
      console.error('加載圖片數據失敗:', error);
    } finally {
      this.isLoading = false;
      this.refreshing = false;
    }
  }

  build() {
    Column() {
      // 下拉刷新組件
      Refresh({
        refreshing: this.refreshing,
        offset: 120,
        friction: 100
      })
      {
        WaterFlow() {
          LazyForEach(this.images, (item: ImageItem) => {
            GridItem() {
              // 點擊事件處理
              GestureDetector() {
                Image(item.url)
                  .width('100%')
                  .height(item.height)
                  .objectFit(ImageFit.Cover)
                  .borderRadius(12)
                  .margin(8)
              }
              .onClick(() => {
                // 點擊圖片查看詳情
                console.log('查看圖片詳情:', item.id);
              })
            }
          }, (item: ImageItem) => item.id.toString())

          // 加載更多提示
          if (!this.isLoading && this.images.length > 0) {
            GridItem() {
              Text('上拉加載更多')
                .fontSize(14)
                .color('#999999')
                .padding(20)
            }
            .columnSpan(2) // 跨兩列
          }
        }
        .columnsTemplate('1fr 1fr')
        .columnsGap(16)
        .rowsGap(16)
        .padding(16)
        // 滾動到底部觸發加載更多
        .onReachEnd(() => {
          if (!this.isLoading) {
            this.loadData();
          }
        })
      }
      .onRefresh(() => {
        this.loadData(true); // 下拉刷新,重新加載第一頁數據
      })

      // 底部加載指示器
      if (this.isLoading && this.images.length > 0) {
        Row() {
          LoadingProgress()
            .color('#007DFF')
            .size({ width: 24, height: 24 })
          Text('加載中...')
            .fontSize(14)
            .color('#666666')
            .margin({ left: 8 })
        }
        .padding(20)
      }
    }
    .width('100%')
    .height('100%')
  }
}
2. 實現圖片懶加載和緩存

為了提升性能,我們可以實現圖片的懶加載和緩存功能:

@Entry
@Component
struct WaterFlowWithLazyLoadPage {
  // 圖片數據
  @State images: ImageItem[] = [];
  // 加載狀態
  @State isLoading: boolean = true;
  // 圖片加載狀態映射
  @State imageLoadStatus: Record<number, 'loading' | 'loaded' | 'error'> = {};

  // 頁面加載時初始化數據
  aboutToAppear() {
    this.loadData();
  }

  // 加載數據
  async loadData() {
    try {
      this.isLoading = true;

      // 模擬網絡請求延遲
      await new Promise(resolve => setTimeout(resolve, 1500));

      // 模擬圖片數據
      this.images = Array.from({ length: 20 }, (_, index) => ({
        id: index + 1,
        url: `https://example.com/image${index + 1}.jpg`,
        height: Math.floor(Math.random() * 200) + 200
      }));

      // 初始化圖片加載狀態
      this.images.forEach(item => {
        this.imageLoadStatus[item.id] = 'loading';
      });
    } catch (error) {
      console.error('加載圖片數據失敗:', error);
    } finally {
      this.isLoading = false;
    }
  }

  // 圖片加載完成
  onImageLoad(id: number) {
    this.imageLoadStatus[id] = 'loaded';
  }

  // 圖片加載失敗
  onImageError(id: number) {
    this.imageLoadStatus[id] = 'error';
  }

  build() {
    Column() {
      if (this.isLoading) {
        Column() {
          LoadingProgress()
            .color('#007DFF')
            .size({ width: 40, height: 40 })
          Text('加載中...')
            .fontSize(14)
            .color('#666666')
            .margin({ top: 12 })
        }
        .height('100%')
        .justifyContent(FlexAlign.Center)
      } else {
        WaterFlow() {
          LazyForEach(this.images, (item: ImageItem) => {
            GridItem() {
              Stack() {
                // 圖片加載中
                if (this.imageLoadStatus[item.id] === 'loading') {
                  Row() {
                    LoadingProgress()
                      .color('#007DFF')
                      .size({ width: 24, height: 24 })
                  }
                  .width('100%')
                  .height(item.height)
                  .justifyContent(FlexAlign.Center)
                  .alignItems(VerticalAlign.Center)
                  .backgroundColor('#F5F5F5')
                  .borderRadius(12)
                  .margin(8)
                }
                // 圖片加載失敗
                else if (this.imageLoadStatus[item.id] === 'error') {
                  Row() {
                    Text('❌')
                      .fontSize(32)
                  }
                  .width('100%')
                  .height(item.height)
                  .justifyContent(FlexAlign.Center)
                  .alignItems(VerticalAlign.Center)
                  .backgroundColor('#F5F5F5')
                  .borderRadius(12)
                  .margin(8)
                }
                // 圖片加載完成
                else {
                  Image(item.url)
                    .width('100%')
                    .height(item.height)
                    .objectFit(ImageFit.Cover)
                    .borderRadius(12)
                    .margin(8)
                    .onComplete(() => this.onImageLoad(item.id)) // 加載完成回調
                    .onError(() => this.onImageError(item.id)) // 加載失敗回調
                }
              }
            }
          }, (item: ImageItem) => item.id.toString())
        }
        .columnsTemplate('1fr 1fr')
        .columnsGap(16)
        .rowsGap(16)
        .padding(16)
      }
    }
    .width('100%')
    .height('100%')
  }
}

性能優化建議

鴻蒙學習實戰之路 - 瀑布流操作實現_數據_03

1. 數據懶加載

使用 LazyForEach 替代 ForEach,實現數據的懶加載,避免一次性渲染過多內容項:

// 推薦使用
WaterFlow() {
  LazyForEach(this.data, (item) => {
    GridItem() {
      // 內容項
    }
  }, (item) => item.id.toString())
}

// 不推薦使用(大數據量下性能差)
WaterFlow() {
  ForEach(this.data, (item) => {
    GridItem() {
      // 內容項
    }
  }, (item) => item.id.toString())
}

2. 圖片優化

  • 圖片懶加載:只加載可視區域內的圖片
  • 圖片壓縮:使用合適分辨率的圖片,避免加載過大圖片
  • 圖片緩存:實現圖片緩存策略,減少重複請求
  • 佔位符:使用骨架屏或佔位符提升用户體驗

3. 佈局優化

  • 固定列數:避免動態修改列數導致佈局重排
  • 預計算高度:提前計算內容項高度,避免佈局抖動
  • 減少嵌套:減少組件嵌套層級,提升渲染性能

4. 內存優化

  • 合理設置緩存大小:避免緩存過多數據導致內存溢出
  • 及時釋放資源:在組件銷燬時釋放圖片等資源
  • 避免內存泄漏:及時清理定時器和事件監聽器

交互體驗優化

鴻蒙學習實戰之路 - 瀑布流操作實現_瀑布流_04

1. 加載狀態反饋

  • 初始加載時顯示加載動畫
  • 下拉刷新時顯示刷新動畫
  • 上拉加載更多時顯示加載指示器
  • 加載失敗時顯示錯誤信息和重試按鈕

2. 手勢交互

  • 支持下拉刷新
  • 支持上拉加載更多
  • 支持點擊查看詳情
  • 支持長按操作

3. 視覺效果

  • 添加適當的間距和圓角,提升視覺舒適度
  • 使用漸入動畫,提升頁面活力
  • 添加陰影效果,增強層次感
  • 統一的色彩方案,提升品牌一致性

總結

本文介紹了在 HarmonyOS 中實現瀑布流佈局的完整流程,從基礎實現到高級優化,涵蓋了以下內容:

  1. 基礎瀑布流實現:使用 WaterFlow 組件創建基本的瀑布流佈局
  2. 高級功能:添加下拉刷新、上拉加載更多、圖片懶加載等功能
  3. 性能優化:數據懶加載、圖片優化、佈局優化、內存優化
  4. 交互體驗:加載狀態反饋、手勢交互、視覺效果優化

通過本文的學習,你應該能夠在 HarmonyOS 應用中實現高性能、流暢的瀑布流佈局,為用户提供良好的視覺體驗和交互體驗。

如果你有任何問題或建議,歡迎留言討論!