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> §ion) { 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