跳到主要内容

InfiniteScrollView

重要 注意 提醒

Coding Style wiki


InfiniteScrollView无限列表的抽象基类 (继承 UIBehaviour,并以 RequireComponent 要求同对象上挂载 UGUI 的 ScrollRect),以对象池概念对 Cell (InfiniteCell) 进行有效循环利用,仅需少量 GameObject 即可承载大量列表数据;内置四种实现 (ScrollView Variants),也可自行继承扩展定制列表。

命名空间OxGKit.InfiniteScrollView
类型[RequireComponent(typeof(ScrollRect))] public abstract class InfiniteScrollView : UIBehaviour
源码InfiniteScrollView.cs
using OxGKit.InfiniteScrollView;

快速上手

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

public class ListLauncher : MonoBehaviour
{
// 场景上挂载于 ScrollRect 对象的列表组件 (以垂直列表为例)
public VerticalInfiniteScrollView scrollView;

private async void Start()
{
// 1. 初始对象池 (若已于 Inspector 勾选 initializePoolOnAwake 则可省略)
await this.scrollView.InitializePool();

// 2. 批量添加数据后统一刷新 (Add 默认不刷新)
for (int i = 0; i < 1000; i++)
this.scrollView.Add(new InfiniteCellData(new Vector2(100f, 100f), $"Item {i}"));
this.scrollView.Refresh();

// 3. Cell 点击事件 (Cell 端需调用 base.OnClick() 触发)
this.scrollView.onCellSelected += (cell) => Debug.Log($"Selected index: {cell.cellData.index}");

// 4. 移动至指定索引 (0.1 秒补间)
this.scrollView.Snap(50, 0.1f);
}
}

通用规则

初始流程

  1. Initialize Pool On Awake:于 Inspector 勾选 initializePoolOnAwake,列表会在 Awake 自动初始对象池。
  2. 手动初始:自行 await InitializePool(args) (args 会转传给每个 Cell 的 OnCreate)。

重要 所有操作 (Add / Insert / Remove / Clear / Refresh / Snap) 皆须于初始完成后才有效,未初始会输出警告 Please InitializePool first!!!

对象池循环

  • 初始时依 cellPoolSize 预先实例化 Cell,滚动时仅对可视范围内 (含 extendVisibleRange 的延伸) 的数据配置 Cell,滚出范围即回收回池循环利用。
  • 数据与显示分离:数据 (InfiniteCellData) 由列表持有,Cell 于配置显示时才绑定数据并触发 OnRefresh

注意 cellPoolSize 需足够覆盖可视数量 + extendVisibleRange 的预留量,对象池不足时会输出错误 The cell display error occurred, not enough cells in the cell pool!!!

提醒 content 节点不需要挂载 LayoutGroup / ContentSizeFitter,Cell 位置由列表自行计算;ScrollRect 的滚动轴向 (Vertical / Horizontal) 请自行与所用实现对应。


成员

成员说明
public bool initializePoolOnAwake是否于 Awake 自动初始对象池,默认 false
public int cellPoolSize对象池的 Cell 数量,默认 20;需足够覆盖可视数量与 extendVisibleRange 的预留量。
public InfiniteCell cellPrefabCell 预制体 (挂载 InfiniteCell 子类的 Prefab)。
public DataOrder dataOrder数据排列顺序 (DataOrder),默认 NormalReverse 会反转数据索引的显示顺序。
public float extendVisibleRange可视范围的延伸距离,于可视范围外预先配置 Cell,滚动显示更平滑。
public ScrollRect scrollRect { get; protected set; }同对象上的 UGUI ScrollRect (于初始时获取)。
public SnapAlign snapAlignSnap 对齐方式 (SnapAlign),默认 Start
public Padding padding内容边距 (Padding)。
public int visibleCount { get; protected set; }当前可视范围内的 Cell 数量。
public int lastMaxVisibleCount { get; protected set; }曾出现过的最大可视数量。
public float lastVisibleRangeSize { get; protected set; }上次计算的可视范围尺寸 (含 extendVisibleRange)。
public bool isVisibleRangeFilled { get; protected set; }可视范围是否已被 Cell 填满 (可用于判断数据量是否足以填满画面)。
public bool isInitialized { get; protected set; }对象池是否已初始完成。

