当前位置:首页 > 技术 > 正文内容

HarmonyOS NEXT 构建高性能瀑布流与懒加载列表

访客 技术 2026年9月13日 10

HarmonyOS NEXT 构建高性能瀑布流与懒加载列表

在HarmonyOS应用开发中,处理大量图文混排数据时,瀑布流(WaterFlow)结合懒加载(LazyForEach)是提升渲染性能和用户体验的核心方案。本文将深入探讨如何通过自定义数据源和组件,实现一个高性能的瀑布流列表。

核心实现思路

  1. 定义数据模型:构建描述瀑布流子项的数据接口。
  2. 实现数据源:基于 IDataSource 接口封装数据管理逻辑,支持数据的动态增删与状态通知。
  3. 封装子项组件:开发可复用的UI组件,根据数据特征自适应布局。
  4. 页面组装:在页面中使用 WaterFlow 容器配合 LazyForEach 实现按需渲染。

WaterFlow 组件特性

WaterFlow 是一种流式布局容器,通过行列网格划分,将不同尺寸的子项按照从上到下、从左到右的规则紧密排列,形成瀑布流效果。它仅允许包含 FlowItem 子组件,并全面支持 if/else、ForEach、LazyForEach 以及 Repeat 等渲染控制语法。

数据模型与数据源实现

首先定义子项的数据结构,然后实现 IDataSource 接口以驱动 LazyForEach。为了优化代码结构,我们将初始数据的生成逻辑进行了封装,并规范了数据变更的通知机制。


// 定义瀑布流子项的数据模型
export interface FlowItemData {
  coverImg: Resource;
  coverWidth: Length;
  coverHeight: Length;
  description: string;
  actionText: string;
}

// 实现 IDataSource 接口,管理瀑布流数据
export class FlowListDataProvider implements IDataSource {
  private itemList: FlowItemData[] = [];
  private observers: DataChangeListener[] = [];

  constructor() {
    this.loadInitialData();
  }

  // 封装模拟数据生成逻辑
  private generateMockItems(): FlowItemData[] {
    return [
      { coverImg: $r('app.media.pic_1'), coverWidth: 162, coverHeight: 130, description: 'Explore the art of modern design...', actionText: 'Read More' },
      { coverImg: $r('app.media.pic_2'), coverWidth: '100%', coverHeight: 117, description: 'Daily routines for a healthier life...', actionText: 'Read More' },
      { coverImg: $r('app.media.pic_3'), coverWidth: '100%', coverHeight: 117, description: 'The science behind your morning coffee...', actionText: 'Read More' },
      { coverImg: $r('app.media.pic_4'), coverWidth: 162, coverHeight: 130, description: 'Trends shaping the fashion industry...', actionText: 'Read More' },
      { coverImg: $r('app.media.pic_5'), coverWidth: '100%', coverHeight: 206, description: 'Building beautiful UIs with ease...', actionText: 'Read More' }
    ];
  }

  private loadInitialData(): void {
    this.itemList.push(...this.generateMockItems());
  }

  public getData(index: number): FlowItemData {
    return this.itemList[index];
  }

  public totalCount(): number {
    return this.itemList.length;
  }

  public registerDataChangeListener(listener: DataChangeListener): void {
    if (!this.observers.includes(listener)) {
      this.observers.push(listener);
    }
  }

  public unregisterDataChangeListener(listener: DataChangeListener): void {
    const idx = this.observers.indexOf(listener);
    if (idx !== -1) {
      this.observers.splice(idx, 1);
    }
  }

  // 通知数据重载
  private notifyReload(): void {
    this.observers.forEach(obs => obs.onDataReloaded());
  }

  // 通知数据新增
  private notifyAdd(index: number): void {
    this.observers.forEach(obs => obs.onDataAdd(index));
  }

  // 通知数据删除
  private notifyDelete(index: number): void {
    this.observers.forEach(obs => obs.onDataDelete(index));
  }

  // 批量追加数据
  public appendBatchData(): void {
    const newItems = this.generateMockItems();
    this.itemList.push(...newItems);
    this.notifyReload(); 
  }

  // 在头部插入数据
  public prependItem(item: FlowItemData): void {
    this.itemList.unshift(item);
    this.notifyAdd(0);
  }

  // 移除指定索引数据
  public removeItemAt(index: number): void {
    if (index >= 0 && index < this.itemList.length) {
      this.itemList.splice(index, 1);
      this.notifyDelete(index);
    }
  }
}

自定义瀑布流子项组件

子项组件需要支持组件复用(@Reusable),并在 aboutToReuse 生命周期中更新状态。布局上,根据封面图片的宽度属性动态切换垂直(Column)或水平(Row)排列。


