Skip to main content

InfiniteScrollView

Important Attention Reminder

Coding Style wiki


InfiniteScrollView is the abstract base class of the infinite list (inherits UIBehaviour and requires a UGUI ScrollRect on the same object via RequireComponent). It recycles cells (InfiniteCell) efficiently with an object-pool concept, so only a handful of GameObjects can carry a large amount of list data; four implementations are built in (ScrollView Variants), and you can also inherit from it to customize your own list.

NamespaceOxGKit.InfiniteScrollView
Type[RequireComponent(typeof(ScrollRect))] public abstract class InfiniteScrollView : UIBehaviour
SourceInfiniteScrollView.cs
using OxGKit.InfiniteScrollView;

Quick Start

using Cysharp.Threading.Tasks;
using OxGKit.InfiniteScrollView;
using UnityEngine;

public class ListLauncher : MonoBehaviour
{
// The list component attached to the ScrollRect object in the scene (vertical list as an example)
public VerticalInfiniteScrollView scrollView;

private async void Start()
{
// 1. Initialize the pool (can be skipped if initializePoolOnAwake is checked on the Inspector)
await this.scrollView.InitializePool();

// 2. Add data in batch, then refresh once (Add does not refresh by default)
for (int i = 0; i < 1000; i++)
this.scrollView.Add(new InfiniteCellData(new Vector2(100f, 100f), $"Item {i}"));
this.scrollView.Refresh();

// 3. Cell click event (the cell must call base.OnClick() to trigger it)
this.scrollView.onCellSelected += (cell) => Debug.Log($"Selected index: {cell.cellData.index}");

// 4. Move to a specific index (0.1-second tween)
this.scrollView.Snap(50, 0.1f);
}
}

General Rules

