在本教程中,您将学习如何异步加载要在 列表框 (List Box) 节点中显示的资源。异步加载资源时,异步加载不会阻止用户界面,因此您的 Kanzi 应用程序可以保持响应。用户滚动列表时,会加载每个变为可见的项,而不是把所有项保持在工作内存中。在本教程中,将应用程序设置为加载资源时显示占位符内容。
以下情况下,可以异步加载资源:
本教程将展示如何使用列表项生成器,异步加载资源。要异步加载单个预设件的资源,在 Kanzi Studio 中使用触发器和动作。请参阅异步加载预设件资源。
视频显示教程的结果。
开始本教程之前,请确保您已建立了 Kanzi 开发环境。安装:
本教程假定您了解使用 Kanzi Studio 的基础知识。熟悉 Kanzi 的最佳切入点是:
本教程的起点资料是存储在<KanziWorkspace>/Tutorials/Loading/Start/Tool_project 目录中的Loading.kzproj Kanzi Studio 工程文件。
<KanziWorkspace>/Tutorials/Loading/Completed 目录包含本教程已完成的工程。
起点工程包含完成本教程所需的内容:
在本节中,您将使用 Kanzi Engine API 创建一个为 3D 轨迹列表框 (Trajectory List Box 3D) 节点提供项的列表项生成器。可使用此列表项生成器定义应用程序加载和显示列表各项的方式。
要创建一个列表项生成器:
如果您在 Visual Studio 2017 中打开教程解决方案,遇到提示您重新定位工程到最新的 Microsoft 工具集时,请点击取消 (Cancel)。
#ifndef CUSTOM_ITEM_GENERATOR_HPP
#定义 CUSTOM_ITEM_GENERATOR_HPP
#包含 <kanzi/kanzi.hpp>
using namespace kanzi;
//创建一个列表项生成器,用于异步加载 3D 轨迹列表框 (Trajectory List Box 3D) 节点的列表项。
class CustomListBoxItemGenerator : public ListBoxItemGenerator3D, public enable_shared_from_this<CustomListBoxItemGenerator>
{
//定义保留每个列表项相关信息的结构。
struct ItemInfo
{
//定义显示每个列表项的预设件实例的根节点。
Node3DSharedPtr m_item;
};
//定义同时存储可见和隐藏列表项的矢量的类型。
typedef vector<ItemInfo> ItemInfoVector;
};
#endif
CustomListBoxItemGenerator
函数后面,定义存储每个列表项相关信息的矢量类型,实现列表项生成器的基类需要的回调函数。//创建一个列表项生成器,用于异步加载 3D 轨迹列表框 (Trajectory List Box 3D) 节点的列表项。 class CustomListBoxItemGenerator : public ListBoxItemGenerator3D, public enable_shared_from_this<CustomListBoxItemGenerator> { ... public: //列表项生成器的构造函数。 explicit CustomListBoxItemGenerator(Domain* domain); //实现将列表项生成器附加到 3D 轨迹列表框 (Trajectory List Box 3D) 节点的回调函数。 virtual void attach(Node3D& node) KZ_OVERRIDE; //实现将列表项生成器从 3D 轨迹列表框 (Trajectory List Box 3D) 节点分离的回调函数。 virtual void detach(Node3D& node) KZ_OVERRIDE; //实现获取和初始化 Placeholder 预设件的回调函数。 //当应用程序异步加载列表项资源时,显示 Placeholder 预设件。 virtual ItemSharedPtr acquireItem(size_t index) KZ_OVERRIDE; //实现移除 acquireItem() 函数返回的 //指向 3D 轨迹列表框 (Trajectory List Box 3D) 的智能指针的回调函数。在本教程中,不定义释放列表项 //项管理函数。例如,当列表项变为不可见时,会释放这些列表项 //以卸载这些项的资源。 virtual void releaseItem(ItemSharedPtr object) KZ_OVERRIDE; //实现获取 3D 轨迹列表框 (Trajectory List Box 3D) 节点从 acquireItem() 函数返回的 //列表项索引的回调函数。应用程序在 updateItem() 函数中使用该列表项的索引, //将 Placeholder 预设件替换为该列表项。 virtual optional<size_t> getItemIndex(ItemSharedPtr object) KZ_OVERRIDE; //实现获取列表项大小,以给定索引显示的回调函数。 //如果不设置列表项大小,应用程序会使用从此函数返回的大小。 virtual Vector3 getItemSize(size_t index) KZ_OVERRIDE; //实现获取列表项生成器中项的数量的回调函数。 //如果在 3D 轨迹列表框 (Trajectory List Box 3D) 节点中添加或移动一个项,则应用程序使用从此函数返回的值。 virtual size_t getCount() KZ_OVERRIDE; private: //实现每次应用程序加载完一个预设件时, //3D 轨迹列表框 (Trajectory List Box 3D) 节点调用的回调函数。使用用此函数将 //Placeholder 预设件替换为一个列表项。 void updateItem(size_t index, string url); //列表项生成器附加到的 3D 轨迹列表框 (Trajectory List Box 3D) 节点。 TrajectoryListBox3D* m_listBox; //同时存储可见和隐藏列表项的矢量。 ItemInfoVector m_items; };
CustomListBoxItemGenerator
类添加构造函数,并定义在头文件中实现的 attach()
和 detach()
回调函数:CustomListBoxItemGenerator::CustomListBoxItemGenerator(Domain* domain) : ListBoxItemGenerator3D(domain) { } //定义将列表项生成器附加到 3D 轨迹列表框 (Trajectory List Box 3D) 节点的回调函数。 void CustomListBoxItemGenerator::attach(Node3D& listBox) { //存储一个指向 3D 轨迹列表框 (Trajectory List Box 3D) 节点的指针。 TrajectoryListBox3D* trajectoryListBox = dynamic_cast<TrajectoryListBox3D*>(&listBox); //检查 3D 轨迹列表框 (Trajectory List Box 3D) 节点存在。 if (!trajectoryListBox) { kzThrowException(logic_error("You can attach this item generator only to a 3D 轨迹列表框 (Trajectory List Box 3D) node.")); } m_listBox = trajectoryListBox; //设置存储列表项的矢量的大小。 //大小设置为 9,因为该应用程序的 kzb 文件有 9 项。 //当列表项的数量不断变化时,可以定义一个数据源, //供列表项生成器用于提供列表项。当出于此目的使用数据源时, //必须同步列表项生成器与该数据源。 m_items.resize(9); } //实现将列表项生成器从 3D 轨迹列表框 (Trajectory List Box 3D) 节点分离的回调函数。 void CustomListBoxItemGenerator::detach(Node3D&) { //此回调函数将列表项生成器从 3D 轨迹列表框 (Trajectory List Box 3D) 节点分离。 //在本教程中,不涉及取消初始化列表项生成器。 //在应用程序中,当要清理内存时,可取消初始化列表项生成器。 }
detach()
回调函数后面,定义返回列表项生成器中各项的索引、大小和总数的回调函数。实现获取 3D 轨迹列表框 (Trajectory List Box 3D) 节点从 acquireItem() 函数返回的 //列表项的索引的回调函数。应用程序在 updateItem() 函数中使用该列表的索引, //将 Placeholder 预设件替换为该列表项。 optional<size_t> CustomListBoxItemGenerator::getItemIndex(ItemSharedPtr item) { //查找下一个列表项的索引。 //通过搜索列表项容器可获取索引。 optional<size_t> itemIndex; for (size_t i = 0; i < m_items.size(); ++i) { ItemInfo& itemInfo = m_items[i]; if (itemInfo.m_item == item) { return i; } } return itemIndex; } //定义获取列表项大小,以给定索引显示的回调函数。 //如果不设置列表项大小,应用程序会使用从此函数返回的大小。 Vector3 CustomListBoxItemGenerator::getItemSize(size_t /*index*/) { //将项的大小设置为 0。 //在 Kanzi Studio 的 3D 轨迹列表框 (Trajectory List Box 3D) 节点中,设置规定 3D 轨迹列表框 (Trajectory List Box 3D) 节点中 //各项大小的属性。 return Vector3(); } //定义获取列表项生成器中项的总数的回调函数。 //如果在 3D 轨迹列表框 (Trajectory List Box 3D) 节点中添加或移动一个项,则应用程序使用从此函数返回的值。 size_t CustomListBoxItemGenerator::getCount() { //获取可见项和隐藏项的数量。 return m_items.size(); }
#包含 <kanzi/kanzi.hpp> #包含 "custom_item_generator.hpp"
Loading
应用程序类添加创建的列表项生成器的初始化函数:class Loading : public ExampleApplication { ... virtual void onProjectLoaded() KZ_OVERRIDE { //获取屏幕 (Screen) 节点。 ScreenSharedPtr screen = getScreen(); //获取 3D 轨迹列表框 (Trajectory List Box 3D) 节点。 //应用程序 kzb 文件包含用于获取 3D 轨迹列表框 (Trajectory List Box 3D) 节点的一个别名。 TrajectoryListBox3DSharedPtr listBox = screen->lookupNode<TrajectoryListBox3D>("#3D 轨迹列表框 (Trajectory List Box 3D)"); //初始化列表项生成器并设置 3D 轨迹列表框 (Trajectory List Box 3D) 节点使用在该列表项生成器中 //实现的功能。 shared_ptr<CustomListBoxItemGenerator> itemGenerator(new CustomListBoxItemGenerator(getDomain())); listBox->setItemGenerator(itemGenerator); } private: //为 3D 轨迹列表框 (Trajectory List Box 3D) 节点创建一个指针。 TrajectoryListBox3DSharedPtr m_listBox; };
在本节中,您学习了如何异步加载用户滚动 3D 轨迹列表框 (Trajectory List Box 3D) 节点过程中,变为可见的每个项的资源。将应用程序设置为加载某项资源前,显示点位符项。
要异步加载列表项,请执行以下代码:
detach()
回调函数后面,定义 acquireItem()
函数:
//定义应用程序加载对应项的资源前,获取和显示每个列表项的
// Placeholder 预设件。
//此函数异步获取来自同一预设件的每个列表项的资源,并
//在某项更改时,调用 updateItem()
函数告知 3D 轨迹列表框 (Trajectory List Box 3D) 节点。
CustomListBoxItemGenerator::ItemSharedPtr CustomListBoxItemGenerator::acquireItem(size_t index)
{
//如果某项已存在,则返回对应的项。
if (m_items[index].m_item)
{
return m_items[index].m_item;
}
//获取并实例化用作正在加载的列表项的 Placeholder 占位符。
PrefabTemplateSharedPtr itemPrefabTemplate = m_listBox->acquireResource<PrefabTemplate>(ResourceID("kzb://loading/Prefabs/Placeholder"));
Node3DSharedPtr item = itemPrefabTemplate->instantiate<Node3D>("Placeholder");
//在该项的容器中,将 Placeholder 另存为现有项。
//当应用程序完成加载,将占位符替换为已加载的项。
m_items[index].m_item = item;
//为每个来自 kzb 路径和项索引的列表项,创建 kzb URL。
string url = string("kzb://loading/Prefabs/Wheel") + to_string(index);
//获取该列表项的预设件。
PrefabTemplateSharedPtr prefab = m_listBox->acquireResource<PrefabTemplate>(ResourceID(url));
//开始异步加载一个预设件的资源。
ResourceManager::UrlContainer urls;
collectResourceUrls(*prefab, urls);
m_listBox->getResourceManager()->acquireResourcesAsync(urls, bind(&CustomListBoxItemGenerator::updateItem, this, index, url));
return item;
}
//实现移除指向从 acquireItem() 函数返回的项的智能指针 3D 轨迹列表框 (Trajectory List Box 3D)
//的回调函数。
//在本教程中,不涉及定义用于释放列表项的项管理函数。
void CustomListBoxItemGenerator::releaseItem(ItemSharedPtr /*item*/)
{
//在本教程中,列表项生成器不实现任何缓存或复杂项管理。
//可让此函数为空,因为 shared_ptr 析构函数会释放相应列表项。
//例如,可在某项每次变为不可见时,释放相应列表项以卸载其资源。
}
updateItem()
函数,每次调用 acquireItem()
函数,将 Placeholder 预设件替换为相应列表项,并通知 列表框 (List Box) 一个项目已更改:定义每次应用程序完成加载某一预计件资源时, //3D 轨迹列表框 (Trajectory List Box 3D) 节点调用的回调函数。 //使用此函数将 Placeholder 预设件替换为一个列表项。 void CustomListBoxItemGenerator::updateItem(size_t index, string url) { //获取完成加载的预设件并将 Placeholder 替换为下一项。 PrefabTemplateSharedPtr prefab = m_listBox->acquireResource<PrefabTemplate>(ResourceID(url)); Node3DSharedPtr item = prefab->instantiate<Node3D>("item"); m_items[index].m_item = item; //通知 3D 轨迹列表框 (Trajectory List Box 3D) 节点该列表项已更改。 //3D 轨迹列表框 (Trajectory List Box 3D) 调用返回下一项的 acquireItem() 函数。 m_listBox->notifyItemReplaced(index); }
当运行应用程序并滚动 3D 轨迹列表框 (Trajectory List Box 3D) 时,应用程序在异步加载每个列表项资源时,显示 Placeholder 预设件。
在本教程中,您已学习如何在用户滚动 列表框 (List Box) 时,异步加载要在 列表框 (List Box) 节点显示的资源。现在您可以: