使用瀑布流
=====

ArkUI开发框架在NDK接口提供了瀑布流容器组件，通过瀑布流自身的排列规则，将不同大小的"项目"自上而下如瀑布般紧密布局。

#### 接入ArkTS页面

为了使用NDK接口构建UI界面，参考[接入ArkTS页面章节](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ndk-access-the-arkts-page)，在ArkTS页面上创建用于Native页面挂载的占位组件，并实现ArkTS侧的NativeNode模块接口。

#### 使用懒加载

#### \[h2\]NodeAdapter介绍

NDK中提供了NodeAdapter对象替代ArkTS侧的[LazyForEach](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-rendering-control-lazyforeach)功能，用于按需生成子组件。详情请参阅[NodeAdapter介绍](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ndk-loading-long-list#nodeadapter介绍)。

#### \[h2\]实现懒加载适配器

使用FlowItemAdapter类管理懒加载适配器。在类的构造函数中创建NodeAdapter对象，并给NodeAdapter对象设置事件监听器，在类的析构函数中，销毁NodeAdapter对象。

// FlowItemAdapter.h
// 懒加载功能代码。

#ifndef MYAPPLICATION\_FLOWITEMADAPTER\_H
#define MYAPPLICATION\_FLOWITEMADAPTER\_H

#include <arkui/native\_node.h>
#include <stack>
#include <string>
#include <unordered\_set>
#include <arkui/native\_interface.h>

namespace NativeModule {
const int NUM = 100;
class FlowItemAdapter {
public:
    FlowItemAdapter()
    {
        // 初始化函数指针结构体
        OH\_ArkUI\_GetModuleInterface(ARKUI\_NATIVE\_NODE, ArkUI\_NativeNodeAPI\_1, nodeApi\_);
        // 创建Adapter对象
        adapter\_ = OH\_ArkUI\_NodeAdapter\_Create();
        
        // 初始化懒加载数据。
        for (int32\_t i = 0; i < NUM; i++) {
            data\_.emplace\_back(std::to\_string(i));
        }
        // 设置懒加载数据。
        OH\_ArkUI\_NodeAdapter\_SetTotalNodeCount(adapter\_, data\_.size());
        // 设置事件监听器。
        OH\_ArkUI\_NodeAdapter\_RegisterEventReceiver(adapter\_, this, OnStaticAdapterEvent);
    }

    ~FlowItemAdapter()
    {
        // 释放创建的组件。
        while (!cachedItems\_.empty()) {
            cachedItems\_.pop();
        }
        // 释放Adapter相关资源。
        OH\_ArkUI\_NodeAdapter\_UnregisterEventReceiver(adapter\_);
        OH\_ArkUI\_NodeAdapter\_Dispose(adapter\_);
    }

    ArkUI\_NodeAdapterHandle GetAdapter() const { return adapter\_; }

    void RemoveItem(int32\_t index)
    {
        // 删除第index个数据。
        data\_.erase(data\_.begin() + index);
        // 如果index会导致可视区域元素发生可见性变化，则会回调NODE\_ADAPTER\_EVENT\_ON\_REMOVE\_NODE\_FROM\_ADAPTER事件删除元素，
        // 根据是否有新增元素回调NODE\_ADAPTER\_EVENT\_ON\_GET\_NODE\_ID和NODE\_ADAPTER\_EVENT\_ON\_ADD\_NODE\_TO\_ADAPTER事件。
        OH\_ArkUI\_NodeAdapter\_RemoveItem(adapter\_, index, 1);
        // 更新新的数量。
        OH\_ArkUI\_NodeAdapter\_SetTotalNodeCount(adapter\_, data\_.size());
    }

    void InsertItem(int32\_t index, const std::string &value)
    {
        data\_.insert(data\_.begin() + index, value);
        // 如果index会导致可视区域元素发生可见性变化，则会回调NODE\_ADAPTER\_EVENT\_ON\_GET\_NODE\_ID和NODE\_ADAPTER\_EVENT\_ON\_ADD\_NODE\_TO\_ADAPTER事件，
        // 根据是否有删除元素回调NODE\_ADAPTER\_EVENT\_ON\_REMOVE\_NODE\_FROM\_ADAPTER事件。
        OH\_ArkUI\_NodeAdapter\_InsertItem(adapter\_, index, 1);
        // 更新新的数量。
        OH\_ArkUI\_NodeAdapter\_SetTotalNodeCount(adapter\_, data\_.size());
    }

    void MoveItem(int32\_t oldIndex, int32\_t newIndex)
    {
        auto temp = data\_\[oldIndex\];
        data\_.insert(data\_.begin() + newIndex, temp);
        data\_.erase(data\_.begin() + oldIndex);
        // 移到位置如果未发生可视区域内元素的可见性变化，则不回调事件，反之根据新增和删除场景回调对应的事件。
        OH\_ArkUI\_NodeAdapter\_MoveItem(adapter\_, oldIndex, newIndex);
    }

    void ReloadItem(int32\_t index, const std::string &value)
    {
        data\_\[index\] = value;
        // 如果index位于可视区域内，先回调NODE\_ADAPTER\_EVENT\_ON\_REMOVE\_NODE\_FROM\_ADAPTER删除老元素，
        // 再回调NODE\_ADAPTER\_EVENT\_ON\_GET\_NODE\_ID和NODE\_ADAPTER\_EVENT\_ON\_ADD\_NODE\_TO\_ADAPTER事件。
        OH\_ArkUI\_NodeAdapter\_ReloadItem(adapter\_, index, 1);
    }

    void ReloadAllItem()
    {
        std::reverse(data\_.begin(), data\_.end());
        // 全部重新加载场景下，会回调NODE\_ADAPTER\_EVENT\_ON\_GET\_NODE\_ID接口获取新的组件ID，
        // 根据新的组件ID进行对比，ID不发生变化的进行复用，
        // 针对新增ID的元素，调用NODE\_ADAPTER\_EVENT\_ON\_ADD\_NODE\_TO\_ADAPTER事件创建新的组件，
        // 然后判断老数据中遗留的未使用ID，调用NODE\_ADAPTER\_EVENT\_ON\_REMOVE\_NODE\_FROM\_ADAPTER删除老元素。
        OH\_ArkUI\_NodeAdapter\_ReloadAllItems(adapter\_);
    }

private:
    static void OnStaticAdapterEvent(ArkUI\_NodeAdapterEvent \*event)
    {
        // 获取实例对象，回调实例事件。
        auto itemAdapter = reinterpret\_cast<FlowItemAdapter \*>(OH\_ArkUI\_NodeAdapterEvent\_GetUserData(event));
        itemAdapter->OnAdapterEvent(event);
    }

    void OnAdapterEvent(ArkUI\_NodeAdapterEvent \*event)
    {
        auto type = OH\_ArkUI\_NodeAdapterEvent\_GetType(event);
        switch (type) {
            case NODE\_ADAPTER\_EVENT\_ON\_GET\_NODE\_ID:
                OnGetChildId(event);
                break;
            case NODE\_ADAPTER\_EVENT\_ON\_ADD\_NODE\_TO\_ADAPTER:
                OnCreateNewChild(event);
                break;
            case NODE\_ADAPTER\_EVENT\_ON\_REMOVE\_NODE\_FROM\_ADAPTER:
                OnDisposeChild(event);
                break;
            default:
                break;
        }
    }
    
    void OnGetChildId(ArkUI\_NodeAdapterEvent \*event)
    {
        auto index = OH\_ArkUI\_NodeAdapterEvent\_GetItemIndex(event);
        // 设置生成组件的唯一标识符。
        auto hash = std::hash<std::string>();
        OH\_ArkUI\_NodeAdapterEvent\_SetNodeId(event, hash(data\_\[index\]));
    }
    
    void OnCreateNewChild(ArkUI\_NodeAdapterEvent \*event)
    {
        auto index = OH\_ArkUI\_NodeAdapterEvent\_GetItemIndex(event);
        ArkUI\_NodeHandle flowItem = nullptr;
        if (!cachedItems\_.empty()) {
            // 复用缓存
            flowItem = cachedItems\_.top();
            cachedItems\_.pop();
            // 更新数据
            auto \*text = nodeApi\_->getFirstChild(flowItem);
            ArkUI\_AttributeItem item{nullptr, 0, data\_\[index\].c\_str()};
            nodeApi\_->setAttribute(text, NODE\_TEXT\_CONTENT, &item);
        } else {
            // 重新创建。
            auto \*text = nodeApi\_->createNode(ARKUI\_NODE\_TEXT);
            ArkUI\_AttributeItem item{nullptr, 0, data\_\[index\].c\_str()};
            nodeApi\_->setAttribute(text, NODE\_TEXT\_CONTENT, &item);
            flowItem = nodeApi\_->createNode(ARKUI\_NODE\_FLOW\_ITEM);
            ArkUI\_NumberValue value\[\] = {100};
            ArkUI\_AttributeItem height{value, 1};
            nodeApi\_->setAttribute(flowItem, NODE\_HEIGHT, &height);
            value\[0\] = {1};
            ArkUI\_AttributeItem width{value, 1};
            nodeApi\_->setAttribute(flowItem, NODE\_WIDTH\_PERCENT, &width);
            value\[0\] = {.u32 = 0xFFD3D3D3};
            ArkUI\_AttributeItem backgroundColor{value, 1};

            nodeApi\_->setAttribute(flowItem, NODE\_BACKGROUND\_COLOR, &backgroundColor);
            nodeApi\_->addChild(flowItem, text);
        }
        OH\_ArkUI\_NodeAdapterEvent\_SetItem(event, flowItem);
    }
    
    void OnDisposeChild(ArkUI\_NodeAdapterEvent \*event)
    {
        auto \*node = OH\_ArkUI\_NodeAdapterEvent\_GetRemovedNode(event);
        // 缓存节点
        cachedItems\_.emplace(node);
    }

    std::vector<std::string> data\_;
    ArkUI\_NativeNodeAPI\_1 \*nodeApi\_ = nullptr;
    ArkUI\_NodeAdapterHandle adapter\_ = nullptr;

    // 管理回收复用组件池。
    std::stack<ArkUI\_NodeHandle> cachedItems\_;
};

} // namespace NativeModule

#endif // MYAPPLICATION\_FLOWITEMADAPTER\_H

#### 创建分组

使用WaterflowSection类管理[WaterFlow](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-waterflow)中的分组，其中SectionOption用于描述一个分段的各项配置信息。在类的构造函数中创建[ArkUI\_WaterFlowSectionOption](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-arkui-nativemodule-arkui-waterflowsectionoption)对象，在析构函数中将其销毁。

// WaterflowSection.h

#ifndef MYAPPLICATION\_WATERFLOWSECTION\_H
#define MYAPPLICATION\_WATERFLOWSECTION\_H

#include <arkui/native\_node.h>
#include <hilog/log.h>

namespace NativeModule {

struct SectionOption {
    int32\_t itemsCount = 0;
    int32\_t crossCount;
    float columnsGap;
    float rowsGap;
    // {上外边距，右外边距，下外边距，左外边距}
    ArkUI\_Margin margin{0, 0, 0, 0};
    float (\*onGetItemMainSizeByIndex)(int32\_t itemIndex);
    void \*userData;
};

class WaterflowSection {
public:
    WaterflowSection() : sectionOptions\_(OH\_ArkUI\_WaterFlowSectionOption\_Create()){};
    
    ~WaterflowSection()
    {
        OH\_ArkUI\_WaterFlowSectionOption\_Dispose(sectionOptions\_);
    }

    void SetSection(ArkUI\_WaterFlowSectionOption \*sectionOptions, int32\_t index, SectionOption section)
    {
        OH\_ArkUI\_WaterFlowSectionOption\_SetItemCount(sectionOptions, index, section.itemsCount);
        OH\_ArkUI\_WaterFlowSectionOption\_SetCrossCount(sectionOptions, index, section.crossCount);
        OH\_ArkUI\_WaterFlowSectionOption\_SetColumnGap(sectionOptions, index, section.columnsGap);
        OH\_ArkUI\_WaterFlowSectionOption\_SetRowGap(sectionOptions, index, section.rowsGap);
        OH\_ArkUI\_WaterFlowSectionOption\_SetMargin(sectionOptions, index, section.margin.top, section.margin.right,
                                                  section.margin.bottom, section.margin.left);
        OH\_ArkUI\_WaterFlowSectionOption\_RegisterGetItemMainSizeCallbackByIndex(sectionOptions, index,
                                                                               section.onGetItemMainSizeByIndex);
    }
    
    ArkUI\_WaterFlowSectionOption \*GetSectionOptions() const
    {
        return sectionOptions\_;
    }
    
    void PrintSectionOptions()
    {
        int32\_t sectionCnt = OH\_ArkUI\_WaterFlowSectionOption\_GetSize(sectionOptions\_);
        for (int32\_t i = 0; i < sectionCnt; i++) {
            ArkUI\_Margin margin = OH\_ArkUI\_WaterFlowSectionOption\_GetMargin(sectionOptions\_, i);
            OH\_LOG\_Print(LOG\_APP, LOG\_INFO, LOG\_DOMAIN, "CreateWaterflowExample",
                         "Section\[%{public}d\].margin:{%{public}f, %{public}f, %{public}f, %{public}f}", i, margin.top,
                         margin.right, margin.bottom, margin.left);
        }
    }

private:
    ArkUI\_WaterFlowSectionOption \*sectionOptions\_ = nullptr;
};
} // namespace NativeModule

#endif // MYAPPLICATION\_WATERFLOWSECTION\_H

#### 创建瀑布流

使用ArkUIWaterflowNode类管理Waterflow。支持通过SetLazyAdapter为其设置一个FlowItemAdapter，通过SetSection为其设置分段。

// waterflow.h
#ifndef MYAPPLICATION\_WATERFLOW\_H
#define MYAPPLICATION\_WATERFLOW\_H
  
#include "FlowItemAdapter.h"
#include "WaterflowSection.h"
#include "ArkUINode.h"
  
namespace NativeModule {
  
class ArkUIWaterflowNode : public ArkUINode {
public:
    ArkUIWaterflowNode()
        : ArkUINode(CreateWaterflowNode()) {}
  
    ~ArkUIWaterflowNode() override
    {
        // 先卸载 adapter
        if (adapter\_ && nativeModule\_) {
            nativeModule\_->resetAttribute(handle\_, NODE\_WATER\_FLOW\_NODE\_ADAPTER);
            adapter\_.reset();
        }
        // 销毁分段
        section\_.reset();
        // 基类会自动 dispose handle\_
    }

    void SetLazyAdapter(const std::shared\_ptr<FlowItemAdapter> &adapter)
    {
        ArkUI\_AttributeItem item{nullptr, 0, nullptr, adapter->GetAdapter()};
        nativeModule\_->setAttribute(handle\_, NODE\_WATER\_FLOW\_NODE\_ADAPTER, &item);
        adapter\_ = adapter;
    }
  
    void SetSection(const std::shared\_ptr<WaterflowSection> &section)
    {
        if (!section->GetSectionOptions()) {
            return;
        }
        ArkUI\_NumberValue start\[\] = {{.i32 = 0}};
        ArkUI\_AttributeItem optionsItem = {start, 1, nullptr, section->GetSectionOptions()};
        nativeModule\_->setAttribute(handle\_, NODE\_WATER\_FLOW\_SECTION\_OPTION, &optionsItem);
        section\_ = section;
    }
  
    std::shared\_ptr<WaterflowSection> GetWaterflowSection() { return section\_; }
  
private:
    static ArkUI\_NodeHandle CreateWaterflowNode()
    {
        ArkUI\_NativeNodeAPI\_1\* api = nullptr;
        OH\_ArkUI\_GetModuleInterface(ARKUI\_NATIVE\_NODE, ArkUI\_NativeNodeAPI\_1, api);
        if (!api) {
            return nullptr;
        }
        return api->createNode(ARKUI\_NODE\_WATER\_FLOW);
    }
  
    std::shared\_ptr<WaterflowSection> section\_ = nullptr;
    std::shared\_ptr<FlowItemAdapter> adapter\_;
};
  
} // namespace NativeModule
  
#endif // MYAPPLICATION\_WATERFLOW\_H

#### 使用瀑布流

创建一个ArkUIWaterflowNode类的实例，设置其宽高，并绑定NodeAdapter和分段。

// CreateWaterflowExample.h

#ifndef MYAPPLICATION\_CREATEWATERFLOWEXAMPLE\_H
#define MYAPPLICATION\_CREATEWATERFLOWEXAMPLE\_H
#include "waterflow.h"

namespace NativeModule {
const int UI\_WIDTH = 400;
const int UI\_HEIGHT = 600;
const int SECTION\_COUNT = 10;
const int SECTION\_2\_ID = 2;

inline void SetupSections(std::shared\_ptr<WaterflowSection> sections)
{
    SectionOption MARGIN\_GAP\_SECTION\_1 = {10, 2, 10, 10, {20, 30, 40, 50}, nullptr, nullptr};
    SectionOption MARGIN\_GAP\_SECTION\_2 = {10, 4, 10, 10, {20, 30, 40, 50}, nullptr, nullptr};
    for (int i = 0; i < SECTION\_COUNT; i++) {
        sections->SetSection(sections->GetSectionOptions(), i,
                             i % SECTION\_2\_ID ? MARGIN\_GAP\_SECTION\_1 : MARGIN\_GAP\_SECTION\_2);
    }
}

inline std::shared\_ptr<ArkUIWaterflowNode> CreateWaterflowExample(napi\_env env)
{
    auto waterflow = std::make\_shared<ArkUIWaterflowNode>();
    waterflow->SetHeight(UI\_HEIGHT);
    waterflow->SetWidth(UI\_WIDTH);
    waterflow->SetLazyAdapter(std::make\_shared<FlowItemAdapter>());
    auto sections = std::make\_shared<WaterflowSection>();
    SetupSections(sections);
    waterflow->SetSection(sections);
    return waterflow;
}
} // namespace NativeModule

#endif // MYAPPLICATION\_CREATEWATERFLOWEXAMPLE\_H

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/09/v3/PEDOkgUnSciYxmKu1jdlCw/zh-cn_image_0000002594668262.gif?HW-CC-KV=V1&HW-CC-Date=20260612T073315Z&HW-CC-Expire=86400&HW-CC-Sign=073DB0D0781002977452C777C56AD3A68B81E757FD6B9D8D3370DB5A36305560)