1# 使用懒加载开发长列表界面
2
3
4针对List、Grid、WaterFlow、Swiper组件,提供[NodeAdapter](../reference/apis-arkui/_ark_u_i___native_module.md#arkui_nodeadapterhandle)对象替代ArkTS侧的LazyForEach功能,用于按需生成子组件,其中List组件的属性枚举值为NODE_LIST_NODE_ADAPTER,Grid组件的属性枚举值为NODE_GRID_NODE_ADAPTER,WaterFlow组件的属性枚举值为NODE_WATER_FLOW_NODE_ADAPTER,Swiper组件的属性枚举值为NODE_SWIPER_NODE_ADAPTER。
5
6
7虽然都用于按需生成组件,但不同于ArkTS的LazyForEach,NodeAdapter对象的规格如下:
8
9
10- 设置了NodeAdapter属性的节点,不再支持addChild等直接添加子组件的接口。子组件完全由NodeAdapter管理,使用属性方法设置NodeAdapter时,会判断父组件是否已经存在子节点,如果父组件已经存在子节点,则设置NodeAdapter操作失败,返回错误码。
11
12- NodeApdater通过相关事件通知开发者按需生成组件,类似组件事件机制,开发者使用NodeAdapter时要注册[事件监听器](../reference/apis-arkui/_ark_u_i___native_module.md#oh_arkui_nodeadapter_registereventreceiver),在监听器事件中处理逻辑,相关事件通过[ArkUI_NodeAdapterEventType](../reference/apis-arkui/_ark_u_i___native_module.md#arkui_nodeadaptereventtype)定义。另外NodeAdapter不会主动释放不在屏幕内显示的组件对象,开发者需要在[NODE_ADAPTER_EVENT_ON_REMOVE_NODE_FROM_ADAPTER](../reference/apis-arkui/_ark_u_i___native_module.md#arkui_nodeadaptereventtype)事件中进行组件对象的释放,或者进行缓存复用。下图展示了典型列表滑动场景下的事件触发机制:
13  ![zh-cn_image_0000001949769409](figures/zh-cn_image_0000001949769409.png)
14
15
16以下示例代码针对[接入ArkTS页面](ndk-access-the-arkts-page.md)章节代码进行优化,引入懒加载机制实现文本列表:
17
18
191. 接入ArkTS,参考[接入ArkTS页面](ndk-access-the-arkts-page.md)。
20
212. 懒加载适配器相关功能实现。
22   ```
23   // ArkUIListItemAdapter
24   // 用于文本列表懒加载功能代码。
25
26   #ifndef MYAPPLICATION_ARKUILISTITEMADAPTER_H
27   #define MYAPPLICATION_ARKUILISTITEMADAPTER_H
28
29   #include <arkui/native_node.h>
30   #include <stack>
31   #include <string>
32   #include <unordered_set>
33
34   #include "ArkUIListItemNode.h"
35   #include "ArkUITextNode.h"
36   #include "nativeModule.h"
37
38   namespace NativeModule {
39
40   class ArkUIListItemAdapter {
41   public:
42       ArkUIListItemAdapter()
43           : module_(NativeModuleInstance::GetInstance()->GetNativeNodeAPI()), handle_(OH_ArkUI_NodeAdapter_Create()) { // 使用NodeAdapter创建函数。
44           // 初始化懒加载数据。
45           for (int32_t i = 0; i < 1000; i++) {
46               data_.emplace_back(std::to_string(i));
47           }
48           // 设置懒加载数据。
49           OH_ArkUI_NodeAdapter_SetTotalNodeCount(handle_, data_.size());
50           // 设置懒加载回调事件。
51           OH_ArkUI_NodeAdapter_RegisterEventReceiver(handle_, this, OnStaticAdapterEvent);
52       }
53
54       ~ArkUIListItemAdapter() {
55           // 释放创建的组件。
56           while (!cachedItems_.empty()) {
57               cachedItems_.pop();
58           }
59           items_.clear();
60           // 释放Adapter相关资源。
61           OH_ArkUI_NodeAdapter_UnregisterEventReceiver(handle_);
62           OH_ArkUI_NodeAdapter_Dispose(handle_);
63       }
64
65       ArkUI_NodeAdapterHandle GetHandle() const { return handle_; }
66
67       void RemoveItem(int32_t index) {
68           // 删除第index个数据。
69           data_.erase(data_.begin() + index);
70           // 如果index会导致可视区域元素发生可见性变化,则会回调NODE_ADAPTER_EVENT_ON_REMOVE_NODE_FROM_ADAPTER事件删除元素,
71           // 根据是否有新增元素回调NODE_ADAPTER_EVENT_ON_GET_NODE_ID和NODE_ADAPTER_EVENT_ON_ADD_NODE_TO_ADAPTER事件。
72           OH_ArkUI_NodeAdapter_RemoveItem(handle_, index, 1);
73           // 更新新的数量。
74           OH_ArkUI_NodeAdapter_SetTotalNodeCount(handle_, data_.size());
75       }
76
77       void InsertItem(int32_t index, const std::string &value) {
78           data_.insert(data_.begin() + index, value);
79           // 如果index会导致可视区域元素发生可见性变化,则会回调NODE_ADAPTER_EVENT_ON_GET_NODE_ID和NODE_ADAPTER_EVENT_ON_ADD_NODE_TO_ADAPTER事件,
80           // 根据是否有删除元素回调NODE_ADAPTER_EVENT_ON_REMOVE_NODE_FROM_ADAPTER事件。
81           OH_ArkUI_NodeAdapter_InsertItem(handle_, index, 1);
82           // 更新新的数量。
83           OH_ArkUI_NodeAdapter_SetTotalNodeCount(handle_, data_.size());
84       }
85
86       void MoveItem(int32_t oldIndex, int32_t newIndex) {
87           auto temp = data_[oldIndex];
88           data_.insert(data_.begin() + newIndex, temp);
89           data_.erase(data_.begin() + oldIndex);
90           // 移到位置如果未发生可视区域内元素的可见性变化,则不回调事件,反之根据新增和删除场景回调对应的事件。
91           OH_ArkUI_NodeAdapter_MoveItem(handle_, oldIndex, newIndex);
92       }
93
94       void ReloadItem(int32_t index, const std::string &value) {
95           data_[index] = value;
96           // 如果index位于可视区域内,先回调NODE_ADAPTER_EVENT_ON_REMOVE_NODE_FROM_ADAPTER删除老元素,
97           // 再回调NODE_ADAPTER_EVENT_ON_GET_NODE_ID和NODE_ADAPTER_EVENT_ON_ADD_NODE_TO_ADAPTER事件。
98           OH_ArkUI_NodeAdapter_ReloadItem(handle_, index, 1);
99       }
100
101       void ReloadAllItem() {
102           std::reverse(data_.begin(), data_.end());
103           // 全部重新加载场景下,会回调NODE_ADAPTER_EVENT_ON_GET_NODE_ID接口获取新的组件ID,
104           // 根据新的组件ID进行对比,ID不发生变化的进行复用,
105           // 针对新增ID的元素,调用NODE_ADAPTER_EVENT_ON_ADD_NODE_TO_ADAPTER事件创建新的组件,
106           // 然后判断老数据中遗留的未使用ID,调用NODE_ADAPTER_EVENT_ON_REMOVE_NODE_FROM_ADAPTER删除老元素。
107           OH_ArkUI_NodeAdapter_ReloadAllItems(handle_);
108       }
109
110   private:
111       static void OnStaticAdapterEvent(ArkUI_NodeAdapterEvent *event) {
112           // 获取实例对象,回调实例事件。
113           auto itemAdapter = reinterpret_cast<ArkUIListItemAdapter *>(OH_ArkUI_NodeAdapterEvent_GetUserData(event));
114           itemAdapter->OnAdapterEvent(event);
115       }
116
117       void OnAdapterEvent(ArkUI_NodeAdapterEvent *event) {
118           auto type = OH_ArkUI_NodeAdapterEvent_GetType(event);
119           switch (type) {
120           case NODE_ADAPTER_EVENT_ON_GET_NODE_ID:
121               OnNewItemIdCreated(event);
122               break;
123           case NODE_ADAPTER_EVENT_ON_ADD_NODE_TO_ADAPTER:
124               OnNewItemAttached(event);
125               break;
126           case NODE_ADAPTER_EVENT_ON_REMOVE_NODE_FROM_ADAPTER:
127               OnItemDetached(event);
128               break;
129           default:
130               break;
131           }
132       }
133
134       // 分配ID给需要显示的Item,用于ReloadAllItems场景的元素diff。
135       void OnNewItemIdCreated(ArkUI_NodeAdapterEvent *event) {
136           auto index = OH_ArkUI_NodeAdapterEvent_GetItemIndex(event);
137           static std::hash<std::string> hashId = std::hash<std::string>();
138           auto id = hashId(data_[index]);
139           OH_ArkUI_NodeAdapterEvent_SetNodeId(event, id);
140       }
141
142       // 需要新的Item显示在可见区域。
143       void OnNewItemAttached(ArkUI_NodeAdapterEvent *event) {
144           auto index = OH_ArkUI_NodeAdapterEvent_GetItemIndex(event);
145           ArkUI_NodeHandle handle = nullptr;
146           if (!cachedItems_.empty()) {
147               // 使用并更新回收复用的缓存。
148               auto recycledItem = cachedItems_.top();
149               auto textItem = std::dynamic_pointer_cast<ArkUITextNode>(recycledItem->GetChildren().back());
150               textItem->SetTextContent(data_[index]);
151               handle = recycledItem->GetHandle();
152               // 释放缓存池的引用。
153               cachedItems_.pop();
154           } else {
155               // 创建新的元素。
156               auto listItem = std::make_shared<ArkUIListItemNode>();
157               auto textNode = std::make_shared<ArkUITextNode>();
158               textNode->SetTextContent(data_[index]);
159               textNode->SetFontSize(16);
160               textNode->SetPercentWidth(1);
161               textNode->SetHeight(100);
162               textNode->SetBackgroundColor(0xFFfffacd);
163               textNode->SetTextAlign(ARKUI_TEXT_ALIGNMENT_CENTER);
164               listItem->AddChild(textNode);
165               listItem->RegisterOnClick([index]() { OH_LOG_INFO(LOG_APP, "on %{public}d list item click", index); });
166               handle = listItem->GetHandle();
167               // 保持文本列表项的引用。
168               items_.emplace(handle, listItem);
169           }
170           // 设置需要展示的元素。
171           OH_ArkUI_NodeAdapterEvent_SetItem(event, handle);
172       }
173
174       // Item从可见区域移除。
175       void OnItemDetached(ArkUI_NodeAdapterEvent *event) {
176           auto item = OH_ArkUI_NodeAdapterEvent_GetRemovedNode(event);
177           // 放置到缓存池中进行回收复用。
178           cachedItems_.emplace(items_[item]);
179       }
180
181
182       std::vector<std::string> data_;
183       ArkUI_NativeNodeAPI_1 *module_ = nullptr;
184       ArkUI_NodeAdapterHandle handle_ = nullptr;
185
186       // 管理NodeAdapter生成的元素。
187       std::unordered_map<ArkUI_NodeHandle, std::shared_ptr<ArkUIListItemNode>> items_;
188
189       // 管理回收复用组件池。
190       std::stack<std::shared_ptr<ArkUIListItemNode>> cachedItems_;
191   };
192
193   } // namespace NativeModule
194
195   #endif // MYAPPLICATION_ARKUILISTITEMADAPTER_H
196   ```
197
1983. 针对[接入ArkTS页面](ndk-access-the-arkts-page.md)章节使用的列表封装类对象,添加额外懒加载能力。
199   ```
200   // ArkUIListNode.h
201   // 列表封装对象。
202
203   #ifndef MYAPPLICATION_ARKUILISTNODE_H
204   #define MYAPPLICATION_ARKUILISTNODE_H
205
206   #include "ArkUIListItemAdapter.h"
207   #include "ArkUINode.h"
208   #include <hilog/log.h>
209
210   namespace NativeModule {
211   class ArkUIListNode : public ArkUINode {
212   public:
213       ArkUIListNode()
214           : ArkUINode((NativeModuleInstance::GetInstance()->GetNativeNodeAPI())->createNode(ARKUI_NODE_LIST)) {}
215
216       ~ArkUIListNode() override {
217           nativeModule_->unregisterNodeEvent(handle_, NODE_LIST_ON_SCROLL_INDEX);
218           if (adapter_) {
219               // 析构的时候卸载adapter下的UI组件。
220               nativeModule_->resetAttribute(handle_, NODE_LIST_NODE_ADAPTER);
221               adapter_.reset();
222           }
223       }
224
225       void SetScrollBarState(bool isShow) {
226           assert(handle_);
227           ArkUI_ScrollBarDisplayMode displayMode =
228               isShow ? ARKUI_SCROLL_BAR_DISPLAY_MODE_ON : ARKUI_SCROLL_BAR_DISPLAY_MODE_OFF;
229           ArkUI_NumberValue value[] = {{.i32 = displayMode}};
230           ArkUI_AttributeItem item = {value, 1};
231           nativeModule_->setAttribute(handle_, NODE_SCROLL_BAR_DISPLAY_MODE, &item);
232       }
233
234       void RegisterOnScrollIndex(const std::function<void(int32_t index)> &onScrollIndex) {
235           assert(handle_);
236           onScrollIndex_ = onScrollIndex;
237           nativeModule_->registerNodeEvent(handle_, NODE_LIST_ON_SCROLL_INDEX, 0, nullptr);
238       }
239       // 引入懒加载模块。
240       void SetLazyAdapter(const std::shared_ptr<ArkUIListItemAdapter> &adapter) {
241           assert(handle_);
242           ArkUI_AttributeItem item{nullptr, 0, nullptr, adapter->GetHandle()};
243           nativeModule_->setAttribute(handle_, NODE_LIST_NODE_ADAPTER, &item);
244           adapter_ = adapter;
245       }
246
247   protected:
248       void OnNodeEvent(ArkUI_NodeEvent *event) override {
249           auto eventType = OH_ArkUI_NodeEvent_GetEventType(event);
250           switch (eventType) {
251           case NODE_LIST_ON_SCROLL_INDEX: {
252               auto index = OH_ArkUI_NodeEvent_GetNodeComponentEvent(event)->data[0];
253               if (onScrollIndex_) {
254                   onScrollIndex_(index.i32);
255               }
256           }
257           default: {
258           }
259           }
260       }
261
262   private:
263       std::function<void(int32_t index)> onScrollIndex_;
264
265       std::shared_ptr<ArkUIListItemAdapter> adapter_;
266   };
267   } // namespace NativeModule
268
269   #endif // MYAPPLICATION_ARKUILISTNODE_H
270   ```
271
2724. 创建列表懒加载的示例代码。
273   ```
274   // ArkUILazyTextListExample
275   // 懒加载列表示例代码。
276
277   #ifndef MYAPPLICATION_LAZYTEXTLISTEXAMPLE_H
278   #define MYAPPLICATION_LAZYTEXTLISTEXAMPLE_H
279
280   #include "ArkUIBaseNode.h"
281   #include "ArkUIListNode.h"
282   #include "UITimer.h"
283   #include <thread>
284   #include <uv.h>
285
286   namespace NativeModule {
287
288   std::shared_ptr<ArkUIBaseNode> CreateLazyTextListExample(napi_env env) {
289       // 创建组件并挂载
290       // 1:创建List组件。
291       auto list = std::make_shared<ArkUIListNode>();
292       list->SetPercentWidth(1);
293       list->SetPercentHeight(1);
294       // 2:创建ListItem懒加载组件并挂载到List上。
295       auto adapter = std::make_shared<ArkUIListItemAdapter>();
296       list->SetLazyAdapter(adapter);
297
298       // 3:模拟相关懒加载操作。
299       CreateNativeTimer(env, adapter.get(), 4, [](void *userdata, int32_t count) {
300           auto adapter = reinterpret_cast<ArkUIListItemAdapter *>(userdata);
301           switch (count) {
302           case 0: {
303               // 删除第0个元素。
304               adapter->RemoveItem(0);
305               break;
306           }
307           case 1: {
308               // 插入第0个元素。
309               adapter->InsertItem(0, "0");
310               break;
311           }
312           case 2: {
313               // 移到元素位置。
314               adapter->MoveItem(0, 2);
315               break;
316           }
317           case 3: {
318               // 重载元素。
319               adapter->ReloadItem(0, "1112");
320               break;
321           }
322           case 4: {
323               // 全量重载。
324               adapter->ReloadAllItem();
325               break;
326           }
327           default: {
328           }
329           }
330       });
331
332       // 3:注册List相关监听事件.
333       list->RegisterOnScrollIndex([](int32_t index) { OH_LOG_INFO(LOG_APP, "on list scroll index: %{public}d", index); });
334       // 4: 注册挂载事件。
335       list->RegisterOnAppear([]() { OH_LOG_INFO(LOG_APP, "on list mount to tree"); });
336       // 4: 注册卸载事件。
337       list->RegisterOnDisappear([]() { OH_LOG_INFO(LOG_APP, "on list unmount from tree"); });
338       return list;
339   }
340   } // namespace NativeModule
341
342   #endif // MYAPPLICATION_LAZYTEXTLISTEXAMPLE_H
343   ```
344
3455. 定时器模块相关简单实现。
346   ```
347   // UITimer.h
348   // 定时器模块。
349
350   #ifndef MYAPPLICATION_UITIMER_H
351   #define MYAPPLICATION_UITIMER_H
352
353   #include <hilog/log.h>
354   #include <js_native_api.h>
355   #include <js_native_api_types.h>
356   #include <node_api.h>
357   #include <node_api_types.h>
358   #include <string>
359   #include <thread>
360   #include <uv.h>
361
362   namespace NativeModule {
363
364   struct UIData {
365       void *userData = nullptr;
366       int32_t count = 0;
367       int32_t totalCount = 0;
368       void (*func)(void *userData, int32_t count) = nullptr;
369   };
370
371   napi_threadsafe_function threadSafeFunction = nullptr;
372
373   void CreateNativeTimer(napi_env env, void *userData, int32_t totalCount, void (*func)(void *userData, int32_t count)) {
374       napi_value name;
375       std::string str = "UICallback";
376       napi_create_string_utf8(env, str.c_str(), str.size(), &name);
377       // UI主线程回调函数。
378       napi_create_threadsafe_function(
379           env, nullptr, nullptr, name, 0, 1, nullptr, nullptr, nullptr,
380           [](napi_env env, napi_value value, void *context, void *data) {
381               auto userdata = reinterpret_cast<UIData *>(data);
382               userdata->func(userdata->userData, userdata->count);
383               delete userdata;
384           },
385           &threadSafeFunction);
386       // 启动定时器,模拟数据变化。
387       std::thread timerThread([data = userData, totalCount, func]() {
388           uv_loop_t *loop = uv_loop_new();
389           uv_timer_t *timer = new uv_timer_t();
390           uv_timer_init(loop, timer);
391           timer->data = new UIData{data, 0, totalCount, func};
392           uv_timer_start(
393               timer,
394               [](uv_timer_t *handle) {
395                   OH_LOG_INFO(LOG_APP, "on timeout");
396                   napi_acquire_threadsafe_function(threadSafeFunction);
397                   auto *customData = reinterpret_cast<UIData *>(handle->data);
398                   // 创建回调数据。
399                   auto *callbackData =
400                       new UIData{customData->userData, customData->count, customData->totalCount, customData->func};
401                   napi_call_threadsafe_function(threadSafeFunction, callbackData, napi_tsfn_blocking);
402                   customData->count++;
403                   if (customData->count > customData->totalCount) {
404                       uv_timer_stop(handle);
405                       delete handle;
406                       delete customData;
407                   }
408               },
409               4000, 4000);
410           uv_run(loop, UV_RUN_DEFAULT);
411           uv_loop_delete(loop);
412       });
413       timerThread.detach();
414   }
415   } // namespace NativeModule
416
417   #endif // MYAPPLICATION_UITIMER_H
418   ```
419
4206. 按照[接入ArkTS页面](ndk-access-the-arkts-page.md)章节将懒加载相关示例代码挂载到ContentSlot上显示。
421   ```
422   // NDK接口入口挂载文件。
423
424   #include "NativeEntry.h"
425
426   #include "ArkUIMixedRefresh.h"
427   #include "LazyTextListExample.h"
428   #include "MixedRefreshExample.h"
429   #include "TextListExample.h"
430
431   #include <arkui/native_node_napi.h>
432   #include <arkui/native_type.h>
433   #include <js_native_api.h>
434   #include <uv.h>
435
436   namespace NativeModule {
437   namespace {
438   napi_env g_env;
439   }
440
441   napi_env GetNapiEnv() { return g_env; }
442
443   napi_value CreateNativeRoot(napi_env env, napi_callback_info info) {
444       size_t argc = 1;
445       napi_value args[1] = {nullptr};
446
447       napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
448
449       // 获取NodeContent
450       ArkUI_NodeContentHandle contentHandle;
451       OH_ArkUI_GetNodeContentFromNapiValue(env, args[0], &contentHandle);
452       NativeEntry::GetInstance()->SetContentHandle(contentHandle);
453
454       // 创建懒加载文本列表
455       auto node = CreateLazyTextListExample(env);
456
457       // 保持Native侧对象到管理类中,维护生命周期。
458       NativeEntry::GetInstance()->SetRootNode(node);
459       g_env = env;
460       return nullptr;
461   }
462
463   napi_value DestroyNativeRoot(napi_env env, napi_callback_info info) {
464       // 从管理类中释放Native侧对象。
465       NativeEntry::GetInstance()->DisposeRootNode();
466       return nullptr;
467   }
468
469   } // namespace NativeModule
470   ```
471