HarmonyOS鸿蒙Next中通过自定义布局实现瀑布流效果

HarmonyOS鸿蒙Next中通过自定义布局实现瀑布流效果 在开发中,我们常使用系统预置的布局组件来快速构建界面。但是在实际开发中我们可能经常会遇到不规则、动态变化的布局需求,例如Pinterest风格的瀑布流布局。这种布局中,子元素的高度不一,但需要紧密排列,并自动填充到当前最短的列中,系统布局组件不太好直接实现。

那么我们如何实现一种这样的瀑布流效果呢?

6 回复

实现思路

第一步:应用 measure 修饰符,在容器组件(如 Stack)上,首先应用 .measure() 修饰符。.measure() 接收一个回调函数,该函数的参数是 MeasureResult 数组(每个子组件的测量结果)和父容器的 Constraint。

在这个回调中,我们遍历所有子组件,调用 child.measure() 来获取它们期望的尺寸。

根据瀑布流逻辑(找最短列、计算坐标),我们为每个子组件确定其位置和尺寸,并存储到 itemInfos 数组中。最后,返回整个容器的总尺寸。

第二步:应用 layout 修饰符,在 .measure() 之后,链式调用 .layout() 修饰符。.layout() 接收一个回调函数,其参数是 PlaceResult 数组(由 measure 阶段产生)。在这个回调中,我们遍历所有子组件,调用 child.place(),并从 itemInfos 中取出之前计算好的坐标,将子组件放置到正确的位置。

使用场景

瀑布流相关的场景,如图片瀑布流,文章瀑布流等。

实现效果

cke_4914.png

完整代码

interface ImageItem {
  id:number;
  url: Resource;
  height:number;
}

@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;

      // 模拟图片数据
      const list: ImageItem[] = []
      for (let i = 0; i < 20; i++) {
        list.push({
          id: i + 1,
          url: $r('app.media.4'),
          height: Math.floor(Math.random() * 200) + 200
        })
      }
      this.images = list

      // 初始化图片加载状态
      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)
            .margin({ top: 12 })
        }
        .height('100%')
        .justifyContent(FlexAlign.Center)
      } else {
        WaterFlow() {
          ForEach(this.images, (item: ImageItem) => {
            FlowItem() {
              Stack() {
                  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%')
  }
}

更多关于HarmonyOS鸿蒙Next中通过自定义布局实现瀑布流效果的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


很好

真不错的

在HarmonyOS Next中,可通过ArkUI的Grid组件结合自定义布局管理器实现瀑布流。使用WaterFlowLayout类继承自GridItemLayoutManager,重写onMeasure和onLayout方法,动态计算每个子组件的位置与高度。通过自定义组件尺寸和排列逻辑,实现不等高内容的错位排列效果。

在HarmonyOS Next中,可以通过自定义FlexLayoutManagerGridLayoutManager来实现瀑布流效果。核心思路是自定义LayoutManager,在onLayoutChildren方法中计算每个子组件的位置。

具体实现步骤:

  1. 创建自定义WaterfallLayoutManager继承自GridLayoutManager
  2. 重写onLayoutChildren方法,计算每列当前高度
  3. 遍历每个子组件,将其放置在最短的列中
  4. 根据子组件实际高度更新列高

关键代码示例:

class WaterfallLayoutManager extends GridLayoutManager {
  private columnHeights: number[] = [];
  private columnCount = 2; // 列数
  
  onLayoutChildren(recycler: Recycler, state: State) {
    // 初始化列高
    this.columnHeights = new Array(this.columnCount).fill(0);
    
    // 遍历所有子组件
    for (let i = 0; i < getItemCount(); i++) {
      // 找到最短列
      const shortestColumn = this.findShortestColumn();
      
      // 测量子组件
      const child = recycler.getViewForPosition(i);
      measureChildWithMargins(child);
      
      // 计算位置
      const left = shortestColumn * columnWidth;
      const top = this.columnHeights[shortestColumn];
      const right = left + getDecoratedMeasuredWidth(child);
      const bottom = top + getDecoratedMeasuredHeight(child);
      
      // 布局子组件
      layoutDecorated(child, left, top, right, bottom);
      
      // 更新列高
      this.columnHeights[shortestColumn] += getDecoratedMeasuredHeight(child);
    }
  }
  
  private findShortestColumn(): number {
    return this.columnHeights.indexOf(Math.min(...this.columnHeights));
  }
}

这种方法可以高效实现动态高度的瀑布流布局,支持任意列数和动态数据更新。

回到顶部