<aside> 💡 Wiki Page: https://wiki.funovus.com/master/Ui-XML-List

</aside>

Virtual lists greatly improve the performance of long lists by only creating UI elements when the user scrolls. Elements outside of the scroll view won't be created or updated, saving on performance.

Use Case

Virtual lists should be used if your list has a very high number of child items; as an ordinary scroll would create all child items immediately at high performance cost.

Example Map:

https://platform.wildsky.dev/arcade/game/1019

XML Usage:

<!-- The item in the list -->
<Frame layout="flex" height="50" widthPercent="100">
    <Text id="text" text="This is one entry." />
</Frame>

<!-- The list with a normal mode and a virtual list mode -->
<Frame layout="flex" padding="16" heightPercent="100" flexDirection="column">
    <State name="mode" numberValue="0" />

		<!-- The bar of buttons at the top -->
    <Frame widthPercent="100" height="50">
        <Button bind:onClick="state.mode = 1 - state.mode">
            <Text bind:text="'Mode: ' .. (state.mode == 0 and 'normal' or 'virutal')" />
        </Button>
        <Button id="normal_list_load_more" bind:active="state.mode == 0">
            <Text text="Add 100 items" />
        </Button>
        <Button id="normal_list_load_even_more" bind:active="state.mode == 0">
            <Text text="Add 1000 items" />
        </Button>
        <Button id="virtual_list_load_more" bind:active="state.mode == 1">
            <Text text="Add 100 items" />
        </Button>
        <Button id="virtual_list_load_even_more" bind:active="state.mode == 1">
            <Text text="Add 1000 items" />
        </Button>
        <Button id="refresh">
            <Text text="Refresh" />
        </Button>
        <Text id="normal_list_text" bind:active="state.mode == 0" text="Total: 0" />
        <Text id="virtual_list_text" bind:active="state.mode == 1" text="Total: 0" />
    </Frame>

		<!-- The normal list -->
    <Frame bind:active="state.mode == 0" widthPercent="100" heightPercent="100" flexDirection="column">
        <VScroll heightPercent="100" width="500" frameImageColor="r: 1, g: 1, b: 0.8, a: 1">
            <ScrollContent padding="16">
                <Text text="Normal List Begin" />
                <VStack id="normal_list" widthPercent="100" />
                <Text text="Normal List End" />
            </ScrollContent>
        </VScroll>
    </Frame>

		<!-- The virtual list -->
    <Frame bind:active="state.mode == 1" heightPercent="100" widthPercent="100" flexDirection="column">
        <VScroll heightPercent="100" width="500" frameImageColor="r: 0.8, g: 1, b: 1, a: 1">
            <ScrollContent padding="16">
                <Text text="Virtual List Begin" />
                <List id="virtual_list" itemName="MyItem" itemSize="50" itemCount="100" />
                <Text text="Virtual List End" />
            </ScrollContent>
        </VScroll>
    </Frame>
</Frame>

Lua usage:

-- ============================================================================
-- LIBRARIES
-- ============================================================================
local Core = require("Core")
local GMUI = require("GMUI")
Core.HideDefaultUi()

-- ============================================================================
-- Setup
-- ============================================================================

-- Sets the "target" string to use the melee unit's data
local target = DCEI.FindUnit(DCEI.Unit("Standard MeleeUnit"))
DCEI.BindUnitData("target", target)

-- Creates the list
local layout = GMUI.Layout.New({ name = "TestList", parent = DCEI.GetUiRootFrame() })

function ShowItemAnimated(item)
    DCEI.AnimateFrameScale(item, { x = 0, y = 0, z = 0 }, { x = 1, y = 1, z = 1 }, 1, "OutExpo")
end

-- ============================================================================
-- Normal list loading
-- ============================================================================

local normal_item_count = 0
function NormalListLoadMore(count)
    for _ = 1, count do
        normal_item_count = normal_item_count + 1
        local item = GMUI.Layout.New({ name = "MyItem", parent = layout.normal_list })
        DCEI.SetTextFrameTextExpression(item.text, "row " .. normal_item_count .. " = {target.Health.Current}")
        ShowItemAnimated(item)
    end
    DCEI.SetTextFrameText(layout.normal_list_text, "total: " .. normal_item_count)
end
DCEI.SetOnClickCallback(layout.normal_list_load_more, function()
    NormalListLoadMore(100)
end)
DCEI.SetOnClickCallback(layout.normal_list_load_even_more, function()
    NormalListLoadMore(1000)
end)

-- ============================================================================
-- Virtual list setup
-- ============================================================================

-- Virtual list requires a separate data storage.
local virtual_items = {}
DCEI.SetListFrameItemCount(layout.virtual_list, #virtual_items)
local refresh_count = 0
-- Tell the virtual list how to create new items as well as how to bind the data to each item.
DCEI.SetListFrameItemDataCallback(layout.virtual_list, function(item, index)
    local text = DCEI.GetChildFrameById(item, "text")
    if refresh_count == 0 then
        DCEI.SetTextFrameTextExpression(text, "row " .. virtual_items[index].value .. " = {target.Health.Current}")
    else
        DCEI.SetTextFrameTextExpression(
            text,
            "r(" .. refresh_count .. ") " .. virtual_items[index].value .. " = {target.Health.Current}"
        )
    end
    ShowItemAnimated(item)
end)

-- ============================================================================
-- Virtual list loading
-- ============================================================================

function VirtualListLoadMore(count)
    -- Instead of creating items directly, we update the underlying data structure.
    for i = 1, count do
        table.insert(virtual_items, { value = #virtual_items + 1 })
    end
    -- Tell the virtual list we have more items and the list to manage item creation if needed.
    DCEI.SetListFrameItemCount(layout.virtual_list, #virtual_items)
    DCEI.SetTextFrameText(layout.virtual_list_text, "total: " .. #virtual_items)
end
DCEI.SetOnClickCallback(layout.virtual_list_load_more, function()
    VirtualListLoadMore(100)
end)
DCEI.SetOnClickCallback(layout.virtual_list_load_even_more, function()
    VirtualListLoadMore(1000)
end)

-- ============================================================================
-- Refresh button
-- ============================================================================

DCEI.SetOnClickCallback(layout.refresh, function()
    refresh_count = refresh_count + 1
    DCEI.RefreshListFrameItems(layout.virtual_list, 1, #virtual_items)
end)

Result:

https://github-production-user-asset-6210df.s3.amazonaws.com/60531792/256355483-7013a936-05bb-4b7d-a3fa-1c7985b253a7.gif


Back to Top