事件

事件说明
public Action<Vector2> onValueChanged列表滚动时触发 (参数为 ScrollRect 的 normalizedPosition)。
public Action onRectTransformDimensionsChanged列表的 RectTransform 尺寸变更时触发 (可用于重新刷新布局)。
public Action onRefreshed每次完整刷新 (Refresh) 执行完成后触发。
public Action<InfiniteCell> onCellSelectedCell 被点击选中时触发 (Cell 端需调用 base.OnClick(),详见 InfiniteCell.OnClick)。

初始与数据操作

方法总览

方法说明
InitializePool初始对象池 (所有操作的前置条件)。
DataCount获取数据数量 (等同 Cell 总数)。
Add添加 Cell 数据至末尾 (默认不刷新)。
Insert插入 Cell 数据至指定索引。
Remove移除指定索引的 Cell 数据。
Clear清空所有数据并刷新。

InitializePool

public virtual async UniTask InitializePool(object args = null)

初始对象池:依 cellPoolSizecellPrefab 实例化至 content 下,并逐一 await Cell 的 OnCreate (args 会转传给每个 Cell);同时会清空 content 已有子对象与数据清单,并注册滚动监听。已初始过 (isInitializedtrue) 时再次调用会直接返回。

// 基本初始
await this.scrollView.InitializePool();

// 传递自定义参数给每个 Cell 的 OnCreate
await this.scrollView.InitializePool(myCellInitArgs);

注意 cellPrefab 为 null 时初始会失败并输出错误。

DataCount

public int DataCount()

获取数据数量 (等同 Cell 总数)。

Add

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

添加一条 InfiniteCellData 至末尾。

提醒 refresh 默认为 false — 批量添加后统一调用一次 Refresh 效率较佳。

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)

插入数据至指定索引 (index 可等于 DataCount(),相当于添加至末尾),后续数据的索引会自动更新;索引超出范围返回 falserefreshtrue 时会以「先回收所有活动中的 Cell」的方式刷新 (Refresh(false, true))。

Remove

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

移除指定索引的数据 (该条数据会被 Dispose),后续数据的索引自动前移;索引超出范围返回 false

注意 VerticalInfiniteScrollViewHorizontalInfiniteScrollView 有覆写此方法,移除后会同步校正 content 位置,详见 ScrollView Variants

Clear

public virtual void Clear()

停止滚动并将 content 位置归零,回收所有活动中的 Cell、Dispose 所有数据后刷新视图。


刷新

方法总览

方法说明
Refresh完整刷新 (重算 content 尺寸并刷新可视 Cell)。
RefreshVisibleCells仅重新计算并刷新可视范围内的 Cell。

Refresh

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

完整刷新:重算 content 尺寸 → 刷新可视 Cell → 触发 onRefreshed 事件。

参数说明
refreshOnNextScrolltrue 时不立即刷新,仅标记于下一次滚动时才执行完整刷新 (此时不会触发 onRefreshed)。
recycleActiveCellstrue 时会先将所有活动中的 Cell 回收回对象池再刷新 (不清除数据)。

提醒 若 viewport 尺寸尚未就绪 (为 0,常见于 UI 首帧),会自动延迟至 LastPostLateUpdate 再执行刷新。

RefreshVisibleCells

public void RefreshVisibleCells()

仅重新计算可视范围并配置/回收 Cell (不重算 content 尺寸);列表滚动时内部会自动调用。


滚动与状态

方法总览

方法说明
ScrollToTop / ScrollToBottom立即滚动至顶部/底部 (垂直)。
ScrollToLeft / ScrollToRight立即滚动至最左/最右 (水平)。
VerticalNormalizedPosition / HorizontalNormalizedPosition获取标准化滚动位置。
IsAtTop / IsAtBottom / IsAtLeft / IsAtRight是否已滚动至各边界。

ScrollToTop / ScrollToBottom

public void ScrollToTop()
public void ScrollToBottom()

直接设置 verticalNormalizedPosition 滚动至顶部 (1) / 底部 (0),适用垂直列表。

ScrollToLeft / ScrollToRight

public void ScrollToLeft()
public void ScrollToRight()

