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  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