harmony 鸿蒙Developing a Long List with Lazy Loading

  • 2025-06-12
  • 浏览 (5)

Developing a Long List with Lazy Loading

For the List, Grid, WaterFlow, and Swiper components, the NodeAdapter object is provided as an alternative to the ArkTS LazyForEach feature for on-demand child component generation. The specific attribute enumeration values are as follows:

  • List: NODE_LIST_NODE_ADAPTER
  • Grid: NODE_GRID_NODE_ADAPTER
  • WaterFlow: NODE_WATER_FLOW_NODE_ADAPTER
  • Swiper: NODE_SWIPER_NODE_ADAPTER

Key specifications of NodeAdapter include:

  • Nodes with NodeAdapter set do not support direct child addition APIs like addChild. Child components are managed entirely by NodeAdapter. If a parent component already has child nodes, setting NodeAdapter will fail and return an error code.

  • NodeAdapter uses events to notify you to generate components on demand. You must register an event listener and handle logic within listener events, which are defined by ArkUI_NodeAdapterEventType. NodeAdapter does not automatically release off-screen component objects; you must manage object release or caching during the NODE_ADAPTER_EVENT_ON_REMOVE_NODE_FROM_ADAPTER event. The following image illustrates the event triggering mechanism in a typical list scrolling scenario. en-us_image_0000001949769409

