• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Implementing a Waterfall Flow Layout
2
3The ArkUI framework provides a waterfall flow container component through NDK APIs. This component arranges items of different sizes in a waterfall-like manner from top to bottom.
4
5## Integrating with ArkTS Pages
6To use the NDK APIs for building UIs, follow the instructions in [Integrating with ArkTS Pages](../ui/ndk-access-the-arkts-page.md). This involves creating a placeholder component on the ArkTS page for mounting the native page and implementing the **NativeNode** module APIs on the ArkTS side.
7
8## Implementing Lazy Loading
9### NodeAdapter Overview
10The NDK provides the **NodeAdapter** object as an alternative to the **LazyForEach** functionality in ArkTS for on-demand generation of child components. For details, see [NodeAdapter Overview](../ui/ndk-loading-long-list.md#nodeadapter-overview).
11
12### Lazy Loading Adapter Implementation
13
14Use the **FlowItemAdapter** class to manage the lazy loading adapter. Create a **NodeAdapter** object in the class constructor with event listeners, and destroy it in the class destructor.
15
16```c++
17// FlowItemAdapter.h
18// Lazy loading implementation
19
20#ifndef MYAPPLICATION_FLOWITEMADAPTER_H
21#define MYAPPLICATION_FLOWITEMADAPTER_H
22
23#include <arkui/native_node.h>
24#include <stack>
25#include <string>
26#include <unordered_set>
27#include <arkui/native_interface.h>
28
29namespace NativeModule {
30
31class FlowItemAdapter {
32public:
33    FlowItemAdapter(){
34
35        // Initialize the function pointer structure.
36        OH_ArkUI_GetModuleInterface(ARKUI_NATIVE_NODE, ArkUI_NativeNodeAPI_1, nodeApi_);
37        // Create a NodeAdapter object.
38        adapter_ = OH_ArkUI_NodeAdapter_Create();
39
40        // Initialize lazy loading data.
41        for (int32_t i = 0; i < 100; i++) {
42            data_.emplace_back(std::to_string(i));
43        }
44        // Set the total number of items for lazy loading.
45        OH_ArkUI_NodeAdapter_SetTotalNodeCount(adapter_, data_.size());
46        // Set the event listener.
47        OH_ArkUI_NodeAdapter_RegisterEventReceiver(adapter_, this, OnStaticAdapterEvent);
48    }
49
50    ~FlowItemAdapter() {
51        // Release created components.
52        while (!cachedItems_.empty()) {
53            cachedItems_.pop();
54        }
55        // Release adapter resources.
56        OH_ArkUI_NodeAdapter_UnregisterEventReceiver(adapter_);
57        OH_ArkUI_NodeAdapter_Dispose(adapter_);
58    }
59
60    ArkUI_NodeAdapterHandle GetAdapter() const { return adapter_; }
61
62    void RemoveItem(int32_t index) {
63        // Remove the item at the specified index.
64        data_.erase(data_.begin() + index);
65        // If the index change affects the visibility of items in the visible area, the NODE_ADAPTER_EVENT_ON_REMOVE_NODE_FROM_ADAPTER event will be triggered to remove the element.
66        // If items are added, the NODE_ADAPTER_EVENT_ON_GET_NODE_ID and NODE_ADAPTER_EVENT_ON_ADD_NODE_TO_ADAPTER events will be triggered accordingly.
67        OH_ArkUI_NodeAdapter_RemoveItem(adapter_, index, 1);
68        // Update the total count.
69        OH_ArkUI_NodeAdapter_SetTotalNodeCount(adapter_, data_.size());
70    }
71
72    void InsertItem(int32_t index, const std::string &value) {
73        data_.insert(data_.begin() + index, value);
74        // If the index change affects the visibility of elements in the visible area, the NODE_ADAPTER_EVENT_ON_GET_NODE_ID and NODE_ADAPTER_EVENT_ON_ADD_NODE_TO_ADAPTER events will be triggered.
75        // If items are removed, the NODE_ADAPTER_EVENT_ON_REMOVE_NODE_FROM_ADAPTER event will be triggered accordingly.
76        OH_ArkUI_NodeAdapter_InsertItem(adapter_, index, 1);
77        // Update the total count.
78        OH_ArkUI_NodeAdapter_SetTotalNodeCount(adapter_, data_.size());
79    }
80
81    void MoveItem(int32_t oldIndex, int32_t newIndex) {
82        auto temp = data_[oldIndex];
83        data_.insert(data_.begin() + newIndex, temp);
84        data_.erase(data_.begin() + oldIndex);
85        // If the move changes the visibility of items within the visible area, the corresponding events will be triggered.
86        OH_ArkUI_NodeAdapter_MoveItem(adapter_, oldIndex, newIndex);
87    }
88
89    void ReloadItem(int32_t index, const std::string &value) {
90        data_[index] = value;
91        // If the index is within the visible area, first trigger the NODE_ADAPTER_EVENT_ON_REMOVE_NODE_FROM_ADAPTER event to remove the old item,
92        // then trigger the NODE_ADAPTER_EVENT_ON_GET_NODE_ID and NODE_ADAPTER_EVENT_ON_ADD_NODE_TO_ADAPTER events.
93        OH_ArkUI_NodeAdapter_ReloadItem(adapter_, index, 1);
94    }
95
96    void ReloadAllItem() {
97        std::reverse(data_.begin(), data_.end());
98        // In the scenario where all items are reloaded, the NODE_ADAPTER_EVENT_ON_GET_NODE_ID event will be triggered to obtain new component IDs,
99        // compare the new component IDs, and reuse those whose IDs have not changed;
100        // for items with new IDs, trigger the NODE_ADAPTER_EVENT_ON_ADD_NODE_TO_ADAPTER event to create new components,
101        // then identify any unused IDs from the old data and call NODE_ADAPTER_EVENT_ON_REMOVE_NODE_FROM_ADAPTER to remove the old items.
102        OH_ArkUI_NodeAdapter_ReloadAllItems(adapter_);
103    }
104
105private:
106    static void OnStaticAdapterEvent(ArkUI_NodeAdapterEvent *event) {
107        // Obtain the instance object and invoke the instance event callback.
108        auto itemAdapter = reinterpret_cast<FlowItemAdapter *>(OH_ArkUI_NodeAdapterEvent_GetUserData(event));
109        itemAdapter->OnAdapterEvent(event);
110    }
111
112    void OnAdapterEvent(ArkUI_NodeAdapterEvent *event) {
113        auto type = OH_ArkUI_NodeAdapterEvent_GetType(event);
114        switch (type) {
115        case NODE_ADAPTER_EVENT_ON_GET_NODE_ID:
116            OnGetChildId(event);
117            break;
118        case NODE_ADAPTER_EVENT_ON_ADD_NODE_TO_ADAPTER:
119            OnCreateNewChild(event);
120            break;
121        case NODE_ADAPTER_EVENT_ON_REMOVE_NODE_FROM_ADAPTER:
122            OnDisposeChild(event);
123            break;
124        default:
125            break;
126        }
127    }
128
129    void OnGetChildId(ArkUI_NodeAdapterEvent *event) {
130        auto index = OH_ArkUI_NodeAdapterEvent_GetItemIndex(event);
131        // Set the unique identifier for the generated component.
132        auto hash = std::hash<std::string>();
133        OH_ArkUI_NodeAdapterEvent_SetNodeId(event, hash(data_[index]));
134    }
135
136    void OnCreateNewChild(ArkUI_NodeAdapterEvent *event) {
137        auto index = OH_ArkUI_NodeAdapterEvent_GetItemIndex(event);
138        ArkUI_NodeHandle flowItem = nullptr;
139        if (!cachedItems_.empty()) {
140            // Reuse cached items.
141            flowItem = cachedItems_.top();
142            cachedItems_.pop();
143            // Update data.
144            auto *text = nodeApi_->getFirstChild(flowItem);
145            ArkUI_AttributeItem item{nullptr, 0, data_[index].c_str()};
146            nodeApi_->setAttribute(text, NODE_TEXT_CONTENT, &item);
147        } else {
148            // Create an item.
149            auto *text = nodeApi_->createNode(ARKUI_NODE_TEXT);
150            ArkUI_AttributeItem item{nullptr, 0, data_[index].c_str()};
151            nodeApi_->setAttribute(text, NODE_TEXT_CONTENT, &item);
152            flowItem = nodeApi_->createNode(ARKUI_NODE_FLOW_ITEM);
153            ArkUI_NumberValue value[] = {100};
154            ArkUI_AttributeItem height{value, 1};
155            nodeApi_->setAttribute(flowItem, NODE_HEIGHT, &height);
156            value[0] = {1};
157            ArkUI_AttributeItem width{value, 1};
158            nodeApi_->setAttribute(flowItem, NODE_WIDTH_PERCENT, &width);
159            value[0] = {.u32 = 0xFFFF0000};
160            ArkUI_AttributeItem backgroundColor{value, 1};
161
162            nodeApi_->setAttribute(flowItem, NODE_BACKGROUND_COLOR, &backgroundColor);
163            nodeApi_->addChild(flowItem, text);
164        }
165        OH_ArkUI_NodeAdapterEvent_SetItem(event, flowItem);
166    }
167
168    void OnDisposeChild(ArkUI_NodeAdapterEvent *event) {
169        auto *node = OH_ArkUI_NodeAdapterEvent_GetRemovedNode(event);
170        // Cache the node.
171        cachedItems_.emplace(node);
172    }
173
174    std::vector<std::string> data_;
175    ArkUI_NativeNodeAPI_1 *nodeApi_ = nullptr;
176    ArkUI_NodeAdapterHandle adapter_ = nullptr;
177
178    // Manage the component reuse pool.
179    std::stack<ArkUI_NodeHandle> cachedItems_;
180};
181
182} // namespace NativeModule
183
184#endif //MYAPPLICATION_FLOWITEMADAPTER_H
185
186```
187## Creating a Section
188Implement the **WaterflowSection** class to manage grouping within WaterFlow components, where **SectionOption** describes the configuration parameters for each section. In the class constructor, create an **ArkUI_WaterFlowSectionOption** object, which is destroyed in the destructor.
189
190```c++
191//WaterflowSection.h
192
193#ifndef MYAPPLICATION_WATERFLOWSECTION_H
194#define MYAPPLICATION_WATERFLOWSECTION_H
195
196#include <arkui/native_node.h>
197#include <hilog/log.h>
198
199namespace NativeModule {
200
201struct SectionOption {
202    int32_t itemsCount = 0;
203    int32_t crossCount;
204    float columnsGap;
205    float rowsGap;
206    // top right bottom left
207    ArkUI_Margin margin{0, 0, 0, 0};
208    float (*onGetItemMainSizeByIndex)(int32_t itemIndex);
209    void *userData;
210};
211
212class WaterflowSection {
213public:
214    WaterflowSection() : sectionOptions_(OH_ArkUI_WaterFlowSectionOption_Create()){};
215
216    ~WaterflowSection(){
217        OH_ArkUI_WaterFlowSectionOption_Dispose(sectionOptions_);
218    }
219
220    void SetSection(ArkUI_WaterFlowSectionOption *sectionOptions, int32_t index, SectionOption section) {
221        OH_ArkUI_WaterFlowSectionOption_SetItemCount(sectionOptions, index, section.itemsCount);
222        OH_ArkUI_WaterFlowSectionOption_SetCrossCount(sectionOptions, index, section.crossCount);
223        OH_ArkUI_WaterFlowSectionOption_SetColumnGap(sectionOptions, index, section.columnsGap);
224        OH_ArkUI_WaterFlowSectionOption_SetRowGap(sectionOptions, index, section.rowsGap);
225        OH_ArkUI_WaterFlowSectionOption_SetMargin(sectionOptions, index, section.margin.top, section.margin.right,
226                                                  section.margin.bottom, section.margin.left);
227        OH_ArkUI_WaterFlowSectionOption_RegisterGetItemMainSizeCallbackByIndex(sectionOptions, index,
228                                                                               section.onGetItemMainSizeByIndex);
229    }
230
231    ArkUI_WaterFlowSectionOption *GetSectionOptions() const {
232        return sectionOptions_;
233    }
234
235    void PrintSectionOptions() {
236        int32_t sectionCnt = OH_ArkUI_WaterFlowSectionOption_GetSize(sectionOptions_);
237        for (int32_t i = 0; i < sectionCnt; i++) {
238            ArkUI_Margin margin = OH_ArkUI_WaterFlowSectionOption_GetMargin(sectionOptions_, i);
239            OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "CreateWaterflowExample",
240                        "Section[%{public}d].margin:{%{public}f, %{public}f, %{public}f, %{public}f}", i, margin.top,
241                        margin.right, margin.bottom, margin.left);
242        }
243    }
244
245private:
246    ArkUI_WaterFlowSectionOption *sectionOptions_ = nullptr;
247};
248} // namespace NativeModule
249
250#endif // MYAPPLICATION_WATERFLOWSECTION_H
251
252```
253
254## Creating a WaterFlow Component
255Implement the **ArkUIWaterflowNode** class to manage **WaterFlow** components. This class supports configuration through **SetLazyAdapter** for assigning a **FlowItemAdapter** and **SetSection** for defining sections.
256
257```c++
258//Waterflow.h
259
260#ifndef MYAPPLICATION_WATERFLOWE_H
261#define MYAPPLICATION_WATERFLOWE_H
262
263#include "FlowItemAdapter.h"
264#include "WaterflowSection.h"
265
266namespace NativeModule {
267class ArkUIWaterflowNode {
268public:
269
270    ArkUIWaterflowNode() {
271
272        OH_ArkUI_GetModuleInterface(ARKUI_NATIVE_NODE, ArkUI_NativeNodeAPI_1, nodeApi_);
273        // Create a Waterflow component.
274        waterflow_ = nodeApi_->createNode(ARKUI_NODE_WATER_FLOW);
275
276    }
277
278    ~ArkUIWaterflowNode() {
279        nodeApi_->disposeNode(waterflow_);
280
281        // Destroy the adapter.
282        adapter_.reset();
283
284        // Destroy sections.
285        section_.reset();
286    }
287
288    void SetWidth(float width) {
289        ArkUI_NumberValue value[] = {{.f32 = width}};
290        ArkUI_AttributeItem item = {value, 1};
291        nodeApi_->setAttribute(waterflow_, NODE_WIDTH, &item);
292    }
293
294    void SetHeight(float height) {
295        ArkUI_NumberValue value[] = {{.f32 = height}};
296        ArkUI_AttributeItem item = {value, 1};
297        nodeApi_->setAttribute(waterflow_, NODE_HEIGHT, &item);
298    }
299
300    void SetLazyAdapter(const std::shared_ptr<FlowItemAdapter> &adapter) {
301        ArkUI_AttributeItem item{nullptr,0, nullptr, adapter->GetAdapter()};
302        nodeApi_->setAttribute(waterflow_, NODE_WATER_FLOW_NODE_ADAPTER, &item);
303        adapter_ = adapter;
304    }
305
306    void SetSection(const std::shared_ptr<WaterflowSection> &section) {
307        ArkUI_NumberValue start[] = {{.i32 = 0}};
308        ArkUI_AttributeItem optionsItem = {start, 1, nullptr, section->GetSectionOptions()};
309        if(!section->GetSectionOptions()){
310            return;
311        }
312        nodeApi_->setAttribute(waterflow_, NODE_WATER_FLOW_SECTION_OPTION, &optionsItem);
313        section_ = section;
314    }
315
316    ArkUI_NodeHandle GetWaterflow() { return waterflow_; }
317
318    std::shared_ptr<WaterflowSection> GetWaterflowSection() { return section_; }
319
320public:
321    ArkUI_NativeNodeAPI_1 *nodeApi_ = nullptr;
322    ArkUI_NodeHandle waterflow_ = nullptr;
323
324    std::shared_ptr<WaterflowSection> section_ = nullptr;
325
326    std::shared_ptr<FlowItemAdapter> adapter_;
327};
328}// namespace NativeModule
329
330#endif // MYAPPLICATION_WATERFLOWE_H
331```
332
333## Implementing a WaterFlow Component
334Create an instance of the **ArkUIWaterflowNode** class, set its width and height, and bind the **NodeAdapter** and sections.
335
336```c++
337// CreateWaterflowExample.h
338
339#ifndef MYAPPLICATION_CREATEWATERFLOWEXAMPLE_H
340#define MYAPPLICATION_CREATEWATERFLOWEXAMPLE_H
341#include "waterflow.h"
342
343namespace NativeModule {
344std::shared_ptr<ArkUIWaterflowNode> CreateWaterflowExample() {
345    // Create a WaterFlow component.
346    auto waterflow = std::make_shared<ArkUIWaterflowNode>();
347    waterflow->SetHeight(600);
348    waterflow->SetWidth(400);
349
350    // Configure the adapter.
351    waterflow->SetLazyAdapter(std::make_shared<FlowItemAdapter>());
352
353    // Set sections.
354    auto sections = std::make_shared<WaterflowSection>();
355    SectionOption MARGIN_GAP_SECTION_1 = {10, 2, 10, 10, margin : {20, 30, 40, 50}, nullptr, nullptr};
356    SectionOption MARGIN_GAP_SECTION_2 = {10, 4, 10, 10, margin : {20, 30, 40, 50}, nullptr, nullptr};
357    for (int i = 0; i < 10; i++) {
358        sections->SetSection(sections->GetSectionOptions(), i, i % 2 ? MARGIN_GAP_SECTION_1 : MARGIN_GAP_SECTION_2);
359    }
360    waterflow->SetSection(sections);
361
362    return waterflow;
363}
364} // namespace NativeModule
365
366#endif // MYAPPLICATION_CREATEWATERFLOWEXAMPLE_H
367
368```
369