Unity UGUI 无限循环列表 ----------验证通过版
上面为原创链接;下面是我个人的一些见解和看法,做出对这个优化比较客观的评价。
前提概要:
Content(滑动区域)的中心点Pivot(0, 1)位于左上角、锚点Anchors位于左上角
ScrollRect(滚动区域):宽度、高度为400
一、核心思路
//判断是否超出显示范围
protected bool IsOutRange(float pos)
{
//lisP是Content的anchoredPosition
Vector3 listP = m_ContentRectTrans.anchoredPosition;
//垂直ScrollRect情况,超出显示范围考虑的是Y值
if (m_IsVertical)
{
//pos是Slot(Content下的子物体)的localPosition.y
//判断是否超出显示范围要有一个边界,而边界的坐标系以ScrollRect左上角为中心点
//pos + listP.y 是为了将Slot的y值从Content左上角为中心点的坐标系空间
//转为以ScrollRect左上角为中心点的坐标系下的y值;
//PS:(以ScrollRect左上角为中心点的坐标系)简称"ScrollRect空间","Content空间"同理
//转化原理:1. listP.y是Content在ScrollRect空间下的y值
// 2. pos是Slot在Content空间下的y值
//将pos从Content空间转化为ScrollRect空间,仅仅只需一个偏移!画个图;
if (pos + listP.y > m_CellObjectHeight || pos + listP.y < -rectTrans.rect.height)
{
return true;
}
}
else//水平ScrollRect情况,考虑X值原理同上,不再阐述
{
if (pos + listP.x < -m_CellObjectWidth || pos + listP.x > rectTrans.rect.width)
{
return true;
}
}
return false;
}
在进行如上解释后,再解释一波Vertical垂直情况的边界问题:
至此,上面解释了一下如何决定一个Item是否在可见区域内的问题!
通过简单的锚点设置+中心点设置,就能通过slot.y+content.y的方式将slot.y转为ScrollRect空间下的坐标y值,进行和边界比较从而得出是否在可视范围内。
注意:这个优化ScrollRect的代码,在初始化ScrollRect时就已经计算好Content最佳大小,每一个Slot的相对于Content的位置都会保存起来,根据这个slot相对位置先通过转化空间再判断是否在可视范围内来进行是否显示一个Slot在该slot相对位置上;
Slot生成可以用对象池技术进行再次优化;
当Slot相对位置不在可视范围内,就进行回收Slot物体并清空物体数据;
其中,在进行显示物体时,会调用一个刷新slot回调方法,传递slot物体自身和物体索引号,进行初始化物体。
原本功能:支持spacing间隔、Vertical布局、Horizontal布局、一行或一列自定义Slot个数,刷新Slot回调、清空ScrollRect等
可扩展的地方有:
1. 支持不同大小的Slot
2. 支持约束Slot的大小
3. 支持配置表信息初始化ScrollRect,目前是仅仅传递一个int变量 代表ScrollRect有多少个Slot
4. 支持其他回调,如:单独删除某个Slot,进行刷新ScrollRect;
5. 转成Lua
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BagPanel : MonoBehaviour
{
public BaseLoopList baseLoopList;
private void Awake()
{
baseLoopList.Init(UpdateCell);
baseLoopList.ShowList(30);
}
public void UpdateCell(GameObject cell, int index)
{
cell.GetComponent().SetNum(index.ToString());
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Slot : MonoBehaviour {
private Text _text;
public Text Text
{
get
{
if (_text==null)
{
_text = GetComponentInChildren();
}
return _text;
}
}
public void SetNum(string n)
{
Text.text = n;
}
}
//*****************************-》 基类 循环列表 《-****************************
//初始化:
// Init()
//刷新列表:
// ShowList(int = 数量)
//Lua回调:
//Func(GameObject = Cell, int = Index) //刷新列表
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
using System.Collections;
using UnityEngine.EventSystems;
using System;
public class BaseLoopList : MonoBehaviour, IBeginDragHandler, IEndDragHandler, IDragHandler
{
public int m_Row = 1; //排
public bool m_IsVertical = true;
public float m_Spacing = 0f; //间距
public GameObject m_CellGameObject; //指定的cell
//-> c# 回调相关
public delegate void CallBackFunc(GameObject cell, int index);
protected CallBackFunc m_CallBackFunc = null; //c# 回调
//-> Lua 回调相关
protected object m_Members;
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; //是否清空列表
//c# 初始化
public virtual void Init(CallBackFunc func)
{
m_CallBackFunc = func;
DisposeAll();
if (m_isInited)
return;
m_Content = this.GetComponent().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();
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();
Rect planeRect = rectTrans.rect;
m_PlaneHeight = planeRect.height;
m_PlaneWidth = planeRect.width;
//记录 Content 信息
m_ContentRectTrans = m_Content.GetComponent();
Rect contentRect = m_ContentRectTrans.rect;
m_ContentHeight = contentRect.height;
m_ContentWidth = 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();
m_ScrollRect.onValueChanged.RemoveAllListeners();
//添加滑动事件
m_ScrollRect.onValueChanged.AddListener(delegate (Vector2 value) { ScrollRectListener(value); });
m_isInited = true;
}
//检查 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 virtual void ShowList(string numStr) { }
public virtual void ShowList(int num)
{
m_MinIndex = -1;
m_MaxIndex = -1;
//-> 计算 Content 尺寸
if (m_IsVertical)
{
float contentSize = (m_Spacing + m_CellObjectHeight) * Mathf.CeilToInt((float)num / m_Row);
m_ContentHeight = contentSize;
m_ContentWidth = m_ContentRectTrans.sizeDelta.x;
contentSize = contentSize < rectTrans.rect.height ? rectTrans.rect.height : contentSize;
m_ContentRectTrans.sizeDelta = new Vector2(m_ContentWidth, contentSize);
if (num != m_MaxCount)
{
m_ContentRectTrans.anchoredPosition = new Vector2(m_ContentRectTrans.anchoredPosition.x, 0);
}
}
else
{
float contentSize = (m_Spacing + m_CellObjectWidth) * Mathf.CeilToInt((float)num / m_Row);
m_ContentWidth = contentSize;
m_ContentHeight = m_ContentRectTrans.sizeDelta.x;
contentSize = contentSize < rectTrans.rect.width ? rectTrans.rect.width : contentSize;
m_ContentRectTrans.sizeDelta = new Vector2(contentSize, 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);
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().anchoredPosition = tempCellInfo.pos;
tempCellInfo.obj.name = i.ToString();
tempCellInfo.obj.SetActive(true);
//-> 回调 Lua 函数
Func(tempCellInfo.obj);
}
else
{
SetPoolsObj(tempCellInfo.obj);
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_Spacing * Mathf.FloorToInt(i / m_Row);
rowPos = m_CellObjectWidth * (i % m_Row) + m_Spacing * (i % m_Row);
cellInfo.pos = new Vector3(rowPos, -pos, 0);
}
else
{
pos = m_CellObjectWidth * Mathf.FloorToInt(i / m_Row) + m_Spacing * Mathf.FloorToInt(i / m_Row);
rowPos = m_CellObjectHeight * (i % m_Row) + m_Spacing * (i % m_Row);
cellInfo.pos = new Vector3(pos, -rowPos, 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().anchoredPosition = cellInfo.pos;
cell.gameObject.name = i.ToString();
//-> 存数据
cellInfo.obj = cell;
m_CellInfos[i] = cellInfo;
//-> 回调 Lua 函数
Func(cell);
}
m_MaxCount = num;
m_IsInited = true;
}
//实时刷新列表时用
public virtual void UpdateList()
{
for (int i = 0, length = m_CellInfos.Length; i < length; i++)
{
CellInfo cellInfo = m_CellInfos[i];
if (cellInfo.obj != null)
{
float rangePos = m_IsVertical ? cellInfo.pos.y : cellInfo.pos.x;
if (!IsOutRange(rangePos))
{
Func(cellInfo.obj);
}
}
}
}
//刷新某一项
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().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);
m_CellInfos[i].obj = null;
}
}
else
{
if (obj == null)
{
//优先从 poolsObj中 取出 (poolsObj为空则返回 实例化的cell)
GameObject cell = GetPoolsObj();
cell.transform.localPosition = pos;
cell.gameObject.name = i.ToString();
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 poolsObj = new Stack();
//取出 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 = int.Parse(selectObject.name) + 1;
if (m_CallBackFunc != null)
{
m_CallBackFunc(selectObject, num);
}
}
void SetActive(GameObject cell, bool isShow)
{
cell.SetActive(isShow);
}
public void DisposeAll()
{
}
protected void OnDestroy()
{
DisposeAll();
}
public virtual void OnClickCell(GameObject cell) { }
//-> ExpandLoopList 函数
public virtual void OnClickExpand(int index) { }
//-> FlipLoopList 函数
public virtual void SetToPageIndex(int index) { }
public virtual void OnBeginDrag(PointerEventData eventData)
{
}
public void OnDrag(PointerEventData eventData)
{
}
public virtual void OnEndDrag(PointerEventData eventData)
{
}
protected void OnDragListener(Vector2 value)
{
}
}