The following example optimizes the code in the Integrating with ArkTS Pages section, by introducing a lazy loading mechanism for a text list:

  1. Integrate ArkTS into your project. For details, see Integrating with ArkTS Pages.

  2. Implement lazy loading adapter functionality. “` // ArkUIListItemAdapter // Code for lazy loading functionality in a text list.

#ifndef MYAPPLICATION_ARKUILISTITEMADAPTER_H #define MYAPPLICATION_ARKUILISTITEMADAPTER_H

#include #include #include #include

#include “ArkUIListItemNode.h” #include “ArkUITextNode.h” #include “nativeModule.h”

namespace NativeModule {

class ArkUIListItemAdapter { public: ArkUIListItemAdapter() : module(NativeModuleInstance::GetInstance()->GetNativeNodeAPI()), handle(OH_ArkUI_NodeAdapter_Create()) { // Use the NodeAdapter creation function. // Initialize lazy loading data. for (int32t i = 0; i < 1000; i++) { data.emplace_back(std::to_string(i)); } // Set lazy loading data. OH_ArkUI_NodeAdapterSetTotalNodeCount(handle, data_.size()); // Register the event receiver for lazy loading. OH_ArkUI_NodeAdapterRegisterEventReceiver(handle, this, OnStaticAdapterEvent); }

   ~ArkUIListItemAdapter() {
       // Release created components.
       while (!cachedItems_.empty()) {
           cachedItems_.pop();
       }
       items_.clear();
       // Release adapter resources.
       OH_ArkUI_NodeAdapter_UnregisterEventReceiver(handle_);
       OH_ArkUI_NodeAdapter_Dispose(handle_);
   }

   ArkUI_NodeAdapterHandle GetHandle() const { return handle_; }

   void RemoveItem(int32_t index) {
       // Remove the item at the specified index.
       data_.erase(data_.begin() + index);
       // 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.
       // 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.
       OH_ArkUI_NodeAdapter_RemoveItem(handle_, index, 1);
       // Update the new total count.
       OH_ArkUI_NodeAdapter_SetTotalNodeCount(handle_, data_.size());
   }

   void InsertItem(int32_t index, const std::string &value) {
       data_.insert(data_.begin() + index, value);
       // 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.
       // If items are removed, the NODE_ADAPTER_EVENT_ON_REMOVE_NODE_FROM_ADAPTER event will be triggered accordingly.
       OH_ArkUI_NodeAdapter_InsertItem(handle_, index, 1);
       // Update the new total count.
       OH_ArkUI_NodeAdapter_SetTotalNodeCount(handle_, 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);
       // If the move changes the visibility of items within the visible area, the corresponding events will be triggered.
       OH_ArkUI_NodeAdapter_MoveItem(handle_, oldIndex, newIndex);
   }

   void ReloadItem(int32_t index, const std::string &value) {
       data_[index] = value;
       // 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,
       // then trigger the NODE_ADAPTER_EVENT_ON_GET_NODE_ID and NODE_ADAPTER_EVENT_ON_ADD_NODE_TO_ADAPTER events.
       OH_ArkUI_NodeAdapter_ReloadItem(handle_, index, 1);
   }

   void ReloadAllItem() {
       std::reverse(data_.begin(), data_.end());
       // 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,
       // compare the new component IDs, and reuse those whose IDs have not changed,
       // for items with new IDs, trigger the NODE_ADAPTER_EVENT_ON_ADD_NODE_TO_ADAPTER event to create new components,
       // then identify any unused IDs from the old data and call NODE_ADAPTER_EVENT_ON_REMOVE_NODE_FROM_ADAPTER to remove the old items.
       OH_ArkUI_NodeAdapter_ReloadAllItems(handle_);
   }

private: static void OnStaticAdapterEvent(ArkUI_NodeAdapterEvent *event) { // Obtain the instance object and invoke its event callback. auto itemAdapter = reinterpret_cast(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:
           OnNewItemIdCreated(event);
           break;
       case NODE_ADAPTER_EVENT_ON_ADD_NODE_TO_ADAPTER:
           OnNewItemAttached(event);
           break;
       case NODE_ADAPTER_EVENT_ON_REMOVE_NODE_FROM_ADAPTER:
           OnItemDetached(event);
           break;
       default:
           break;
       }
   }

   // Assign IDs to items that need to be displayed, used for element diffing in the ReloadAllItems scenario.
   void OnNewItemIdCreated(ArkUI_NodeAdapterEvent *event) {
       auto index = OH_ArkUI_NodeAdapterEvent_GetItemIndex(event);
       static std::hash<std::string> hashId = std::hash<std::string>();
       auto id = hashId(data_[index]);
       OH_ArkUI_NodeAdapterEvent_SetNodeId(event, id);
   }

   // Handle the display of new items in the visible area.
   void OnNewItemAttached(ArkUI_NodeAdapterEvent *event) {
       auto index = OH_ArkUI_NodeAdapterEvent_GetItemIndex(event);
       ArkUI_NodeHandle handle = nullptr;
       if (!cachedItems_.empty()) {
           // Use and update the recycled item from the cache.
           auto recycledItem = cachedItems_.top();
           auto textItem = std::dynamic_pointer_cast<ArkUITextNode>(recycledItem->GetChildren().back());
           textItem->SetTextContent(data_[index]);
           handle = recycledItem->GetHandle();
           // Release the reference from the cache.
           cachedItems_.pop();
       } else {
           // Create a new item.
           auto listItem = std::make_shared<ArkUIListItemNode>();
           auto textNode = std::make_shared<ArkUITextNode>();
           textNode->SetTextContent(data_[index]);
           textNode->SetFontSize(16);
           textNode->SetPercentWidth(1);
           textNode->SetHeight(100);
           textNode->SetBackgroundColor(0xFFfffacd);
           textNode->SetTextAlign(ARKUI_TEXT_ALIGNMENT_CENTER);
           listItem->AddChild(textNode);
           listItem->RegisterOnClick([index]() { OH_LOG_INFO(LOG_APP, "on %{public}d list item click", index); });
           handle = listItem->GetHandle();
           // Keep a reference to the text list item.
           items_.emplace(handle, listItem);
       }
       // Set the item to be displayed.
       OH_ArkUI_NodeAdapterEvent_SetItem(event, handle);
   }

   // Remove an item from the visible area.
   void OnItemDetached(ArkUI_NodeAdapterEvent *event) {
       auto item = OH_ArkUI_NodeAdapterEvent_GetRemovedNode(event);
       // Place the item in the cache pool for recycling and reuse.
       cachedItems_.emplace(items_[item]);
   }


   std::vector<std::string> data_;
   ArkUI_NativeNodeAPI_1 *module_ = nullptr;
   ArkUI_NodeAdapterHandle handle_ = nullptr;

   // Manage items generated by the NodeAdapter.
   std::unordered_map<ArkUI_NodeHandle, std::shared_ptr<ArkUIListItemNode>> items_;

   // Manage the component reuse pool.
   std::stack<std::shared_ptr<ArkUIListItemNode>> cachedItems_;

};

} // namespace NativeModule

#endif // MYAPPLICATION_ARKUILISTITEMADAPTER_H


3. Enhance the encapsulated list class object used in the [Integrating with ArkTS Pages](https://m.seaxiang.com/blog/mlDQQ5) section with additional lazy loading capabilities.

// ArkUIListNode.h // Encapsulated list class object.

#ifndef MYAPPLICATION_ARKUILISTNODE_H #define MYAPPLICATION_ARKUILISTNODE_H

#include “ArkUIListItemAdapter.h” #include “ArkUINode.h” #include

namespace NativeModule { class ArkUIListNode : public ArkUINode { public: ArkUIListNode() : ArkUINode((NativeModuleInstance::GetInstance()->GetNativeNodeAPI())->createNode(ARKUI_NODE_LIST)) {}

   ~ArkUIListNode() override {
       nativeModule_->unregisterNodeEvent(handle_, NODE_LIST_ON_SCROLL_INDEX);
       if (adapter_) {
           // Unmount UI components associated with the adapter upon destruction.
           nativeModule_->resetAttribute(handle_, NODE_LIST_NODE_ADAPTER);
           adapter_.reset();
       }
   }

   void SetScrollBarState(bool isShow) {
       assert(handle_);
       ArkUI_ScrollBarDisplayMode displayMode =
           isShow ? ARKUI_SCROLL_BAR_DISPLAY_MODE_ON : ARKUI_SCROLL_BAR_DISPLAY_MODE_OFF;
       ArkUI_NumberValue value[] = {{.i32 = displayMode}};
       ArkUI_AttributeItem item = {value, 1};
       nativeModule_->setAttribute(handle_, NODE_SCROLL_BAR_DISPLAY_MODE, &item);
   }

   void RegisterOnScrollIndex(const std::function<void(int32_t index)> &onScrollIndex) {
       assert(handle_);
       onScrollIndex_ = onScrollIndex;
       nativeModule_->registerNodeEvent(handle_, NODE_LIST_ON_SCROLL_INDEX, 0, nullptr);
   }
   // Import the lazy loading module.
   void SetLazyAdapter(const std::shared_ptr<ArkUIListItemAdapter> &adapter) {
       assert(handle_);
       ArkUI_AttributeItem item{nullptr, 0, nullptr, adapter->GetHandle()};
       nativeModule_->setAttribute(handle_, NODE_LIST_NODE_ADAPTER, &item);
       adapter_ = adapter;
   }

protected: void OnNodeEvent(ArkUI_NodeEvent *event) override { auto eventType = OH_ArkUI_NodeEvent_GetEventType(event); switch (eventType) { case NODE_LIST_ON_SCROLL_INDEX: { auto index = OH_ArkUI_NodeEventGetNodeComponentEvent(event)->data[0]; if (onScrollIndex) { onScrollIndex_(index.i32); } } default: { } } }

private: std::function onScrollIndex_;

   std::shared_ptr<ArkUIListItemAdapter> adapter_;

}; } // namespace NativeModule

#endif // MYAPPLICATION_ARKUILISTNODE_H


4. Write code for lazy loading of a list.

// ArkUILazyTextListExample // Sample code for lazy loading a list.

#ifndef MYAPPLICATION_LAZYTEXTLISTEXAMPLE_H #define MYAPPLICATION_LAZYTEXTLISTEXAMPLE_H

#include “ArkUIBaseNode.h” #include “ArkUIListNode.h” #include “UITimer.h” #include #include

namespace NativeModule {

std::shared_ptr CreateLazyTextListExample(napi_env env) { // Create components and mount them. // 1: Create a List component. auto list = std::make_shared(); list->SetPercentWidth(1); list->SetPercentHeight(1); // 2: Create ListItem child components for lazy loading and mount them to the List component. auto adapter = std::make_shared(); list->SetLazyAdapter(adapter);

   // 3: Simulate lazy loading operations.
   CreateNativeTimer(env, adapter.get(), 4, [](void *userdata, int32_t count) {
       auto adapter = reinterpret_cast<ArkUIListItemAdapter *>(userdata);
       switch (count) {
       case 0: {
           // Remove the 0th item.
           adapter->RemoveItem(0);
           break;
       }
       case 1: {
           // Insert the 0th item.
           adapter->InsertItem(0, "0");
           break;
       }
       case 2: {
           // Move an item to a new position.
           adapter->MoveItem(0, 2);
           break;
       }
       case 3: {
           // Reload a single item.
           adapter->ReloadItem(0, "1112");
           break;
       }
       case 4: {
           // Reload all items.
           adapter->ReloadAllItem();
           break;
       }
       default: {
       }
       }
   });

   // 3: Register list-related listening events.
   list->RegisterOnScrollIndex([](int32_t index) { OH_LOG_INFO(LOG_APP, "on list scroll index: %{public}d", index); });
   // 4: Register the appear event.
   list->RegisterOnAppear([]() { OH_LOG_INFO(LOG_APP, "on list mount to tree"); });
   // 4: Register the disappear event.
   list->RegisterOnDisappear([]() { OH_LOG_INFO(LOG_APP, "on list unmount from tree"); });
   return list;

} } // namespace NativeModule

#endif // MYAPPLICATION_LAZYTEXTLISTEXAMPLE_H


5. Implement a simple timer module.

// UITimer.h // Timer module.

#ifndef MYAPPLICATION_UITIMER_H #define MYAPPLICATION_UITIMER_H

#include #include #include #include #include #include #include #include

namespace NativeModule {

struct UIData { void *userData = nullptr; int32_t count = 0; int32_t totalCount = 0; void (*func)(void *userData, int32_t count) = nullptr; };

napi_threadsafe_function threadSafeFunction = nullptr;

void CreateNativeTimer(napi_env env, void *userData, int32_t totalCount, void (*func)(void *userData, int32_t count)) { napi_value name; std::string str = “UICallback”; napi_create_string_utf8(env, str.c_str(), str.size(), &name); // UI main thread callback function. napi_create_threadsafe_function( env, nullptr, nullptr, name, 0, 1, nullptr, nullptr, nullptr, [](napi_env env, napi_value value, void *context, void *data) { auto userdata = reinterpret_cast(data); userdata->func(userdata->userData, userdata->count); delete userdata; }, &threadSafeFunction); // Start the timer to simulate data changes. std::thread timerThread([data = userData, totalCount, func]() { uv_loop_t *loop = uv_loop_new(); uv_timer_t *timer = new uv_timer_t(); uv_timer_init(loop, timer); timer->data = new UIData{data, 0, totalCount, func}; uv_timer_start( timer, [](uv_timer_t *handle) { OH_LOG_INFO(LOG_APP, “on timeout”); napi_acquire_threadsafe_function(threadSafeFunction); auto *customData = reinterpret_cast(handle->data); // Create callback data. auto *callbackData = new UIData{customData->userData, customData->count, customData->totalCount, customData->func}; napi_call_threadsafe_function(threadSafeFunction, callbackData, napi_tsfn_blocking); customData->count++; if (customData->count > customData->totalCount) { uv_timer_stop(handle); delete handle; delete customData; } }, 4000, 4000); uv_run(loop, UV_RUN_DEFAULT); uv_loop_delete(loop); }); timerThread.detach(); } } // namespace NativeModule

#endif // MYAPPLICATION_UITIMER_H


6. Mount the lazy loading example code onto the **ContentSlot** as described in the [Integrating with ArkTS Pages](https://m.seaxiang.com/blog/mlDQQ5) section.

// NDK API entry mount file.

#include “NativeEntry.h”

#include “ArkUIMixedRefresh.h” #include “LazyTextListExample.h” #include “MixedRefreshExample.h” #include “TextListExample.h”

#include #include #include #include

namespace NativeModule { namespace { napi_env g_env; }

napi_env GetNapiEnv() { return g_env; }

napi_value CreateNativeRoot(napi_env env, napi_callback_info info) { size_t argc = 1; napi_value args[1] = {nullptr};

   napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

   // Obtain NodeContent.
   ArkUI_NodeContentHandle contentHandle;
   OH_ArkUI_GetNodeContentFromNapiValue(env, args[0], &contentHandle);
   NativeEntry::GetInstance()->SetContentHandle(contentHandle);

   // Create a lazy-loaded text list.
   auto node = CreateLazyTextListExample(env);

   // Keep the native side object in the management class to maintain its lifecycle.
   NativeEntry::GetInstance()->SetRootNode(node);
   g_env = env;
   return nullptr;

}

napi_value DestroyNativeRoot(napi_env env, napi_callback_info info) { // Release the native side object from the management class. NativeEntry::GetInstance()->DisposeRootNode(); return nullptr; }

} // namespace NativeModule “`

你可能感兴趣的鸿蒙文章

harmony 鸿蒙ArkUI

harmony 鸿蒙Atomic Service Full Screen Launch Component (FullScreenLaunchComponent)

harmony 鸿蒙Arc Button (ArcButton)

harmony 鸿蒙Animation Smoothing

harmony 鸿蒙Animation Overview

harmony 鸿蒙Frame Animation (ohos.animator)

harmony 鸿蒙Implementing Property Animation

harmony 鸿蒙Property Animation Overview

harmony 鸿蒙Dialog Box Overview

harmony 鸿蒙Blur Effect

0  赞