VeloScroll
A high-performance recycling ScrollView for uGUI. Render thousands of rows with a bounded handful of live cells, and configure the whole thing in the Inspector — no code required. Pure C#, built by composition over Unity's own ScrollRect, with no native binaries.
RESULT=PASS on both Mono and IL2CPP. See How It Works.⚡ First list in 5 minutes
Add the VeloScroll View component, press Play — 10k items render with a handful of live cells.
🔧 No-code authoring
Component + custom inspector + GameObject > UI > VeloScroll menus + a data-source asset. Zero scripting.
▦ List & Grid
Single-track lists or fixed column/row grids, vertical or horizontal, all recycled.
↕ Variable sizes
Per-cell heights or widths; the visible range is found by binary search, cells stay bounded.
✨ Animated cells
Scroll-driven focus-scale, fade and depth presets — juice with no code.
🎯 Snap & jump
Snap to the nearest cell on drag-end and animated ScrollToIndex with an OnSnap event.
📥 Import CSV / JSON
Read real CSV or JSON into a list or table and pick which columns to show — quoted commas and nested objects handled.
This guide starts with installation and a five-minute Quick Start, explains how the recycler works, then walks each feature, the no-code editor tooling, and the full scripting API.
Installation
Import the package, make sure uGUI and the Input System are present, and add a component.
In
Window → Package Manager → My Assets, download and import VeloScroll. Everything lands under Assets/VeloScroll/ — that is the only folder that ships.The runtime depends only on
com.unity.ugui (Unity's standard UI), which is present in virtually every project. No other package is required for the core component.The scaffolding menus and sample scenes wire an
EventSystem for the new Input System. Make sure com.unity.inputsystem is installed so pointer drags are routed. The core recycler itself uses uGUI drag handlers and works under either input backend.Add a VeloScroll View via
Add Component → UI → VeloScroll → VeloScroll View, or scaffold a ready-to-play one from GameObject → UI → VeloScroll → Recycling List.What gets installed
Assets/VeloScroll/
Runtime/ // VeloScrollView, Recycler, CellPool, layouts, animator, data sources
Editor/ // Custom inspector, scaffolding menus, Welcome window, project validator
Demo/ // The interactive WebGL-ready demo scene (Build Index 0)
Samples/ // Per-feature example scenes + the no-code template scene
Documentation/ // You are here
Tests/ // EditMode + PlayMode tests (run headless via Unity -runTests)Project
Demo/ and Samples/ are never referenced by the runtime, so you can delete them and the asset still compiles. Only Assets/VeloScroll/ ships — nothing is written outside it, and there are no network calls.Quick Start
Your first recycling list in about five minutes — no code, then one optional line of script.
Option A — Add the component
In a scene with a Canvas, add an empty UI GameObject (or any RectTransform under the Canvas).
Add Component → UI → VeloScroll → VeloScroll View. The component self-builds a viewport and content and drives a ScrollRect for you — nothing else to wire.With all-default fields it renders a built-in deterministic sample list immediately. Scroll it: the item count is in the hundreds by default, but only a handful of cells are ever alive.
Select the view while playing — the inspector's Preview line shows
Items, Live cells and Created. Live cells stay bounded as you scroll.Option B — Scaffold from the menu
Prefer a one-click setup? Use GameObject → UI → VeloScroll → Recycling List. It creates a Canvas and EventSystem only if the scene lacks them, drops in a pre-wired VeloScroll View, and registers full Undo. Press Play and it renders the sample. (There is a Recycling Grid variant too.)
Make it 10,000 items from code
One call turns the same view into a 10,000-row list bound to your own data. The recycler binds only the cells near the viewport, so the binder runs O(visible), never O(count):
using UnityEngine;
using VeloScroll;
public class QuickStart : MonoBehaviour
{
[SerializeField] private VeloScrollView view;
void Start()
{
// 10,000 items, a handful of live cells, no per-item instantiation while scrolling.
view.SetData<VeloScrollTextCell>(10000, (i, c) => c.SetContent("Item " + i, ""));
}
}
SetData / SetDataSource marks the view as code-configured, so its automatic build-on-play will defer to you and never overwrite your data with the sample.How It Works
Recycling (a.k.a. virtualization) in one idea: reuse a small, bounded pool of cells as the list scrolls.
A naive scroll list instantiates one cell GameObject per item, so a 10,000-row list creates 10,000 cells — slow to build and heavy on memory. VeloScroll instead keeps only the cells that are near the viewport alive. As a cell scrolls out one edge, it is returned to a pool and immediately reused to show the item appearing at the other edge. The count of live cells therefore stays roughly constant regardless of how many items the data has.
- The driven
ScrollRect'sonValueChangedreports the new scroll offset. - The active layout (List or Grid) maps that offset to the range of item indices currently visible. For variable sizes a cumulative-offset cache is searched by binary search.
- The recycler returns any now-off-screen cells to the cell pool and pulls cells back out for the items entering view.
- The active data source binds each incoming cell (
Bind(index, cell)) — only for the small visible set. - If an animation preset is set, the animator nudges each visible cell's scale/opacity by its distance from the viewport center.
visible + 2 × buffer cells — a small constant independent of your item count. Tests confirm 12 live cells render 100, 10k and 50k items; in the shipped player 9 cells render 10,000 items. A 1,000-tick scroll stays at the same pool size (no growth), and the steady-state recycler path measures zero managed allocation.Built by composition, not subclassing
VeloScroll drives a standard uGUI ScrollRect by composition; it never subclasses it. That means it coexists with any existing ScrollRect setup, and added to a bare GameObject it self-builds the viewport + content it needs. There is a blank-first-frame guard, too: because UI rects are 0×0 until a layout pass runs, the first build defers one frame until the viewport has a real size.
Feature: Recycling List
A single-track recycled list, vertical or horizontal, with uniform cell sizes.
The default layout is a list: one column when scrolling vertically, one row when scrolling horizontally. Set the scroll Axis, the uniform Cell Main Size (height for vertical, width for horizontal), Spacing, inner Padding and an off-screen Buffer (extra cells kept alive just outside the viewport for smoother fast scrolling) in the Inspector.
// Switch back to a single-track list from code (e.g. after using a grid):
view.SetListLayout();
// Drive it from a typed binder. The default cell is VeloScrollTextCell.
view.SetData<VeloScrollTextCell>(10000, (i, c) => c.SetContent("Row " + i, "subtitle"));
VeloScrollTextCell (a background image plus uGUI title/subtitle text), so the zero-setup path needs no prefab and no TextMeshPro. Provide your own prefab holding a VeloScrollCell subclass when you want custom visuals.Feature: Grid
A fixed-count grid recycler — columns when scrolling vertically, rows when scrolling horizontally.
Switch the Layout Kind to Grid and set the Grid Cross Count (number of columns for a vertical scroll, or rows for a horizontal scroll) and the Grid Cross Spacing. The grid recycles exactly like the list — only the visible lines are alive.
// 3-column grid (vertical scroll), 6px between columns:
view.SetGridLayout(3, 6f);
view.SetData<VeloScrollTextCell>(5000, (i, c) => c.SetContent("Cell " + i, ""));
GameObject → UI → VeloScroll → Recycling Grid scaffolds a view already set to a 3-column grid.Feature: Variable Cell Sizes
Per-cell heights (vertical) or widths (horizontal), with the live set still bounded.
Uniform cells are the fast default, but real lists often need rows of different sizes. Provide a size function and the list layout measures each item along the scroll axis, caches cumulative offsets, and finds the visible range by binary search — so even a non-uniform 10k-row list keeps a bounded live-cell set. Pass null to revert to uniform sizing.
// Alternating tall/short rows; the recycler stays bounded.
view.SetVariableSizes(i => (i % 2 == 0) ? 120f : 72f);
view.SetData<VeloScrollTextCell>(10000, (i, c) => c.SetContent("Item " + i, "variable height"));
Feature: Animated Cells
Scroll-driven cell emphasis — the no-code "juice" layer, on a real virtualizer.
Pick an Animation Kind and the animator shapes each visible cell by its distance from the viewport center. The math is a pure, testable function of that distance, and writes only when a value changes (no per-frame allocation):
| Kind | Effect |
|---|---|
None | Animation disabled (default). |
FocusScale | Edge cells shrink toward the configured strength; the centered cell is full size. |
Fade | Edge cells fade out via a CanvasGroup; centered cell is fully opaque. |
Depth | A combined gentle scale + fade for a depth-of-focus look. |
Parallax | In V1, a gentle depth-style focus (true cross-axis parallax is on the roadmap). |
// Strength 0..1 controls how strongly edge cells shrink/fade; curve is optional.
view.SetAnimation(VeloScrollAnimationKind.FocusScale, 0.35f);
// Shape the falloff with an AnimationCurve (empty = linear):
view.SetAnimation(VeloScrollAnimationKind.Depth, 0.5f, myCurve);
Feature: Snapping / Paging
Snap to the nearest cell when a drag ends, and jump to any index instantly or with an animated scroll.
Enable Snapping and choose where the snapped cell lands in the viewport with Snap Align (Start, Center or End). When the user releases a drag, VeloScroll animates the content so the nearest cell settles at that alignment over the configured Snap Duration, using a smoothstep ease (or your own curve). Each settle raises the OnSnap event with the resolved item index.
// Turn on drag-end snapping, center-aligned, quarter-second ease.
view.SetSnapping(true, VeloScrollSnapAlign.Center, 0.25f);
// Get notified when a snap settles (drag-end or animated jump):
view.OnSnap += index => Debug.Log("Snapped to " + index);
// Jump to an item. animated:true performs an eased scroll and then fires OnSnap.
view.ScrollToIndex(500, VeloScrollSnapAlign.Center, animated: true);
Feature: Dynamic Data
Change the count or the contents at runtime — the list re-binds without an instantiation storm.
VeloScroll never stores your model; it asks the data source for a Count and binds cells on demand. When your data changes, call the matching operation and the recycler resizes the content and refreshes the window without rebuilding the world:
| Call | Use when |
|---|---|
Reload() | The count or contents changed and you want a full re-evaluation of the visible window. |
InsertItems(index, count) | You inserted rows in your source (the new count comes from your source). |
RemoveItems(index, count) | You removed rows in your source. |
RefreshVisible() | Item contents changed but the count did not — re-binds the visible cells in place. |
// A live count delegate keeps the list in sync with a growing/shrinking collection.
view.SetData<VeloScrollTextCell>(() => items.Count, (i, c) => c.SetContent(items[i].Title, ""));
items.Add(newItem);
view.Reload(); // content resized, window refreshed, correct re-bind, no NaN
Feature: Masonry / Staggered Grid
A Pinterest-style grid: fixed columns, but every cell has its own height, packed into the shortest column.
Where the regular Grid keeps a uniform cell size, the staggered grid takes a per-item main size and places each item into whichever column is currently shortest — the masonry look — while staying fully recycled and bounded.
// 3 columns, 6px cross spacing, each item's height from your model.
view.SetStaggeredGrid(3, 6f, i => 80f + items[i].ExtraHeight);
view.SetData<VeloScrollTextCell>(items.Count, (i, c) => c.SetContent(items[i].Title, ""));
Feature: Sticky Section Headers
Grouped lists with a header per section that floats, pinned to the top, until the next header pushes it up.
Describe your list as sections — each one header followed by its rows — and VeloScroll flattens them into the recycler's index space. Headers and rows recycle through separate pools. With sticky on, the current section's header is mirrored into a floating overlay pinned to the viewport top, and the next section's incoming header shoves it out for a seamless hand-off.
// 40 sections, 12 rows each; 50px headers, 44px rows; sticky on.
view.SetSections(
sectionCount: 40,
rowsInSection: s => 12,
headerSize: 50f, rowSize: 44f,
bindHeader: (s, cell) => ((VeloScrollTextCell)cell).SetContent("Section " + s, ""),
bindRow: (s, r, cell) => ((VeloScrollTextCell)cell).SetContent("Row " + r, ""),
sticky: true);
Feature: Looping & Auto-Scroll
Endless, seamless loops for pickers, carousels and tickers — optionally self-scrolling.
Looping repeats your data as one long, seamless run (the virtual extent is capped so scroll precision stays sub-pixel), wrapping each index back onto your real data. Pair it with auto-scroll for a hands-off marquee that wraps with no visible seam.
// A spinning value picker that wraps forever.
view.SetData<VeloScrollTextCell>(24, (i, c) => c.SetContent(hours[i], ""));
view.SetLooping(true);
// A news ticker: loop + continuous auto-scroll at 120 px/s.
view.SetLooping(true);
view.SetAutoScroll(120f);
Feature: Pull-to-Refresh & Load-More
The familiar mobile gesture: over-drag past an end to refresh or load the next page.
Enable it, set a threshold, and wire the events (in code or the Inspector). Over-pull past the start fires OnPullToRefresh; past the end fires OnLoadMore — perfect for infinite feeds.
view.SetPullToRefresh(true, thresholdPx: 90f);
view.OnPullToRefresh.AddListener(() => ReloadFromServer());
view.OnLoadMore.AddListener(() => AppendNextPage());
Feature: Multi-Prefab Cells
Mix different cell types in one list — ads between posts, dividers, rich vs plain rows — all bounded.
Register several cell prefabs plus a per-index type selector. Each type recycles through its own pool, so a heterogeneous list stays as bounded and allocation-free as a uniform one. The untyped binder casts each cell to the type it was handed.
// Two cell types alternating, 20,000 items, still a bounded handful of live cells.
view.SetMultiPrefabData(20000, new[] { postPrefab, adPrefab },
typeOf: i => (i % 8 == 7) ? 1 : 0, // every 8th row is an ad
bind: (i, cell) => Populate(i, cell));
Feature: Themes & Animation Presets
Reusable ScriptableObject assets for look-and-feel — assign in the Inspector, no code required.
A Theme asset recolors the built-in text cells (background, title, subtitle) and drives spacing; it is applied on every bind through a zero-allocation hook. An Animation Preset asset bundles a scroll animation (kind + strength + curve) and overrides the inline settings. Create both from Assets › Create › VeloScroll, drop them on the component, or assign from code.
view.SetTheme(darkTheme); // recolors cells + spacing
view.SetAnimationPreset(focusPreset); // overrides inline animation
Feature: Table / Columns (frozen header)
A virtualized data grid: aligned columns, a header row that stays pinned at the top, thousands of rows recycled.
Describe your columns (a title + a pixel width; a width of 0 flexes to share the leftover space), give a row count and a string[] per row, and VeloScroll renders a table whose rows recycle like any list — bounded no matter how many rows. The header row is mirrored into a frozen overlay pinned to the top.
var columns = new[] {
new VeloScrollColumn("ID", 80),
new VeloScrollColumn("Name", 0), // flex
new VeloScrollColumn("Score", 100),
};
view.SetTable(columns, 10000, row => new[] { ""+row, data[row].Name, ""+data[row].Score });
Feature: Drag-to-Reorder
Drag a cell to a new position; on drop you move the data and the list reflects it.
Turn it on with a callback. Press-drag a cell and it lifts and follows the pointer; on release VeloScroll computes the drop index and raises onReorder(from, to) so you mutate your own list, then it reloads into the new order. Your data stays the single source of truth.
view.SetReorderable(true, (from, to) => {
var item = list[from];
list.RemoveAt(from);
list.Insert(to, item); // the view reloads to show the new order
});
Feature: Nested Scrolling
Horizontal lists inside a vertical list (a "shelf of shelves") that route drags correctly.
A plain uGUI ScrollRect swallows the whole drag, so a vertical swipe started on an inner horizontal list never reaches the outer one. Add VeloScrollNestedRouter to the inner scroll's GameObject: it forwards drags that belong to the parent — those orthogonal to the inner axis, or continuing past the inner's edge — to the nearest parent scroll, while the inner keeps handling its own axis.
// On each inner (e.g. horizontal) VeloScrollView's GameObject:
innerGo.AddComponent<VeloScrollNestedRouter>(); // auto-finds the parent scroll
Feature: Import CSV / JSON
A universal file reader: turn a real CSV or JSON payload into a list or a table, and let the user choose which columns to show.
Pass raw text to VeloScrollImport and it returns a VeloScrollTabularData — the discovered columns plus the rows. It is deliberately forgiving: it strips byte-order marks, sniffs the CSV delimiter (comma / semicolon / tab / pipe), honours RFC-4180 quoting (quoted commas, embedded newlines, doubled "" escapes) and pads ragged rows; for JSON it locates the record array (top-level, a wrapper property, or a single object), unions every record's keys into the schema and flattens nested objects into dotted columns (address.city). It is pure C# and culture-invariant, so it runs in players too — not just the editor.
Every column starts included; toggle any of them and the projection (IncludedHeader() / IncludedRow(i)) collapses to just the chosen columns — the data is never mutated, so you can re-include later. Then drive the view with the ApplyAsTable / ApplyAsList extensions.
var data = VeloScrollImport.FromCsv(csvText); // or .FromJson(json) / .Auto(text) / .FromFile(path)
data.SetIncluded("internal_id", false); // the user excludes a column they don't want
view.ApplyAsTable(data); // virtualized table of the included columns…
view.ApplyAsList(data); // …or a list: first column as title, the rest as subtitle
Let the user pick a real file at runtime. VeloScrollFilePicker opens the platform file dialog and hands back the chosen file's text. In a WebGL build it opens the browser's native file picker (via a bundled .jslib — System.IO cannot read a user-selected file in WebGL); in the editor it shows an open-file panel, so the same button works while you iterate. Combine it with Auto (which sniffs CSV vs JSON) for a complete "import my data" button:
VeloScrollFilePicker.Open(".csv,.json,.txt", (name, text) => {
var data = VeloScrollImport.Auto(text); // CSV vs JSON auto-detected from the contents
view.ApplyAsTable(data);
});
Editor Tools / No-Code
VeloScroll is built to be used without writing a line of code. The component, its custom inspector, the scaffolding menus, the data-source asset and the Welcome window cover the whole authoring loop.
1. The component & custom inspector
The VeloScroll View component exposes every option as a serialized field with sensible defaults, so it "just works" unconfigured. Its custom inspector groups the fields and shows only the knobs that apply:
- Setup: Axis, Layout Kind, Cell Main Size, Spacing, Padding, Buffer. Grid Cross Count / Cross Spacing appear only when Layout Kind is Grid.
- Data: a Data Source Mode selector (Sample / Asset / Code), a Data Source Asset slot (shown in Asset mode, with a helpful hint when empty), the Sample Count (in Sample mode), and an optional Cell Prefab.
- Animation: Animation Kind, with Strength and Curve appearing only when a kind other than None is chosen.
- Behaviour: the Build On Start toggle.
- Preview: a manual Rebuild Preview button in edit mode, and live
Items / Live cells / Createdstats while playing.
SerializedObject (multi-edit safe), and the Rebuild button defers its work to the next editor tick rather than mutating the scene mid-IMGUI — the correct, crash-free way to materialize cells from a button.2. GameObject scaffolding menus
Two one-click entry points create a ready-to-play view, adding a Canvas and an EventSystem only if the scene lacks them (so running an item twice never litters the hierarchy), wiring the EventSystem for the new Input System, and registering full Undo with the new object selected and pinged:
GameObject → UI → VeloScroll → Recycling ListGameObject → UI → VeloScroll → Recycling Grid
3. The VeloScrollDataSource asset
For a fully no-code list, create a data asset via Assets → Create → VeloScroll → Data Source (or the CreateAssetMenu entry). Each row is a small struct — Title, Subtitle, Tint — authored in the Inspector or generated with a deterministic Generate button. Set the component's Data Source Mode to Asset, drop the asset in the slot, and press Play: it renders the asset's rows with no scripting. A ready-made sample data asset ships under Assets/VeloScroll/Samples/ for you to duplicate.
4. Precedence: "just works" vs "code wins"
On Play, the gated auto-build resolves data in this order:
So a code-set data source is never overwritten by the auto-build, and a truly unconfigured component is never blank.
5. The Welcome window
On first load, a one-screen Welcome window orients you: a 3-step getting-started, a Create your first Recycling List button (which reuses the exact scaffolding the menu uses), an Open Documentation button, and a "don't show again" toggle. It is available any time under Window → VeloScroll → Welcome.
-batchmode, so headless/CI runs never hang on an editor window or an unexpected build.Sample scenes
Generate per-feature example scenes from Tools → VeloScroll → Generate Sample Scenes. Each one isolates a single feature: the recycling list, grid, masonry, variable sizes, sticky sections, animation, snapping, looping + marquee, pull-to-refresh, multi-prefab, table, drag-to-reorder, nested scrolling, dynamic data, CSV / JSON import, and a fully no-code template (a VeloScrollView in Asset mode bound to a VeloScrollDataSource). Open any one, press Play, and see exactly that feature. The interactive demo scene showcases all of them together with live controls.
Scripting API
The public surface of VeloScroll.VeloScrollView and its supporting types. Namespace: VeloScroll.
Getting a reference and setting data
using UnityEngine;
using VeloScroll;
var view = GetComponent<VeloScrollView>();
// Count + typed binder (the default cell type is VeloScrollTextCell):
view.SetData<VeloScrollTextCell>(10000, (i, c) => c.SetContent("Item " + i, ""));
// Live-count delegate for dynamic collections:
view.SetData<VeloScrollTextCell>(() => model.Count, (i, c) => c.SetContent(model[i].Name, ""));
// Full control: implement IVeloScrollDataSource and assign it.
view.SetDataSource(myDataSource);
Methods
| Signature | What it does |
|---|---|
void SetData<TCell>(int count, Action<int, TCell> bind) | Drive the list from a fixed count + a typed per-cell binder. TCell : VeloScrollCell. |
void SetData<TCell>(Func<int> count, Action<int, TCell> bind) | Same, with a live count delegate for dynamic lists. |
void SetDataSource(IVeloScrollDataSource source) | Assign a custom data source and rebuild. Marks the view code-configured. |
void SetListLayout() | Switch to a single-track list and rebuild. |
void SetGridLayout(int crossCount, float crossSpacing = 6f) | Switch to a fixed column/row grid and rebuild. |
void SetStaggeredGrid(int crossCount, float crossSpacing = 6f, Func<int, float> mainSizeOf = null) | Masonry layout: fixed columns, variable per-item main sizes packed into the shortest column. |
void SetVariableSizes(Func<int, float> sizeOf) | Per-cell sizes along the scroll axis (binary-search range). null reverts to uniform. |
void SetCellSize(float mainSize) | Set the uniform cell extent along the scroll axis (height for vertical, width for horizontal). |
void SetContentPadding(int left, int top, int right, int bottom) | Padding (px) around the cells inside the content. Reloads if already built. |
void SetSections(int sectionCount, Func<int,int> rowsInSection, float headerSize, float rowSize, Action<int,VeloScrollCell> bindHeader, Action<int,int,VeloScrollCell> bindRow, GameObject headerPrefab = null, GameObject rowPrefab = null, bool sticky = true) | Sectioned list (header + rows per section), flattened into the recycler. With sticky, the current section's header floats pinned to the viewport top. |
void SetMultiPrefabData(int count, GameObject[] prefabs, Func<int,int> typeOf, Action<int,VeloScrollCell> bind) | Heterogeneous cells: N prefabs + a per-index type selector + an untyped binder. Each type recycles through its own pool. |
void SetCellPrefabs(GameObject[] prefabs, Func<int,int> typeOf) | Register the cell prefabs + type selector for a multi-prefab view (drive data with SetMultiPrefabData). |
void SetAnimation(VeloScrollAnimationKind kind, float strength = 0.35f, AnimationCurve curve = null) | Set the scroll-driven cell animation preset (None disables). |
void SetAnimationPreset(VeloScrollAnimationPreset preset) | Assign an Animation Preset asset (overrides the inline animation). null reverts. |
void SetTheme(VeloScrollTheme theme) | Assign a Theme asset: recolors built-in text cells on bind and drives spacing. null clears. |
void SetSnapping(bool enabled, VeloScrollSnapAlign align = Start, float duration = 0.25f) | Enable/disable drag-end snapping and its alignment + duration. |
void SetPaging(bool enabled) | Snap by full viewport pages on drag end (document-style paging). OnSnap reports the page index. |
void SetLooping(bool enabled, int copies = 0) | Repeat the data as a long seamless loop (uniform List/Grid) for pickers, carousels and endless feeds. copies = 0 auto-sizes. |
void SetAutoScroll(float pixelsPerSecond) | Continuously advance the scroll (0 = off). With looping on it wraps seamlessly — an endless marquee/ticker. |
void SetPullToRefresh(bool enabled, float thresholdPx = 80f) | Fire OnPullToRefresh / OnLoadMore when the list is over-dragged past its ends. |
void SetTable(VeloScrollColumn[] columns, int rowCount, Func<int, string[]> rowValues, float rowHeight = 36f, bool frozenHeader = true) | Virtualized table: column titles + widths (a width ≤ 0 flexes), string[] per row, optional frozen header pinned at the top. |
void SetReorderable(bool enabled, Action<int,int> onReorder = null) | Drag a cell to reorder; on drop onReorder(from, to) fires so you mutate your data, then the view reloads. |
void Reload() | Reapply after the data count or contents changed (resizes content, refreshes window). |
void InsertItems(int index, int countToInsert) | Reflect an insertion in your source. |
void RemoveItems(int index, int countToRemove) | Reflect a removal in your source. |
void RefreshVisible() | Re-bind the visible cells in place (contents changed, count did not). |
void ScrollToIndex(int index, VeloScrollSnapAlign align = Start, bool animated = false) | Bring an item into view at the given alignment, instant or eased. |
void Build() / void RebuildFromInspector() | (Re)build the hierarchy, layout, pool and initial visible set. The editor preview uses the same path. |
Importing data (CSV / JSON)
| Signature | What it does |
|---|---|
VeloScrollTabularData VeloScrollImport.FromCsv(string text, char delimiter = '\0', bool hasHeader = true) | Parse delimited text. BOM-stripped; delimiter auto-sniffed (comma / semicolon / tab / pipe) when '\0'; RFC-4180 quoting; ragged rows padded; blank lines dropped. |
VeloScrollTabularData VeloScrollImport.FromJson(string text) | Parse arbitrary JSON. Finds the record array (top-level, a wrapper property, or a single object), unions keys into the schema, flattens nested objects to dotted.names and joins primitive arrays. |
VeloScrollTabularData VeloScrollImport.Auto(string text) · FromFile(string path) | Sniff JSON-vs-CSV from the text, or read a file from disk and dispatch on the extension. |
void data.SetIncluded(int col, bool) · SetIncluded(string col, bool) · SetAllIncluded(bool) | Choose which discovered columns to show (all start included). Toggling is a projection — the data is never mutated. |
IReadOnlyList<string> data.Columns · int RowCount · string Cell(int row, int col) · string[] IncludedHeader() · string[] IncludedRow(int) | Read the full grid, or the projection of just the included columns. |
void view.ApplyAsTable(VeloScrollTabularData data, float rowHeight = 34, bool frozenHeader = true) | Render the included columns as a virtualized table (extension method). |
void view.ApplyAsList(VeloScrollTabularData data, float cellHeight = 60) | Render each row as a built-in text cell: first included column as the title, the rest as a "Name: value" subtitle (extension method). |
void VeloScrollFilePicker.Open(string accept, Action<string name, string text> onLoaded) · bool IsSupported | Open the platform file dialog — the browser file picker in a WebGL build, an open-file panel in the editor — and return the chosen file's name + text. Pair with Auto to import it. |
Properties & events
| Member | Purpose |
|---|---|
int Count | Total logical item count, from the active data source. |
int LiveCellCount | Live (instantiated + active) cells right now. Bounded; independent of Count. |
int TotalCellsCreated | Total cells ever instantiated. Constant during scroll once the pool is warm — the proof of reuse. |
bool IsLooping | True while looping is active. |
ScrollRect ScrollRect | The driven ScrollRect (created on first build if absent). |
event Action<int> OnSnap | Raised when a snap (drag-end, page, or animated ScrollToIndex) settles on an item/page index. |
UnityEvent OnPullToRefresh | Invoked when the user over-pulls past the start and releases (wire in code or the Inspector). |
UnityEvent OnLoadMore | Invoked when the user over-pulls past the end and releases. |
Supporting types
| Type | Purpose |
|---|---|
IVeloScrollDataSource | The virtual data contract: int Count { get; } + void Bind(int index, VeloScrollCell cell). |
VeloScrollCell | Base cell. Override OnBind(index) / OnRecycle() for custom cells. |
VeloScrollTextCell | Built-in default cell (background image + title/subtitle uGUI text). SetContent(title, subtitle), SetBackground(color). |
VeloScrollDataSource | ScriptableObject no-code data asset (rows of Title/Subtitle/Tint; deterministic GenerateSample(count)). |
VeloScrollAnimationPreset | ScriptableObject animation preset (Kind + Strength + Curve). Assign via SetAnimationPreset or the Inspector. Create: Assets › Create › VeloScroll › Animation Preset. |
VeloScrollTheme | ScriptableObject theme (cell/background/title/subtitle colors, spacing, font sizes). Assign via SetTheme or the Inspector. Create: Assets › Create › VeloScroll › Theme. |
VeloScrollColumn | A table column: Title + pixel Width (≤ 0 = flex). Used by SetTable. |
VeloScrollNestedRouter | Add to an inner scroll's GameObject for nested scrolling — forwards orthogonal / at-edge drags + wheel to the parent ScrollRect. |
VeloScrollAxis | Vertical / Horizontal. |
VeloScrollLayoutKind | List / Grid / Staggered. |
VeloScrollSnapAlign | Start / Center / End. |
VeloScrollAnimationKind | None / FocusScale / Parallax / Fade / Depth. |
Custom cells
public class MyCell : VeloScrollCell
{
public override void OnBind(int index)
{
base.OnBind(index); // records Index; recycler treats this as "show item N now"
// ... populate your visuals from your model[index] ...
}
public override void OnRecycle()
{
base.OnRecycle(); // resets scale/alpha so a reused cell carries no stale state
// ... release any per-item state ...
}
}
Compatibility
Where VeloScroll runs, and what it was verified against.
| Area | Support |
|---|---|
| Unity version | Compiles on Unity 2021.3 LTS through Unity 6. Verified on 2021.3.30f1 and 6000.0.62f1; no post-2021.3 API is used in the shipped code. |
| UI system | uGUI (com.unity.ugui) — the only runtime dependency. |
| Render pipeline | Built-in, URP and HDRP. The runtime is uGUI canvas-space, so it is pipeline-agnostic. |
| Input | Routed through uGUI's EventSystem, so it works with the new or the legacy input handler. Scaffolded scenes wire the new Input System's UI module. |
| Scripting backend | Mono and IL2CPP. The shipped player's self-test reported RESULT=PASS on both. |
| Platforms | Desktop, mobile and WebGL (the demo builds to WebGL). The recycler is pure managed code with no native plugin. |
Troubleshooting
The issues that come up most, and how to fix them.
The list is blank on the first frame, then appears
Expected and handled. uGUI rects are 0×0 until a layout pass runs, so the first build defers one frame until the viewport has a real size. If it stays blank, confirm the view (or an ancestor) actually has a non-zero RectTransform size — a VeloScroll View under a stretched Canvas, or with an explicit size, will build correctly.
Dragging or clicking does nothing in a built/scaffolded scene
The scene needs an EventSystem with a working UI input module. The scaffolding menus add one wired for the new Input System; if you built the scene yourself, make sure com.unity.inputsystem is installed and the EventSystem uses InputSystemUIInputModule (the menu calls AssignDefaultActions() for you). Without a valid module, pointer drags are silently ignored.
Play shows the sample data instead of mine
Either call SetData/SetDataSource from code (which marks the view code-configured, so the auto-build defers to you), or set Data Source Mode to Asset and assign a VeloScrollDataSource. With no data configured, the component deliberately renders a built-in sample so the "add component + Play" path is never blank.
My data changed but the list didn't update
Tell the view what changed: RefreshVisible() when only contents changed, or Reload() (also InsertItems/RemoveItems) when the count changed. If you used a fixed-count SetData overload, switch to the Func<int> overload so the count tracks your collection.
A reused cell shows a stale scale or opacity
If you wrote a custom VeloScrollCell and overrode OnRecycle(), call base.OnRecycle() — it resets the cell's scale and CanvasGroup alpha so a recycled cell never carries an animation value from its previous item.
Snapping lands one cell off at an exact boundary
This was hardened: the snap target uses a half-pixel boundary epsilon so a float round-trip through the normalized scroll position cannot floor to the wrong index. Make sure your Snap Align matches where you expect the cell to land (Start/Center/End).
Compile error mentioning FindFirstObjectByType on Unity 2021.3
The shipped code already guards newer convenience APIs behind version checks and only uses APIs available on the 2021.3 floor. If you see this, confirm you imported the shipped Assets/VeloScroll/ unmodified — the package compiles clean on a fresh 2021.3.30f1 project.
FAQ
Quick answers to the questions that come up most.
Do I have to write code to use VeloScroll?
No. Add the VeloScroll View component (or scaffold one from GameObject → UI → VeloScroll), optionally assign a VeloScrollDataSource asset, and press Play. The whole thing is configurable in the Inspector. Scripting is available when you want full control — and code always takes precedence over the Inspector data.
How many cells does it actually keep alive?
A small constant, roughly visible + 2 × buffer — independent of your item count. In tests, 12 live cells render 100, 10k and 50k items; in the shipped player 9 cells render 10,000 items. The steady-state recycler path allocates zero bytes and instantiates no cells while scrolling once the pool is warm.
Does it use native plugins or make network calls?
No. VeloScroll is pure C# plus standard uGUI components. There are no native binaries, no network access, and no data collection. A static audit fails the build if a network symbol appears anywhere in the shipped package.
Will it conflict with my existing ScrollRect?
No. VeloScroll drives a ScrollRect by composition and never subclasses it, so it coexists with existing setups. Added to a bare GameObject, it self-builds the viewport and content it needs.
Which render pipelines and platforms are supported?
Built-in, URP and HDRP — the runtime is uGUI canvas-space and therefore pipeline-agnostic. It runs on Mono and IL2CPP across desktop, mobile and WebGL.
What's the minimum Unity version?
Unity 2021.3 LTS. The package compiles on 2021.3 through Unity 6 and was verified on 2021.3.30f1 and 6000.0.62f1, with no post-2021.3 API in the shipped code.
Can cells be different sizes?
Yes, in the List layout. Provide a size function via SetVariableSizes; the layout measures each item and finds the visible range by binary search, keeping the live set bounded. Grid cells use the uniform cell size along the scroll axis.
Does it support TextMeshPro?
The core recycler is text-agnostic and the built-in default cell uses uGUI Text so the core depends only on com.unity.ugui. To use TextMeshPro, put a TMP component on your own cell prefab (a VeloScrollCell subclass) and bind it in your data source — the demo and sample cells do exactly that.
Support
Need help? We're here for you.
Get in Touch
Bug reports, feature requests, setup questions — reach out and we'll get back to you.
✉ szekipapa77@gmail.comWe typically respond within 24–48 hours.
- Check the Troubleshooting section
- Confirm the scene has an EventSystem for input
- Confirm a VeloScroll View is on the object
- Check the Console for messages
- Unity version (e.g.
6000.0.62f1) - VeloScroll version (v1.0.0)
- Render pipeline (Built-in / URP / HDRP)
- Scripting backend (Mono / IL2CPP) and target platform
VeloScroll v1.0.0 · Built for Unity 2021.3 LTS through Unity 6 · Pure C#, no native binaries
Support: szekipapa77@gmail.com