直接设置 horizontalNormalizedPosition 滚动至最左 (0) / 最右 (1),适用水平列表。

VerticalNormalizedPosition / HorizontalNormalizedPosition

public float VerticalNormalizedPosition()
public float HorizontalNormalizedPosition()

获取 ScrollRect 的标准化滚动位置 (0~1);scrollRect 尚未获取时返回 -1

IsAtTop / IsAtBottom / IsAtLeft / IsAtRight

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

是否已滚动至顶/底/左/右边界 (内部已依 content 方向 pivot 自动校正判断);边界状态于可视刷新时更新 — 垂直实现更新 Top / Bottom、水平实现更新 Left / Right。

// 滚动至底部时加载更多 (垂直列表)
this.scrollView.onValueChanged += (pos) =>
{
if (this.scrollView.IsAtBottom())
this.LoadMore();
};

Snap 定位

方法总览

方法说明
Snap移动至指定索引的 Cell。
SnapFirst移动至第一个 Cell。
SnapMiddle移动至中间的 Cell。
SnapLast移动至最后一个 Cell。

Snap

public abstract void Snap(int index, float duration)

移动至指定索引的 Cell,对齐方式依 snapAlign (Start / Center / End):

  • duration 小于等于 0 → 同步,立即定位。
  • duration 大于 0 → 异步补间 (UniTask),于 duration 秒内平滑移动 (重复调用会中断前一次补间)。

到位后若目标 Cell 为显示状态,会触发其 OnSnapdataOrderReverse 时索引会自动转换。

// 立即跳至索引 10
this.scrollView.Snap(10, 0);

// 0.1 秒平滑移动至索引 10
this.scrollView.Snap(10, 0.1f);

SnapFirst

public void SnapFirst(float duration)

移动至第一个 Cell (等同 Snap(0, duration))。

SnapMiddle

public void SnapMiddle(float duration)

移动至中间的 Cell (索引为 (DataCount() - 1) / 2)。

SnapLast

public void SnapLast(float duration)

移动至最后一个 Cell (等同 Snap(DataCount() - 1, duration))。

// 聊天室类应用:新增消息后自动滚动至最新
this.chatScrollView.Add(cellData);
this.chatScrollView.Refresh();
this.chatScrollView.SnapLast(0.1f);

类型定义

DataOrder

public enum DataOrder
{
Normal,
Reverse
}

数据排列顺序;Reverse 会反转数据索引的显示顺序 (由反方向开始排列)。

SnapAlign

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

Snap 对齐方式:将目标 Cell 对齐可视范围的起始端 / 中央 / 结尾端

Padding

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

内容边距 (上 / 下 / 左 / 右)。


继承扩展 (protected)

自行继承 InfiniteScrollView (或内置实现) 定制列表时,可运用以下 protected 成员:

成员说明
protected List<InfiniteCellData> _dataList数据清单。
protected List<InfiniteCell> _cellList与数据对应的活动 Cell 清单 (未显示的索引为 null)。
protected Queue<InfiniteCell> _cellPoolCell 对象池。
protected abstract void DoRefreshVisibleCells()实现可视范围的 Cell 配置/回收逻辑。
protected abstract void DoRefresh(bool refreshOnNextScroll, bool recycleActiveCells)实现完整刷新逻辑。
protected abstract UniTask DoDelayRefresh(bool refreshOnNextScroll, bool recycleActiveCells)实现延迟刷新 (viewport 尺寸未就绪时)。
protected void SetupCell(InfiniteCell cell, int index, Vector2 pos)将 Cell 绑定数据并配置至指定位置显示。
protected void RecycleCell(int index)回收指定索引的 Cell 回对象池 (触发 OnRecycle)。
protected void RecycleActiveCells()回收所有活动中的 Cell (不清除数据)。
protected void ClearAllData()Dispose 所有数据。
protected void DoSnapping(int index, Vector2 target, float duration)执行 Snap 位移补间。
protected float CalculateSnapPos(ScrollType scrollType, SnapAlign snapPosType, float originValue, InfiniteCellData cellData)依对齐方式计算 Snap 目标位置。
protected bool IsInitialized()检查是否已初始 (未初始会输出警告)。