Skip to content

ListView

Virtualized scrollable list with object pooling. Supports vertical/horizontal direction, multiple prefab types, and zero-GC generics via VirtualListView<T>.

Inspector Setup

  1. Create a container with AutoLayout.
  2. AutoLayoutScrollView and ListView are added automatically by the builder. For manual setup, add both.
  3. Register prefabs in the Prefabs list.
  4. Set item count and bind data via code.
FieldTypeDefaultDescription
DirectionListViewDirectionVerticalScroll axis
Gapfloat0Gap between items
VirtualizedbooltrueEnable object pooling
Default Pool Capacityint10Initial pool size per type
Overscan Countint2Extra items rendered offscreen
PrefabsList-Prefab registrations (typeId + prefab + capacity)

Code Usage

AutoUI Builder (Single Type)

AutoUI.Create()
.WidthFill().HeightFill()
.ListView(ListViewDirection.Vertical)
.Prefab(itemPrefab, 15)
.ItemCount(data.Count)
.GetItem(i => data[i])
.BindItem((go, index, data) =>
{
go.GetComponent<MyItemView>().Bind((MyData)data);
})
.UnbindItem((go, index) =>
{
go.GetComponent<MyItemView>().Unbind();
})
.Capture(lv => m_ListView = lv)
.End()
.Build();

AutoUI Builder (Multi-Type)

AutoUI.Create()
.WidthFill().HeightFill()
.ListView(ListViewDirection.Vertical)
.Prefab(0, contentPrefab, 15) // Type 0: content rows
.Prefab(1, headerPrefab, 5) // Type 1: section headers
.ItemCount(flatList.Count)
.GetItem(i => flatList[i].Data)
.GetItemType(i => flatList[i].IsHeader ? 1 : 0)
.BindItem((go, index, data) =>
{
if (flatList[index].IsHeader)
go.GetComponentInChildren<TMP_Text>().text = (string)data;
else
go.GetComponent<MyItemView>().Bind((MyData)data);
})
.Capture(lv => m_ListView = lv)
.End()
.Build();

Quick Setup (Convenience)

m_ListView.Setup(
prefab: itemPrefab,
count: data.Count,
bindItem: (go, index, data) => { /* bind */ },
getData: i => data[i]
);

Runtime Updates

m_ListView.ItemCount = newData.Count;
m_ListView.RefreshItems(); // Deferred refresh
m_ListView.RefreshItemsImmediate(); // Synchronous
m_ListView.RefreshItem(5); // Single item
m_ListView.ScrollToItem(50, ScrollAlignment.Center); // Scroll to item
m_ListView.InvalidateItemSize(10); // Recalculate item size
m_ListView.Clear(); // Remove all

VirtualListView<T> (Zero-GC Generic)

For struct data or when you need to avoid boxing:

public class MyScreen : MonoBehaviour
{
private VirtualListView<PlayerData> m_List;
void Start()
{
// VirtualListView<T> is added via AddComponent
m_List = gameObject.AddComponent<VirtualListView<PlayerData>>();
m_List.GetItemCallback = i => m_Players[i]; // No boxing
m_List.BindItemCallback = (go, i, data) => // Typed data
{
go.GetComponent<PlayerView>().Bind(data);
};
m_List.ItemCount = m_Players.Count;
m_List.RefreshItems();
}
}

IListViewItem<T> Interface

Implement on prefab root for auto-binding without callbacks:

public class PlayerView : MonoBehaviour, IListViewItem<PlayerData>
{
public void Bind(int index, PlayerData data)
{
// Bind typed data — no boxing, no casting
}
public void Unbind() { }
}

Methods

MethodDescription
RegisterPrefab(int typeId, GameObject prefab, int capacity)Register a prefab type
Setup(GameObject prefab, int count, Action bind, Func getData)Quick setup
RefreshItems()Deferred refresh
RefreshItemsImmediate()Synchronous refresh
RefreshItem(int index)Refresh single item
ScrollToItem(int index, ScrollAlignment alignment, bool animated)Scroll to item
InvalidateItemSize(int index)Recalculate item height
InvalidateItemSizesFrom(int startIndex)Recalculate from index onward
Clear()Remove all items

Properties

PropertyTypeDescription
ItemCountintTotal item count (set to trigger refresh)
DirectionListViewDirectionVertical or Horizontal
GapfloatGap between items
OverscanCountintExtra items rendered offscreen
VisibleCountintCurrently rendered items
CurrentRangeVisibleRangeStart/end indices of visible items
VirtualizedboolPool mode toggle
ScrollViewAutoLayoutScrollViewInternal scroll view

Callbacks

CallbackSignatureDescription
GetItemCallbackFunc<int, object>Return data for index
GetItemTypeCallbackFunc<int, int>Return prefab type for index
BindItemCallbackAction<GameObject, int, object>Bind data to item
UnbindItemCallbackAction<GameObject, int>Cleanup on recycle

Interfaces

  • IListViewItem — Implement on prefab for auto-binding.
  • IListViewItem<T> — Zero-GC typed binding (use with VirtualListView<T>).
  • IListViewDelegate — Full data source control (overrides callbacks).

Builder API

MethodDescription
.Prefab(GameObject, int)Register type-0 prefab
.Prefab(int, GameObject, int)Register typed prefab
.PoolCapacity(int)Set default pool size
.Overscan(int)Set overscan count
.Virtualized(bool)Enable/disable pooling
.ItemCount(int)Set total items
.Delegate(IListViewDelegate)Set delegate
.GetItem(Func<int, object>)Set data callback
.GetItemType(Func<int, int>)Set type callback
.BindItem(Action<GameObject, int, object>)Set bind callback
.UnbindItem(Action<GameObject, int>)Set unbind callback
.Capture(Action<ListView>)Capture reference (e.g. .Capture(lv => m_List = lv))
.End()Return to parent LayoutBuilder

Keyboard Navigation

ListView implements IMoveHandler for arrow key / gamepad navigation. Click the list to select it, then use arrow keys to move focus.

DirectionVertical ListHorizontal List
Up / LeftPrevious itemPrevious item
Down / RightNext itemNext item
Cross-axisNot consumed (passes through)Not consumed
// Listen for focus changes
m_ListView.OnFocusedIndexChanged += index =>
{
Debug.Log($"Focused: {index}");
};
// Set focus programmatically
m_ListView.FocusedIndex = 5;
Property / EventTypeDescription
FocusedIndexintCurrently focused item (-1 = none)
OnFocusedIndexChangedAction<int>Fired when focused index changes

Notes

  • Virtualized = false instantiates all items upfront. Use for small, fixed-size lists.
  • Overscan renders extra items offscreen to prevent flicker during fast scrolling.
  • Pool items are marked IsVirtualized = true internally to optimize enable/disable cycles.
  • For best performance with struct data, use VirtualListView<T> to avoid boxing allocations.