之前做项目就想写一篇关于UI无限循环的总结,但一直没写过,一是自己也没理解清楚,二是项目忙,也没时间。现在由于在等待新项目的启动,终于有时间去总结一下UI方面的相关知识。
无限循环:主要应用在游戏排行榜,邮件,国家成员等需要大量创建Gameobj的场景,因为如果每多一条数据,就创建一个Gameobj去显示的话,那么游戏当中会存在大量的Obj,消耗性能,造成卡帧的现象,影响玩家游戏体验。这时候,就需要无限循环的功能了,因为游戏视窗大小是固定的,只显示一定数量的UI,这样,就可以用一定数量的UI去模拟所有数据的显示。
UIWrapContent就是NGUI封装好,用来实现这套功能的脚本。
接下来,就来详解这个脚本。
protected virtual void Start ()
{
SortBasedOnScrollMovement();
WrapContent(); //核心函数
if (mScroll != null) mScroll.GetComponent().onClipMove = OnMove; //由UIPanel的驱动,来更新对应的数据
mFirstTime = false;
}
public void SortBasedOnScrollMovement () //这段函数主要用来将他的子物体排序,代替了UIGrid的功能
{
if (!CacheScrollView()) return;
// Cache all children and place them in order
mChildren.Clear();
for (int i = 0; i < mTrans.childCount; ++i)
mChildren.Add(mTrans.GetChild(i));
// Sort the list of children so that they are in order
if (mHorizontal) mChildren.Sort(UIGrid.SortHorizontal);
else mChildren.Sort(UIGrid.SortVertical);
ResetChildPositions();
}
//上图对wrapcontent的实现,大致画了一下,以便于理解
public void WrapContent () //这个函数,是核心函数,用来实现无限循环的功能
{
float extents = itemSize * mChildren.Count * 0.5f; //计算所有的UI距离,并分为一半(后面用来判断用)
Vector3[] corners = mPanel.worldCorners;
for (int i = 0; i < 4; ++i) //将panel的世界坐标转化为本地坐标(具体哪些点,可以参考上图)
{
Vector3 v = corners[i];
v = mTrans.InverseTransformPoint(v);
corners[i] = v;
}
Vector3 center = Vector3.Lerp(corners[0], corners[2], 0.5f); //插值计算出中心点
bool allWithinRange = true;
float ext2 = extents * 2f; //ui真正的范围大小
if (mHorizontal) //横向
{
float min = corners[0].x - itemSize; //最小范围
float max = corners[2].x + itemSize; //最大范围
for (int i = 0, imax = mChildren.Count; i < imax; ++i) //遍历整个子物体
{
Transform t = mChildren[i];
float distance = t.localPosition.x - center.x; //判断移动超出的距离
if (distance < -extents) //距离超出左边范围
{
Vector3 pos = t.localPosition;
pos.x += ext2; //ui由最左边移动到最右边
distance = pos.x - center.x;
int realIndex = Mathf.RoundToInt(pos.x / itemSize);
if (minIndex == maxIndex || (minIndex <= realIndex && realIndex <= maxIndex)) //如果满足设置的数量,就进行位置变换
{
t.localPosition = pos;
UpdateItem(t, i);
}
else allWithinRange = false;
}
else if (distance > extents) //距离超出右边范围
{
Vector3 pos = t.localPosition;
pos.x -= ext2;
distance = pos.x - center.x;
int realIndex = Mathf.RoundToInt(pos.x / itemSize);
if (minIndex == maxIndex || (minIndex <= realIndex && realIndex <= maxIndex))
{
t.localPosition = pos;
UpdateItem(t, i);
}
else allWithinRange = false;
}
else if (mFirstTime) UpdateItem(t, i);
if (cullContent)
{
distance += mPanel.clipOffset.x - mTrans.localPosition.x;
if (!UICamera.IsPressed(t.gameObject))
NGUITools.SetActive(t.gameObject, (distance > min && distance < max), false);
}
}
}
else //纵向
{
float min = corners[0].y - itemSize;
float max = corners[2].y + itemSize;
for (int i = 0, imax = mChildren.Count; i < imax; ++i)
{
Transform t = mChildren[i];
float distance = t.localPosition.y - center.y;
if (distance < -extents)
{
Vector3 pos = t.localPosition;
pos.y += ext2;
distance = pos.y - center.y;
int realIndex = Mathf.RoundToInt(pos.y / itemSize);
if (minIndex == maxIndex || (minIndex <= realIndex && realIndex <= maxIndex))
{
t.localPosition = pos;
UpdateItem(t, i);
}
else allWithinRange = false;
}
else if (distance > extents)
{
Vector3 pos = t.localPosition;
pos.y -= ext2;
distance = pos.y - center.y;
int realIndex = Mathf.RoundToInt(pos.y / itemSize);
if (minIndex == maxIndex || (minIndex <= realIndex && realIndex <= maxIndex))
{
t.localPosition = pos;
UpdateItem(t, i);
}
else allWithinRange = false;
}
else if (mFirstTime) UpdateItem(t, i);
if (cullContent)
{
distance += mPanel.clipOffset.y - mTrans.localPosition.y;
if (!UICamera.IsPressed(t.gameObject))
NGUITools.SetActive(t.gameObject, (distance > min && distance < max), false);
}
}
}
mScroll.restrictWithinPanel = !allWithinRange; //用来控制ScrollView是否可以继续滑动
}
以上是NGUI实现无限循环功能的核心代码。因为无限循环在项目中经常需要用到,所以自己写了一套简易的框架,如有错误,请指正。
private List mLisData = new List() { "自己","天地", "众生", "白骨", "杨戬", "悟空", "大圣", "贪狼", "破军", "百花", "大鹏" }; //数据
private UIGridContainer mGrid; //项目代码(用来生成子物体,并负责排序,其实就是UIGrid的扩展)
private UIScrollView mScrollView;
private UIWrapContent mWrapContent;
private UIPanel mPanel;
private int nInterval = 20; //Item之间的间隔
private int nItemSize = 60; //Item的大小(这个笔者用的是60*60的正方形,如果不是正方形的话,size需要区分高和宽)
private int nMaxIndex = 0;
private int nMinIndex = 0;
private void Start()
{
mGrid = Utility.Get(this.transform, "Scroll View/grid");
mScrollView = Utility.Get(this.transform, "Scroll View");
mWrapContent = Utility.Get(this.transform, "Scroll View/grid");
mPanel = mScrollView.panel;
mWrapContent.onInitializeItem = OnInitializeItem;
//根据上图坐标系,横向的话,如果向右查看,索引是越来越大,向左是越来越小,同理,纵向的话,向上索引越来越大,向下索引越来越小
if (mScrollView.movement == UIScrollView.Movement.Horizontal)
{
nMinIndex = 0;
nMaxIndex = mLisData.Count - 1;
}
else
{
nMinIndex = 0;
nMaxIndex = mLisData.Count - 1;
}
InitHorizontalLoop();
//InitVerticalLoop();
}
private void InitHorizontalLoop()
{
if (mGrid == null || mWrapContent == null) return;
int contentSize = nItemSize + nInterval;
int nInitItemCount = (int)mPanel.GetViewSize().x / contentSize;
mGrid.CellWidth = contentSize;
mGrid.transform.localPosition = new Vector3(-119, 0, 0);
mGrid.MaxCount = nInitItemCount + 1;
//下面这些参数,是无限循环计算所需要的
mWrapContent.minIndex = nMinIndex; //最小索引
mWrapContent.maxIndex = nMaxIndex; //最大索引
mWrapContent.itemSize = contentSize; //item的size
}
private void InitVerticalLoop()
{
if (mGrid == null || mWrapContent == null) return;
int contentSize = nItemSize + nInterval;
int nInitItemCount = (int)mPanel.GetViewSize().y / contentSize;
mGrid.CellHeight = contentSize;
mGrid.transform.localPosition = new Vector3(0, 70, 0);
mGrid.MaxCount = nInitItemCount + 1;
mWrapContent.minIndex = -nMaxIndex;
mWrapContent.maxIndex = nMinIndex;
mWrapContent.itemSize = contentSize;
}
//无限循环以后的回调函数 warpIndex:子物体Item在父物体下的位置 realIndex:当先刷新的真实数据索引
private void OnInitializeItem(GameObject go, int wrapIndex, int realIndex)
{
Debug.Log("wrapIndex ----:" + wrapIndex);
Debug.Log("realIndex ----:" + realIndex);
int nIndex = Mathf.Abs(realIndex);
if (nIndex > (mLisData.Count - 1)) return;
UILabel lab = Utility.Get(go.transform, "Label");
lab.text = mLisData[nIndex];
}
//如果有要求,特定显示某一个元素,可以用下面的代码来实现,横向(以0为界,右边是正,左边是负)纵向(以0为界,下面是负,上面是正)因为这是无限循环,所以无法用ScrollView自带的通过SetDragAmount来实现
//index: -num ---0--- +num 之间取值
public void ShowElementByIndex(int index)
{
if (mScrollView.movement == UIScrollView.Movement.Horizontal)
{
Vector2 offset = new Vector2(index * nItemSize, 0);
mScrollView.transform.localPosition = new Vector3(-offset.x, 0, 0);
mScrollView.panel.clipOffset = offset;
}
else
{
Vector2 offset = new Vector2(0, -index * nItemSize);
mScrollView.transform.localPosition = new Vector3(0, offset.y, 0);
mScrollView.panel.clipOffset = offset;
}
}