目前是只写了垂直排列的算法,水平的还没写(原理一样,懒得写了,有需求在说)
特点:
1.支持NGUI的ScrollView进行循环滚动(该脚本挂在UIScrollView下)
2.支持每个单元格有不同的大小
3.支持Scrollbar
4.可运用于NGUI3.9.4(亲测),其他版本不知道
using System;
using System.Collections.Generic;
using UnityEngine;
public class NguiInfiniteScrollView : MonoBehaviour
{
//可见区域cell列表(当前激活的Cell)
Dictionary<int,NguiInfiniteScrollViewCell> cellDatas = new Dictionary<int,NguiInfiniteScrollViewCell>();
//缓冲Cell
Queue<NguiInfiniteScrollViewCell> cellDatasCache = new Queue<NguiInfiniteScrollViewCell>();
//第一个和最后一个物体
private NguiInfiniteScrollViewCell firstCell;
private NguiInfiniteScrollViewCell LastCell;
//第一个和最后一个缓冲物体
private NguiInfiniteScrollViewCell firstCacheCell;
private NguiInfiniteScrollViewCell LastCacheCell;
//要实例化的预制体
public GameObject Prefab;
private UIScrollView scrollView;
private UIPanel panel;
//ScrollView组件下的子物体,用于预制体生成时作为父对象
public UIWidget Hold;
//目前只判定Horizontal和Vertical
private bool posXorY;
private float panelOffset
{
get
{
if (posXorY)
return panel.clipOffset.x;
else
return panel.clipOffset.y;
}
}
//物体坐标
private Vector3 _cellSizeVector3 = Vector3.zero;
private float SetcellSizeVector3
{
set
{
if (posXorY)
_cellSizeVector3.x = value;
else
_cellSizeVector3.y = value;
}
}
//UIPanel初始偏移
private float initoffset;
//ScrollView初始坐标
private float initTransform;
//UIPanel当前滚动区域大小
private float scrollClipArea;
//滚动区域总大小
private float sizeCount;
//数据总数
private int _dataCount;
//更新Cell
private Action<NguiInfiniteScrollViewCell> _updataCallBack;
//根据index获取大小
private Func<int, float> _getDataSizeCallBack;
//获取数据总的Size大小
private void SetSizeCount()
{
float sizeSum = 0;
for (int i = 0; i < _dataCount; i++)
{
sizeSum += _getDataSizeCallBack(i);
}
sizeCount = sizeSum;
Hold.height = (int)sizeCount;
}
//初始化后刷新一下
[HideInInspector]public bool isLoad = false;//用于判定是否初始化过
bool isfirst;//判定是否是第一次初始化
private void LateUpdate()
{
if(isLoad&&!isfirst)
{
isfirst = true;
scrollView.ResetPosition();
initoffset = posXorY ? panel.clipOffset.x : panel.clipOffset.y;
initoffset = Math.Abs(initoffset);
initTransform = transform.localPosition.y;
panel.onClipMove += LoadLastCell;
panel.onClipMove += LoadFirstCell;
}
}
public void ReLoadData(int count, Action<NguiInfiniteScrollViewCell> updataCallBack = null, Func<int, float> getCellSizeCallback = null)
{
_dataCount = count;
if (updataCallBack != null)
_updataCallBack = updataCallBack;
if (getCellSizeCallback != null)
_getDataSizeCallBack = getCellSizeCallback;
SetSizeCount();
foreach (var item in cellDatas)
{
item.Value.Cellprefab.gameObject.SetActive(false);
cellDatasCache.Enqueue(item.Value);
}
cellDatas.Clear();
initLoad();
scrollView.ResetPosition();
}
public void JumpTo(int index)
{
float sizeSum = 0;
for (int i = 0; i < index; i++)
{
sizeSum += _getDataSizeCallBack(i);
}
scrollView.verticalScrollBar.value = sizeSum / sizeCount;
}
public NguiInfiniteScrollViewCell GetItemByDataIndex(int index)
{
cellDatas.TryGetValue(index, out NguiInfiniteScrollViewCell obj);
return obj;
}
public void init(int dataCount, Action<NguiInfiniteScrollViewCell> updataCallBack = null,Func<int,float> getCellSizeCallback = null)
{
_dataCount = dataCount;
_updataCallBack = updataCallBack;
_getDataSizeCallBack = getCellSizeCallback;
scrollView = GetComponent<UIScrollView>();
panel = GetComponent<UIPanel>();
posXorY = scrollView.movement == UIScrollView.Movement.Horizontal;
Prefab.GetComponent<UIWidget>().pivot = posXorY ? UIWidget.Pivot.Left : UIWidget.Pivot.Top;
initLoad();
isLoad = true;
}
//获取缓冲Cell
private NguiInfiniteScrollViewCell GetFreeCell()
{
NguiInfiniteScrollViewCell cellCache = null;
if(cellDatasCache.Count==0)
{
GameObject prefab = Instantiate(Prefab, Hold.transform);
cellCache = new NguiInfiniteScrollViewCell(prefab, 0);
}
else
{
cellCache = cellDatasCache.Dequeue();
}
return cellCache;
}
private void initLoad()
{
int Index = 0;
//初始化区域总长度
float AreaCellSizeCount = 0;
//初始化生成数量
int CreateCount = 0;
scrollClipArea = posXorY ? panel.finalClipRegion.z : panel.finalClipRegion.w;
while (AreaCellSizeCount < scrollClipArea && CreateCount < _dataCount)
{
GameObject prefab = null;
NguiInfiniteScrollViewCell cell = null;
if (cellDatasCache.Count==0)
{
prefab = Instantiate(Prefab, Hold.transform);
cell = new NguiInfiniteScrollViewCell(prefab, Index++);
}else
{
cell = cellDatasCache.Dequeue();
cell.Cellprefab.SetActive(true);
cell.dataIndex = Index++;
prefab = cell.Cellprefab;
}
cellDatas.Add(cell.dataIndex,cell);
//坐标运算
SetcellSizeVector3 = posXorY? AreaCellSizeCount : -AreaCellSizeCount;
prefab.transform.localPosition = _cellSizeVector3;
AreaCellSizeCount += _getDataSizeCallBack(CreateCount);
cell.SetSize(posXorY, (int)_getDataSizeCallBack(CreateCount));
CreateCount++;
_updataCallBack?.Invoke(cell);
}
firstCell = cellDatas[0];
LastCell = cellDatas.Count > 1 ? cellDatas[Index-1] : firstCell;
//默认加载2个缓冲cell
if (cellDatasCache.Count < 2)
{
int cacheCount = cellDatasCache.Count;
for (int i = cacheCount; i < 2; i++)
{
GameObject prefab = Instantiate(Prefab, Hold.transform);
NguiInfiniteScrollViewCell cell = new NguiInfiniteScrollViewCell(prefab, 0);
cellDatasCache.Enqueue(cell);
prefab.gameObject.SetActive(false);
}
}
}
void Update()
{
// LoadLastCell();
// LoadFirstCell();
}
private void LoadFirstCell(UIPanel panel)
{
//不是第一个数据并且first缓存可用
while (firstCell.dataIndex != 0 &&panelOffset- (-initoffset / 2)- firstCell.Cellprefab.transform.localPosition.y>-firstCell.Size*0.5f)
{
//回收最后一个
if (initoffset / 2 - panelOffset + LastCell.Cellprefab.transform.localPosition.y < -LastCell.Size * 1.5)
{
LastCell.Cellprefab.SetActive(false);
cellDatasCache.Enqueue(LastCell);
cellDatas.Remove(LastCell.dataIndex);
LastCell = cellDatas[LastCell.dataIndex - 1];
}
firstCacheCell = GetFreeCell();
int dataindex = firstCell.dataIndex - 1;
firstCacheCell.Cellprefab.SetActive(true);
float cellSize = _getDataSizeCallBack(dataindex);
SetcellSizeVector3 = cellSize;
firstCacheCell.SetSize(posXorY, (int)cellSize);
if (posXorY)
firstCacheCell.Cellprefab.transform.localPosition = firstCell.Cellprefab.transform.localPosition - _cellSizeVector3;
else
firstCacheCell.Cellprefab.transform.localPosition = firstCell.Cellprefab.transform.localPosition + _cellSizeVector3;
firstCacheCell.dataIndex = dataindex;
cellDatas.Add(firstCacheCell.dataIndex,firstCacheCell);
_updataCallBack?.Invoke(firstCacheCell);
firstCell = firstCacheCell;
}
}
private void LoadLastCell(UIPanel panel)
{
//最后一个已经完全加载
while (LastCell.dataIndex != _dataCount - 1 && initoffset/2 - panelOffset + LastCell.Cellprefab.transform.localPosition.y > LastCell.Size*0.5)
{
//第一个单元格往上超出视野
if (panelOffset - (-initoffset / 2) - firstCell.Cellprefab.transform.localPosition.y < -firstCell.Size * 1.5)
{
firstCell.Cellprefab.SetActive(false);
cellDatasCache.Enqueue(firstCell);
cellDatas.Remove(firstCell.dataIndex);
firstCell = cellDatas[firstCell.dataIndex + 1];
}
LastCacheCell = GetFreeCell();
int dataindex = LastCell.dataIndex + 1;
LastCacheCell.Cellprefab.SetActive(true);
float cellSize = _getDataSizeCallBack(dataindex);
SetcellSizeVector3 = LastCell.Size;
LastCacheCell.SetSize(posXorY, (int)cellSize);
if (posXorY)
LastCacheCell.Cellprefab.transform.localPosition = LastCell.Cellprefab.transform.localPosition + _cellSizeVector3;
else
LastCacheCell.Cellprefab.transform.localPosition = LastCell.Cellprefab.transform.localPosition - _cellSizeVector3;
LastCacheCell.dataIndex = dataindex;
cellDatas.Add(LastCacheCell.dataIndex, LastCacheCell);
_updataCallBack?.Invoke(LastCacheCell);
LastCell = LastCacheCell;
}
}
}
public class NguiInfiniteScrollViewCell
{
//要生成的预制体
public GameObject Cellprefab;
//数据下标索引
public int dataIndex;
public float Size;
public NguiInfiniteScrollViewCell(GameObject cellPrefab, int index)
{
Cellprefab = cellPrefab;
dataIndex = index;
}
public void SetSize(bool isX,int size)
{
if (isX)
Cellprefab.GetComponent<UIWidget>().width = size;
else
Cellprefab.GetComponent<UIWidget>().height = size;
Size = size;
}
}