基础版:【Unity技术分享】UGUI之ScrollRect优化_ugui scrollrect 优化-CSDN博客
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
public delegate void OnBaseLoopListItemCallback(GameObject cell, int index);
public class BaseLoopList : MonoBehaviour
{
public int m_Row = 1; //排
public bool m_IsVertical = true;
public float m_SpacingX = 0f; //间距
public float m_SpacingY = 0f; //间距
public GameObject m_CellGameObject; //指定的cell
private Dictionary<GameObject, int> m_GameObjectNumDict;
//-> 回调相关
public event OnBaseLoopListItemCallback onItemShow; //Lua 回调
public event OnBaseLoopListItemCallback onItemHide; //Lua 回调
protected RectTransform rectTrans;
protected float m_PlaneWidth;
protected float m_PlaneHeight;
protected float m_ContentWidth;
protected float m_ContentHeight;
protected float m_CellObjectWidth;
protected float m_CellObjectHeight;
protected GameObject m_Content;
protected RectTransform m_ContentRectTrans;
private bool m_isInited = false;
//记录 物体的坐标 和 物体
protected struct CellInfo
{
public Vector3 pos;
public GameObject obj;
};
protected CellInfo[] m_CellInfos;
protected bool m_IsInited = false;
protected ScrollRect m_ScrollRect;
protected int m_MaxCount = -1; //列表数量
protected int m_MinIndex = -1;
protected int m_MaxIndex = -1;
protected bool m_IsClearList = false; //是否清空列表
protected Vector4 m_Padding = Vector4.zero; //(left,top,right,bottom) 左,上 偏移Item(左上角为锚点) 右,下 扩大Content(宽度和高度)
//c# 初始化
public virtual void Init()
{
if (m_isInited)
return;
m_isInited = true;
m_GameObjectNumDict = new Dictionary<GameObject, int>();
m_Content = this.GetComponent<ScrollRect>().content.gameObject;
if (m_CellGameObject == null)
{
//m_CellGameObject = m_Content.transform.GetChild(0).gameObject;
Debug.LogError("m_CellGameObject 不能为 null");
}
/* Cell 处理 */
//m_CellGameObject.transform.SetParent(m_Content.transform.parent, false);
//SetPoolsObj(m_CellGameObject);
RectTransform cellRectTrans = m_CellGameObject.GetComponent<RectTransform>();
cellRectTrans.pivot = new Vector2(0f, 1f);
CheckAnchor(cellRectTrans);
cellRectTrans.anchoredPosition = Vector2.zero;
//记录 Cell 信息
m_CellObjectHeight = cellRectTrans.rect.height;
m_CellObjectWidth = cellRectTrans.rect.width;
//记录 Plane 信息
rectTrans = GetComponent<RectTransform>();
Rect planeRect = rectTrans.rect;
m_PlaneHeight = planeRect.height;
m_PlaneWidth = planeRect.width;
//记录 Content 信息
m_ContentRectTrans = m_Content.GetComponent<RectTransform>();
Rect contentRect = m_ContentRectTrans.rect;
SetContentHeight(contentRect.height);
SetContentWidth(contentRect.width);
m_ContentRectTrans.pivot = new Vector2(0f, 1f);
//m_ContentRectTrans.sizeDelta = new Vector2 (planeRect.width, planeRect.height);
//m_ContentRectTrans.anchoredPosition = Vector2.zero;
CheckAnchor(m_ContentRectTrans);
m_ScrollRect = this.GetComponent<ScrollRect>();
m_ScrollRect.onValueChanged.RemoveAllListeners();
//添加滑动事件
m_ScrollRect.onValueChanged.AddListener(delegate (Vector2 value) { ScrollRectListener(value); });
}
public virtual void Destroy()
{
if (m_CellInfos != null)
{
for (int i = 0; i < m_CellInfos.Length; i++)
{
SetPoolsObj(m_CellInfos[i].obj);
m_CellInfos[i].obj = null;
}
}
m_GameObjectNumDict.Clear();
}
private void DisposeAll()
{
m_Padding = Vector4.zero;
onItemShow = null;
onItemHide = null;
if (poolsObj != null)
{
foreach (var v in poolsObj)
{
if (v != null)
{
GameObject.Destroy(v);
}
}
poolsObj = null;
}
if (m_CellInfos != null)
{
foreach (var v in m_CellInfos)
{
if (v.obj != null)
{
GameObject.Destroy(v.obj);
}
}
m_CellInfos = null;
}
}
//检查 Anchor 是否正确
private void CheckAnchor(RectTransform rectTrans)
{
if (m_IsVertical)
{
if (!((rectTrans.anchorMin == new Vector2(0, 1) && rectTrans.anchorMax == new Vector2(0, 1)) ||
(rectTrans.anchorMin == new Vector2(0, 1) && rectTrans.anchorMax == new Vector2(1, 1))))
{
rectTrans.anchorMin = new Vector2(0, 1);
rectTrans.anchorMax = new Vector2(1, 1);
}
}
else
{
if (!((rectTrans.anchorMin == new Vector2(0, 1) && rectTrans.anchorMax == new Vector2(0, 1)) ||
(rectTrans.anchorMin == new Vector2(0, 0) && rectTrans.anchorMax == new Vector2(0, 1))))
{
rectTrans.anchorMin = new Vector2(0, 0);
rectTrans.anchorMax = new Vector2(0, 1);
}
}
}
public void SetPadding(float left, float top, float right, float bottom)
{
m_Padding = new Vector4(left, top, right, bottom);
SetContentWidth(m_ContentWidth);
SetContentHeight(m_ContentHeight);
}
private float GetPaddingX()
{
return m_Padding.x;
}
private float GetPaddingY()
{
return m_Padding.y;
}
private float GetExpandWidth()
{
return m_Padding.z;
}
private float GetExpandHeight()
{
return m_Padding.w;
}
public void SetContentWidth(float value)
{
m_ContentWidth = value + GetPaddingX() + GetExpandWidth();
m_ContentWidth = m_ContentWidth < rectTrans.rect.width ? rectTrans.rect.width : m_ContentWidth;
}
public void SetContentHeight(float value)
{
m_ContentHeight = value + GetPaddingY() + GetExpandHeight();
m_ContentHeight = m_ContentHeight < rectTrans.rect.height ? rectTrans.rect.height : m_ContentHeight;
}
public virtual void SetCount(int num)
{
m_MinIndex = -1;
m_MaxIndex = -1;
//-> 计算 Content 尺寸
if (m_IsVertical)
{
float contentSize = (m_SpacingY + m_CellObjectHeight) * Mathf.CeilToInt((float)num / m_Row);
SetContentHeight(contentSize);
SetContentWidth(m_ContentRectTrans.rect.width);
m_ContentRectTrans.sizeDelta = new Vector2(m_ContentWidth, m_ContentHeight);
if (num != m_MaxCount)
{
m_ContentRectTrans.anchoredPosition = new Vector2(m_ContentRectTrans.anchoredPosition.x, 0);
}
}
else
{
float contentSize = (m_SpacingX + m_CellObjectWidth) * Mathf.CeilToInt((float)num / m_Row);
SetContentWidth(contentSize);
SetContentHeight(m_ContentRectTrans.rect.height);
m_ContentRectTrans.sizeDelta = new Vector2(m_ContentWidth, m_ContentHeight);
if (num != m_MaxCount)
{
m_ContentRectTrans.anchoredPosition = new Vector2(0, m_ContentRectTrans.anchoredPosition.y);
}
}
//-> 计算 开始索引
int lastEndIndex = 0;
//-> 过多的物体 扔到对象池 ( 首次调 ShowList函数时 则无效 )
if (m_IsInited)
{
lastEndIndex = num - m_MaxCount > 0 ? m_MaxCount : num;
lastEndIndex = m_IsClearList ? 0 : lastEndIndex;
int count = m_IsClearList ? m_CellInfos.Length : m_MaxCount;
for (int i = lastEndIndex; i < count; i++)
{
if (m_CellInfos[i].obj != null)
{
SetPoolsObj(m_CellInfos[i].obj);
if (onItemHide != null)
{
int objNum = 0;
m_GameObjectNumDict.TryGetValue(m_CellInfos[i].obj, out objNum);
onItemHide.Invoke(m_CellInfos[i].obj, objNum);
}
m_CellInfos[i].obj = null;
}
}
}
//-> 以下四行代码 在for循环所用
CellInfo[] tempCellInfos = m_CellInfos;
m_CellInfos = new CellInfo[num];
//-> 1: 计算 每个Cell坐标并存储 2: 显示范围内的 Cell
for (int i = 0; i < num; i++)
{
// * -> 存储 已有的数据 ( 首次调 ShowList函数时 则无效 )
if (m_MaxCount != -1 && i < lastEndIndex)
{
CellInfo tempCellInfo = tempCellInfos[i];
//-> 计算是否超出范围
float rPos = m_IsVertical ? tempCellInfo.pos.y : tempCellInfo.pos.x;
if (!IsOutRange(rPos))
{
//-> 记录显示范围中的 首位index 和 末尾index
m_MinIndex = m_MinIndex == -1 ? i : m_MinIndex; //首位index
m_MaxIndex = i; // 末尾index
if (tempCellInfo.obj == null)
{
tempCellInfo.obj = GetPoolsObj();
}
tempCellInfo.obj.transform.GetComponent<RectTransform>().anchoredPosition = tempCellInfo.pos;
if (!m_GameObjectNumDict.ContainsKey(tempCellInfo.obj))
{
m_GameObjectNumDict.Add(tempCellInfo.obj, i);
}
else
{
m_GameObjectNumDict[tempCellInfo.obj] = i;
}
tempCellInfo.obj.SetActive(true);
//-> 回调 Lua 函数
Func(tempCellInfo.obj);
}
else
{
if (tempCellInfo.obj != null)
{
SetPoolsObj(tempCellInfo.obj);
if (onItemHide != null)
{
int objNum = 0;
m_GameObjectNumDict.TryGetValue(tempCellInfo.obj, out objNum);
onItemHide.Invoke(tempCellInfo.obj, objNum);
}
tempCellInfo.obj = null;
}
}
m_CellInfos[i] = tempCellInfo;
continue;
}
CellInfo cellInfo = new CellInfo();
float pos = 0; //坐标( isVertical ? 记录Y : 记录X )
float rowPos = 0; //计算每排里面的cell 坐标
// * -> 计算每个Cell坐标
if (m_IsVertical)
{
pos = m_CellObjectHeight * Mathf.FloorToInt(i / m_Row) + m_SpacingY * Mathf.FloorToInt(i / m_Row);
rowPos = m_CellObjectWidth * (i % m_Row) + m_SpacingX * (i % m_Row);
cellInfo.pos = new Vector3(rowPos + GetPaddingX(), -pos - GetPaddingY(), 0);
}
else
{
pos = m_CellObjectWidth * Mathf.FloorToInt(i / m_Row) + m_SpacingX * Mathf.FloorToInt(i / m_Row);
rowPos = m_CellObjectHeight * (i % m_Row) + m_SpacingY * (i % m_Row);
cellInfo.pos = new Vector3(pos + GetPaddingX(), -rowPos - GetPaddingY(), 0);
}
//-> 计算是否超出范围
float cellPos = m_IsVertical ? cellInfo.pos.y : cellInfo.pos.x;
if (IsOutRange(cellPos))
{
cellInfo.obj = null;
m_CellInfos[i] = cellInfo;
continue;
}
//-> 记录显示范围中的 首位index 和 末尾index
m_MinIndex = m_MinIndex == -1 ? i : m_MinIndex; //首位index
m_MaxIndex = i; // 末尾index
//-> 取或创建 Cell
GameObject cell = GetPoolsObj();
cell.transform.GetComponent<RectTransform>().anchoredPosition = cellInfo.pos;
if (!m_GameObjectNumDict.ContainsKey(cell.gameObject))
{
m_GameObjectNumDict.Add(cell.gameObject, i);
}
else
{
m_GameObjectNumDict[cell.gameObject] = i;
}
//-> 存数据
cellInfo.obj = cell;
m_CellInfos[i] = cellInfo;
//-> 回调 Lua 函数
Func(cell);
}
m_MaxCount = num;
m_IsInited = true;
}
//实时刷新列表时用
public virtual void UpdateList()
{
NormalPerformanceMode();
}
//刷新某一项
public void UpdateCell(int index)
{
CellInfo cellInfo = m_CellInfos[index - 1];
if (cellInfo.obj != null)
{
float rangePos = m_IsVertical ? cellInfo.pos.y : cellInfo.pos.x;
if (!IsOutRange(rangePos))
{
Func(cellInfo.obj);
}
}
}
// 更新滚动区域的大小
public void UpdateSize()
{
Rect rect = GetComponent<RectTransform>().rect;
m_PlaneHeight = rect.height;
m_PlaneWidth = rect.width;
}
//滑动事件
protected virtual void ScrollRectListener(Vector2 value)
{
NormalPerformanceMode(); //普通性能模式
}
//普通性能模式
private void NormalPerformanceMode()
{
if (m_CellInfos == null)
return;
//检查超出范围
for (int i = 0, length = m_CellInfos.Length; i < length; i++)
{
CellInfo cellInfo = m_CellInfos[i];
GameObject obj = cellInfo.obj;
Vector3 pos = cellInfo.pos;
float rangePos = m_IsVertical ? pos.y : pos.x;
//判断是否超出显示范围
if (IsOutRange(rangePos))
{
//把超出范围的cell 扔进 poolsObj里
if (obj != null)
{
SetPoolsObj(obj);
if (onItemHide != null)
{
int objNum = 0;
m_GameObjectNumDict.TryGetValue(obj, out objNum);
onItemHide.Invoke(obj, objNum);
}
m_CellInfos[i].obj = null;
}
}
else
{
if (obj == null)
{
//优先从 poolsObj中 取出 (poolsObj为空则返回 实例化的cell)
GameObject cell = GetPoolsObj();
cell.transform.localPosition = pos;
if (!m_GameObjectNumDict.ContainsKey(cell.gameObject))
{
m_GameObjectNumDict.Add(cell.gameObject, i);
}
else
{
m_GameObjectNumDict[cell.gameObject] = i;
}
m_CellInfos[i].obj = cell;
Func(cell);
}
}
}
}
//判断是否超出显示范围
protected bool IsOutRange(float pos)
{
Vector3 listP = m_ContentRectTrans.anchoredPosition;
if (m_IsVertical)
{
if (pos + listP.y > m_CellObjectHeight || pos + listP.y < -rectTrans.rect.height)
{
return true;
}
}
else
{
if (pos + listP.x < -m_CellObjectWidth || pos + listP.x > rectTrans.rect.width)
{
return true;
}
}
return false;
}
//对象池 机制 (存入, 取出) cell
protected Stack<GameObject> poolsObj = new Stack<GameObject>();
//取出 cell
protected virtual GameObject GetPoolsObj()
{
GameObject cell = null;
if (poolsObj.Count > 0)
{
cell = poolsObj.Pop();
}
if (cell == null)
{
cell = Instantiate(m_CellGameObject) as GameObject;
}
cell.transform.SetParent(m_Content.transform);
cell.transform.localScale = Vector3.one;
SetActive(cell, true);
return cell;
}
//存入 cell
protected virtual void SetPoolsObj(GameObject cell)
{
if (cell != null)
{
poolsObj.Push(cell);
SetActive(cell, false);
}
}
//回调
protected void Func(GameObject selectObject)
{
int num = 0;
m_GameObjectNumDict.TryGetValue(selectObject, out num);
onItemShow?.Invoke(selectObject, num);
}
void SetActive(GameObject cell, bool isShow)
{
cell.SetActive(isShow);
}
protected void OnDestroy()
{
DisposeAll();
}
}
其他2个使用案例C#脚本代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LoopListManager : MonoBehaviour
{
public BaseLoopList baseLoopList;
private Dictionary<int, BaseLoopListItem> loopListItemDict;
void Start()
{
baseLoopList.onItemShow += OnShow;
baseLoopList.onItemHide += OnHide;
loopListItemDict = new Dictionary<int, BaseLoopListItem>();
baseLoopList.Init();
//baseLoopList.SetPadding(20, 30, 0, 0);//支持Padding四个方向的偏移
baseLoopList.SetCount(50);
}
private BaseLoopListItem GetItem(GameObject cell, int index)
{
BaseLoopListItem item;
loopListItemDict.TryGetValue(index, out item);
if (item == null)
{
item = cell.GetComponent<BaseLoopListItem>();
loopListItemDict.Add(index, item);
}
return item;
}
public void OnShow(GameObject cell, int index)
{
Debug.Log("Show:" + index);
BaseLoopListItem item = GetItem(cell, index);
item.OnShow(index);
}
public void OnHide(GameObject cell, int index)
{
Debug.Log("Hide:" + index);
BaseLoopListItem item = GetItem(cell, index);
item.OnHide(index);
//注意: 无限循环列表的Item是重复利用的物体,逻辑上Hide之后必须从缓存中移除,否则会一定会出现Item刷新异常;
//原因: 若不移除会出现Show(index物体)时会拿到一个不正确位置的物体, 甚至很大可能是一个已显示中的物体(形成覆盖效果)
loopListItemDict.Remove(index);
}
}
using UnityEngine;
using TMPro;
public class BaseLoopListItem : MonoBehaviour
{
public TextMeshProUGUI text;
public void OnShow(int index)
{
text.text = index.ToString();
}
public void OnHide(int index)
{
text.text = "";
}
}
注意事项:无限循环列表的Item物体是重复利用的,因此OnHide回调后必须将传递进来的物体所有相关的缓存引用清空,例如案例是将其从字典移除loopListItemDict.Remove(index);
Content选择左上角锚点(看情况可选其他,但不要用自适应stretch) 以及不要挂任何自动控制布局组件,如:VerticalLayoutGroup、HorizontalLayoutGroup、GridLayoutGroup、ContentSizeFitter等...(因为会破坏无限循环列表对Content的控制)