异步加载和部署资源

在本教程中,您将学习如何异步加载要在 列表框 (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) 节点提供项的列表项生成器。可使用此列表项生成器定义应用程序加载和显示列表各项的方式。

要创建一个列表项生成器:

  1. 在 Visual Studio 中,打开存储在 <KanziWorkspace>/Tutorials/Loading/Start/Application/configs/platforms/win32 目录中的解决方案。

    如果您在 Visual Studio 2017 中打开教程解决方案,遇到提示您重新定位工程到最新的 Microsoft 工具集时,请点击取消 (Cancel)。

    Visual Studio 解决方案包含完成本教程所需要的模板文件:
  2. custom_item_generator.hpp 头文件中创建列表项生成器的类。
    #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
  3. 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;
    };
  4. custom_item_generator.cpp 文件中为 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) 节点分离。
        //在本教程中,不涉及取消初始化列表项生成器。
        //在应用程序中,当要清理内存时,可取消初始化列表项生成器。
    }
  5. custom_item_generator.cpp 文件中,在 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 Studio3D 轨迹列表框 (Trajectory List Box 3D) 节点中,设置规定 3D 轨迹列表框 (Trajectory List Box 3D) 节点中
        //各项大小的属性。
        return Vector3();
    }
    
    //定义获取列表项生成器中项的总数的回调函数。
    //如果在 3D 轨迹列表框 (Trajectory List Box 3D) 节点中添加或移动一个项,则应用程序使用从此函数返回的值。
    size_t CustomListBoxItemGenerator::getCount()
    {
        //获取可见项和隐藏项的数量。
        return m_items.size();
    }
  6. loading.cpp 文件中包含 custom_item_generator.hpp 头文件,以访问该文件中已实现的函数。
    #包含 <kanzi/kanzi.hpp>
    #包含 "custom_item_generator.hpp"
  7. loading.cpp 文件中,向 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) 节点过程中,变为可见的每个项的资源。将应用程序设置为加载某项资源前,显示点位符项。

要异步加载列表项,请执行以下代码:

  1. custom_item_generator.cpp 文件中 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 析构函数会释放相应列表项。
        //例如,可在某项每次变为不可见时,释放相应列表项以卸载其资源。
    }
  2. custom_item_generator.cpp 文件中,定义 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);
    }
  3. Kanzi Studio 中选择文件 (File) > 导出 (Export) > 导出 KZB (Export KZB)
  4. Kanzi StudioKanzi Studio 工程创建 kzb 文件和配置文件。Kanzi Studio 将导出的文件存储在 <ProjectName>/Application/bin 目录或您在 工程 (Project) > 属性 (Properties)二进制导出目录 (Binary Export Directory) 属性中指定的位置。Kzb 文件包含 Kanzi Studio 工程中的所有节点和资源,您在本地化表中标记为本地化包的资源除外。
    当您从 Visual Studio 中运行您的 Kanzi 应用程序时,您的应用程序就会加载 kzb 文件和配置文件。
  5. 在 Visual Studio 中,为您的 Visual Studio 版本选择一个解决方案配置并运行应用程序。
    例如,如果您仍在开发应用程序,选择GL_vs2015_Debug 配置。要创建 Kanzi 应用程序的产品版本,选择一个可用的发布配置。

当运行应用程序并滚动 3D 轨迹列表框 (Trajectory List Box 3D) 时,应用程序在异步加载每个列表项资源时,显示 Placeholder 预设件。

接下来该做什么?

在本教程中,您已学习如何在用户滚动 列表框 (List Box) 时,异步加载要在 列表框 (List Box) 节点显示的资源。现在您可以:

另请参阅

使用 3D 轨迹列表框 (Trajectory List Box 3D) 节点

异步加载预设件资源

列表框 (List Box) 节点