import { FlowItemData } from './FlowListDataProvider';

@Reusable
@Component
export struct FlowCardView {
  @Prop itemData: FlowItemData = {} as FlowItemData;

  aboutToReuse(params: Record): void {
    this.itemData = params.itemData;
  }

  build() {
    if (this.itemData.coverWidth === '100%') {
      // 垂直布局:图片在上,文本在下
      Column() {
        Image(this.itemData.coverImg)
          .width(this.itemData.coverWidth)
          .height(this.itemData.coverHeight)
          .objectFit(ImageFit.Cover)
        
        Column() {
          Text(this.itemData.description)
            .fontSize(14)
            .fontColor('#333333')
            .maxLines(2)
            .textOverflow({ overflow: TextOverflow.Ellipsis })
          
          Text(this.itemData.actionText)
            .fontSize(12)
            .fontColor('#007DFF')
            .margin({ top: 8 })
        }
        .width('100%')
        .padding(12)
        .layoutWeight(1)
        .alignItems(HorizontalAlign.Start)
      }
      .width('100%')
      .backgroundColor(Color.White)
      .borderRadius(8)
    } else {
      // 水平布局:图片在左,文本在右
      Row() {
        Image(this.itemData.coverImg)
          .width(this.itemData.coverWidth)
          .height(this.itemData.coverHeight)
          .objectFit(ImageFit.Cover)
          .borderRadius({ topLeft: 8, bottomLeft: 8 })

        Column() {
          Text(this.itemData.description)
            .fontSize(14)
            .fontColor('#333333')
            .maxLines(3)
            .textOverflow({ overflow: TextOverflow.Ellipsis })
          
          Text(this.itemData.actionText)
            .fontSize(12)
            .fontColor('#007DFF')
            .margin({ top: 8 })
        }
        .height('100%')
        .layoutWeight(1)
        .padding(12)
        .alignItems(HorizontalAlign.Start)
      }
      .width('100%')
      .height('100%')
      .backgroundColor(Color.White)
      .borderRadius(8)
    }
  }
}

页面集成与 LazyForEach 渲染

在页面中,通过配置 WaterFlowSections 来定义瀑布流的分区规则(如列数、间距、子项高度等)。结合 LazyForEach 实现数据的按需加载,避免一次性渲染所有子项导致的内存和性能问题。


import { FlowItemData, FlowListDataProvider } from './FlowListDataProvider';
import { FlowCardView } from './FlowCardView';

@Entry
@Component
struct WaterFlowExamplePage {
  private listProvider: FlowListDataProvider = new FlowListDataProvider();
  private flowScroller: Scroller = new Scroller();
  @State flowSections: WaterFlowSections = new WaterFlowSections();

  aboutToAppear(): void {
    this.initializeSections();
    // 模拟加载多页数据
    for (let i = 0; i < 5; i++) {
      this.listProvider.appendBatchData();
    }
  }

  // 配置瀑布流分区参数
  private initializeSections(): void {
    const baseMargin: Margin = { top: 10, left: 16, bottom: 10, right: 16 };

    // 单列大卡片分区
    this.flowSections.push({
      itemsCount: 1,
      crossCount: 1,
      margin: { ...baseMargin, top: 20 },
      onGetItemMainSizeByIndex: () => 140
    });

    // 双列标准卡片分区
    this.flowSections.push({
      itemsCount: 2,
      crossCount: 2,
      rowsGap: '16vp',
      columnsGap: '16vp',
      margin: baseMargin,
      onGetItemMainSizeByIndex: () => 220
    });

    // 单列横向卡片分区
    this.flowSections.push({
      itemsCount: 1,
      crossCount: 1,
      margin: baseMargin,
      onGetItemMainSizeByIndex: () => 140
    });

    // 双列长卡片分区
    this.flowSections.push({
      itemsCount: 2,
      crossCount: 2,
      rowsGap: '16vp',
      columnsGap: '16vp',
      margin: baseMargin,
      onGetItemMainSizeByIndex: () => 280
    });
  }

  build() {
    Column() {
      WaterFlow({ scroller: this.flowScroller, sections: this.flowSections }) {
        LazyForEach(this.listProvider, (item: FlowItemData, index: number) => {
          FlowItem() {
            FlowCardView({ itemData: item })
          }
          .width('100%')
          .padding({ bottom: 12 })
        }, (item: FlowItemData, index: number) => {
          // 生成唯一Key,确保组件复用的准确性
          return `flow_item_${index}_${item.description.length}`;
        })
      }
      .columnsGap(12)
      .rowsGap(12)
      .backgroundColor('#F1F3F5')
      .width('100%')
      .layoutWeight(1)
    }
  }
}

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。