Initialization Flow

  1. Initialize Pool On Awake: check initializePoolOnAwake on the Inspector, and the list initializes the pool automatically in Awake.
  2. Manual initialization: call await InitializePool(args) yourself (args is forwarded to every cell's OnCreate).

Important Every operation (Add / Insert / Remove / Clear / Refresh / Snap) only takes effect after initialization completes; otherwise the warning Please InitializePool first!!! is output.

Cell Pooling

  • On initialization, cells are pre-instantiated according to cellPoolSize; while scrolling, cells are only assigned to the data within the visible range (extended by extendVisibleRange), and cells scrolled out of range are recycled back into the pool for reuse.
  • Data and view are separated: the data (InfiniteCellData) is held by the scroll view, and a cell only gets its data bound (triggering OnRefresh) when it is placed for display.

Attention cellPoolSize must be large enough to cover the visible count + the extra cells reserved by extendVisibleRange; an exhausted pool outputs the error The cell display error occurred, not enough cells in the cell pool!!!.

Reminder The content node does not need a LayoutGroup / ContentSizeFitter — cell positions are calculated by the scroll view itself; make sure the ScrollRect's scroll axis (Vertical / Horizontal) matches the implementation you use.


Members

MemberDescription
public bool initializePoolOnAwakeWhether to initialize the pool automatically in Awake, default false.
public int cellPoolSizeNumber of cells in the pool, default 20; must cover the visible count plus the cells reserved by extendVisibleRange.
public InfiniteCell cellPrefabThe cell prefab (a Prefab with an InfiniteCell subclass attached).
public DataOrder dataOrderData order (DataOrder), default Normal; Reverse reverses the display order of the data indexes.
public float extendVisibleRangeExtended distance of the visible range — cells are placed ahead outside the viewport for smoother scrolling.
public ScrollRect scrollRect { get; protected set; }The UGUI ScrollRect on the same object (obtained on initialization).
public SnapAlign snapAlignSnap alignment (SnapAlign), default Start.
public Padding paddingContent padding (Padding).
public int visibleCount { get; protected set; }Current number of cells within the visible range.
public int lastMaxVisibleCount { get; protected set; }The maximum visible count reached so far.
public float lastVisibleRangeSize { get; protected set; }The last calculated visible-range size (including extendVisibleRange).
public bool isVisibleRangeFilled { get; protected set; }Whether the visible range is filled with cells (useful to check if the data is enough to fill the view).
public bool isInitialized { get; protected set; }Whether the pool initialization has completed.

Events

EventDescription
public Action<Vector2> onValueChangedInvoked while the list scrolls (parameter is the ScrollRect normalizedPosition).
public Action onRectTransformDimensionsChangedInvoked when the list's RectTransform dimensions change (useful to re-layout).
public Action onRefreshedInvoked after every full refresh (Refresh) completes.
public Action<InfiniteCell> onCellSelectedInvoked when a cell is clicked/selected (the cell must call base.OnClick(), see InfiniteCell.OnClick).

Initialization and Data Operations

Methods Overview

MethodDescription
InitializePoolInitializes the cell pool (prerequisite for every operation).
DataCountGets the data count (equals the cell count).
AddAppends cell data (no refresh by default).
InsertInserts cell data at the specified index.
RemoveRemoves the cell data at the specified index.
ClearClears all data and refreshes.

InitializePool

public virtual async UniTask InitializePool(object args = null)

Initializes the pool: instantiates cellPrefab under content according to cellPoolSize and awaits each cell's OnCreate (args is forwarded to every cell). Existing content children and the data list are cleared, and the scroll listener is registered. Calling it again after initialization (isInitialized is true) returns immediately.

// Basic initialization
await this.scrollView.InitializePool();

// Pass custom arguments to every cell's OnCreate
await this.scrollView.InitializePool(myCellInitArgs);

Attention Initialization fails with an error when cellPrefab is null.

DataCount

public int DataCount()

Gets the data count (equals the cell count).

Add

public virtual void Add(InfiniteCellData data, bool refresh = false)

Appends one InfiniteCellData to the end.

Reminder refresh defaults to false — adding in batch and calling Refresh once afterwards is more efficient.

for (int i = 0; i < 100; i++)
this.scrollView.Add(new InfiniteCellData(new Vector2(100f, 100f), $"Item {i}"));
this.scrollView.Refresh();

Insert

public virtual bool Insert(int index, InfiniteCellData data, bool refresh = true)

Inserts data at the specified index (index may equal DataCount(), which appends to the end); subsequent data indexes are updated automatically. Returns false when the index is out of range. When refresh is true, it refreshes by first recycling all active cells (Refresh(false, true)).

Remove

public virtual bool Remove(int index, bool refresh = true)

Removes the data at the specified index (the data gets Disposed); subsequent data indexes shift forward automatically. Returns false when the index is out of range.

Attention VerticalInfiniteScrollView and HorizontalInfiniteScrollView override this method to adjust the content position after removal, see ScrollView Variants.

Clear

public virtual void Clear()

Stops scrolling and resets the content position, recycles all active cells, Disposes all data, then refreshes the view.


Refreshing

Methods Overview

MethodDescription
RefreshFull refresh (recalculates the content size and refreshes visible cells).
RefreshVisibleCellsOnly recalculates and refreshes the cells within the visible range.

Refresh

public abstract void Refresh(bool refreshOnNextScroll = false, bool recycleActiveCells = false)

Full refresh: recalculates the content size → refreshes the visible cells → invokes the onRefreshed event.

ParameterDescription
refreshOnNextScrollWhen true, does not refresh immediately — only marks a full refresh to run on the next scroll (onRefreshed is not invoked at marking time).
recycleActiveCellsWhen true, recycles all active cells back into the pool before refreshing (does not clear the data list).

Reminder If the viewport size is not ready yet (0, common on the first UI frame), the refresh is automatically deferred to LastPostLateUpdate.

RefreshVisibleCells

public void RefreshVisibleCells()

Only recalculates the visible range and places/recycles cells (does not recalculate the content size); called internally while the list scrolls.


Scrolling and State

Methods Overview

MethodDescription
ScrollToTop / ScrollToBottomScrolls to the top/bottom immediately (vertical).
ScrollToLeft / ScrollToRightScrolls to the far left/right immediately (horizontal).
VerticalNormalizedPosition / HorizontalNormalizedPositionGets the normalized scroll position.
IsAtTop / IsAtBottom / IsAtLeft / IsAtRightWhether the view has scrolled to each boundary.

ScrollToTop / ScrollToBottom

public void ScrollToTop()
public void ScrollToBottom()

Directly sets verticalNormalizedPosition to scroll to the top (1) / bottom (0); for vertical lists.

ScrollToLeft / ScrollToRight

public void ScrollToLeft()
public void ScrollToRight()

Directly sets horizontalNormalizedPosition to scroll to the far left (0) / far right (1); for horizontal lists.

VerticalNormalizedPosition / HorizontalNormalizedPosition

public float VerticalNormalizedPosition()
public float HorizontalNormalizedPosition()

Gets the normalized scroll position (0~1) of the ScrollRect; returns -1 when scrollRect is not obtained yet.

IsAtTop / IsAtBottom / IsAtLeft / IsAtRight

public bool IsAtTop()
public bool IsAtBottom()
public bool IsAtLeft()
public bool IsAtRight()

Whether the view has scrolled to the top/bottom/left/right boundary (adjusted internally by the content direction pivot). The boundary states are updated on visible refresh — vertical implementations update Top / Bottom, horizontal implementations update Left / Right.

// Load more when scrolled to the bottom (vertical list)
this.scrollView.onValueChanged += (pos) =>
{
if (this.scrollView.IsAtBottom())
this.LoadMore();
};

Snapping

Methods Overview

MethodDescription
SnapMoves to the cell at the specified index.
SnapFirstMoves to the first cell.
SnapMiddleMoves to the middle cell.
SnapLastMoves to the last cell.

Snap

public abstract void Snap(int index, float duration)

Moves to the cell at the specified index, aligned according to snapAlign (Start / Center / End):

  • duration less than or equal to 0 → sync, positions immediately.
  • duration greater than 0 → async tween (UniTask), moves smoothly within duration seconds (calling again interrupts the previous tween).

When finished, the target cell's OnSnap is invoked if it is displayed; when dataOrder is Reverse, the index is converted automatically.

// Jump to index 10 immediately
this.scrollView.Snap(10, 0);

// Move smoothly to index 10 within 0.1 seconds
this.scrollView.Snap(10, 0.1f);

SnapFirst

public void SnapFirst(float duration)

Moves to the first cell (same as Snap(0, duration)).

SnapMiddle

public void SnapMiddle(float duration)

Moves to the middle cell (index (DataCount() - 1) / 2).

SnapLast

public void SnapLast(float duration)

Moves to the last cell (same as Snap(DataCount() - 1, duration)).

// Chat-room-like usage: auto-scroll to the newest message after appending
this.chatScrollView.Add(cellData);
this.chatScrollView.Refresh();
this.chatScrollView.SnapLast(0.1f);

Type Definitions

DataOrder

public enum DataOrder
{
Normal,
Reverse
}

Data order; Reverse reverses the display order of the data indexes (laid out from the opposite direction).

SnapAlign

public enum SnapAlign
{
Start = 1,
Center = 2,
End = 3,
}

Snap alignment: aligns the target cell to the start / center / end of the visible range.

Padding

[Serializable]
public class Padding
{
public int top;
public int bottom;
public int left;
public int right;
}

Content padding (top / bottom / left / right).


Inheritance Extension (protected)

When inheriting InfiniteScrollView (or a built-in implementation) to customize a list, the following protected members are available:

MemberDescription
protected List<InfiniteCellData> _dataListThe data list.
protected List<InfiniteCell> _cellListThe active cell list mapped to the data (indexes not displayed are null).
protected Queue<InfiniteCell> _cellPoolThe cell pool.
protected abstract void DoRefreshVisibleCells()Implements the cell placement/recycling logic of the visible range.
protected abstract void DoRefresh(bool refreshOnNextScroll, bool recycleActiveCells)Implements the full refresh logic.
protected abstract UniTask DoDelayRefresh(bool refreshOnNextScroll, bool recycleActiveCells)Implements the deferred refresh (when the viewport size is not ready).
protected void SetupCell(InfiniteCell cell, int index, Vector2 pos)Binds data to a cell and places it at the given position for display.
protected void RecycleCell(int index)Recycles the cell at the index back into the pool (invokes OnRecycle).
protected void RecycleActiveCells()Recycles all active cells (does not clear the data list).
protected void ClearAllData()Disposes all data.
protected void DoSnapping(int index, Vector2 target, float duration)Runs the snap movement tween.
protected float CalculateSnapPos(ScrollType scrollType, SnapAlign snapPosType, float originValue, InfiniteCellData cellData)Calculates the snap target position according to the alignment.
protected bool IsInitialized()Checks whether initialization is done (outputs a warning if not).