InfiniteScrollView
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.
| Namespace | OxGKit.InfiniteScrollView |
| Type | [RequireComponent(typeof(ScrollRect))] public abstract class InfiniteScrollView : UIBehaviour |
| Source | InfiniteScrollView.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
- Initialize Pool On Awake: check
initializePoolOnAwakeon the Inspector, and the list initializes the pool automatically inAwake. - Manual initialization: call
await InitializePool(args)yourself (argsis 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
| Member | Description |
|---|---|
public bool initializePoolOnAwake | Whether to initialize the pool automatically in Awake, default false. |
public int cellPoolSize | Number of cells in the pool, default 20; must cover the visible count plus the cells reserved by extendVisibleRange. |
public InfiniteCell cellPrefab | The cell prefab (a Prefab with an InfiniteCell subclass attached). |
public DataOrder dataOrder | Data order (DataOrder), default Normal; Reverse reverses the display order of the data indexes. |
public float extendVisibleRange | Extended 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 snapAlign | Snap alignment (SnapAlign), default Start. |
public Padding padding | Content 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
| Event | Description |
|---|---|
public Action<Vector2> onValueChanged | Invoked while the list scrolls (parameter is the ScrollRect normalizedPosition). |
public Action onRectTransformDimensionsChanged | Invoked when the list's RectTransform dimensions change (useful to re-layout). |
public Action onRefreshed | Invoked after every full refresh (Refresh) completes. |
public Action<InfiniteCell> onCellSelected | Invoked when a cell is clicked/selected (the cell must call base.OnClick(), see InfiniteCell.OnClick). |
Initialization and Data Operations
Methods Overview
| Method | Description |
|---|---|
| InitializePool | Initializes the cell pool (prerequisite for every operation). |
| DataCount | Gets the data count (equals the cell count). |
| Add | Appends cell data (no refresh by default). |
| Insert | Inserts cell data at the specified index. |
| Remove | Removes the cell data at the specified index. |
| Clear | Clears 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
| Method | Description |
|---|---|
| Refresh | Full refresh (recalculates the content size and refreshes visible cells). |
| RefreshVisibleCells | Only 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.
| Parameter | Description |
|---|---|
refreshOnNextScroll | When true, does not refresh immediately — only marks a full refresh to run on the next scroll (onRefreshed is not invoked at marking time). |
recycleActiveCells | When 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
| Method | Description |
|---|---|
| ScrollToTop / ScrollToBottom | Scrolls to the top/bottom immediately (vertical). |
| ScrollToLeft / ScrollToRight | Scrolls to the far left/right immediately (horizontal). |
| VerticalNormalizedPosition / HorizontalNormalizedPosition | Gets the normalized scroll position. |
| IsAtTop / IsAtBottom / IsAtLeft / IsAtRight | Whether 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
| Method | Description |
|---|---|
| Snap | Moves to the cell at the specified index. |
| SnapFirst | Moves to the first cell. |
| SnapMiddle | Moves to the middle cell. |
| SnapLast | Moves 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):
durationless than or equal to 0 → sync, positions immediately.durationgreater than 0 → async tween (UniTask), moves smoothly withindurationseconds (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:
| Member | Description |
|---|---|
protected List<InfiniteCellData> _dataList | The data list. |
protected List<InfiniteCell> _cellList | The active cell list mapped to the data (indexes not displayed are null). |
protected Queue<InfiniteCell> _cellPool | The 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). |