在游戏中我们时常看到像背包一样用ScrollView来拖动展示一些物品信息,物品一多那么开始克隆的Item就会多这样使得开始打开界面时耗时长,而且在滑动的时候会出现卡顿,滑动不流畅,滑动卡顿的主要原因是我们未将移出UIScrollView的Item设为false,这会导致Panel在重建DrawCall是将所有的Item都算进来,这时在构建Mesh时会很耗时特别是Item比较多的时候,针对这两个问题我们可以用少量的Item循环使用来解决;NGUI本身就给我们提供了这个代码:UIWrapContent,具体用法NGUI有事例,但它们都是单行单列的,这里我简单的说下怎么用UIWrapContent来展示多行多列的。
先来看看效果:
这里循环使用15个Item来显示0~31,拉到底时不能再往下或往上拉。
Ok,进入主题,下面来说说我的做法,先来看看下面代码:
using UnityEngine;
public class MultiRowWrapContent : UIWrapContent
{
public int RowNum = 3;
protected override void ResetChildPositions()
{
for (int i = 0, imax = mChildren.Count; i < imax; ++i)
{
Transform t = mChildren[i];
t.localPosition = mHorizontal ? new Vector3(i * itemSize, 0f, 0f) : new Vector3((i % RowNum) * itemSize, -(i / RowNum) * itemSize, 0f);
UpdateItem(t, i);
}
}
protected override void UpdateItem(Transform item, int index)
{
if (onInitializeItem != null)
{
int yIndex = Mathf.Abs(Mathf.RoundToInt(item.localPosition.y / itemSize));
int xIndex = Mathf.Abs(Mathf.RoundToInt(item.localPosition.x / itemSize));
int realIndex = RowNum * yIndex + xIndex;
onInitializeItem(item.gameObject, index, realIndex);
}
}
//重写这个函数主要是去掉排序
//if (mHorizontal) mChildren.Sort(UIGrid.SortHorizontal);
// else mChildren.Sort(UIGrid.SortVertical);
public override void SortBasedOnScrollMovement()
{
if (!CacheScrollView()) return;
// Cache all children and place them in order
mChildren.Clear();
for (int i = 0; i < mTrans.childCount; ++i)
{
Transform t = mTrans.GetChild(i);
if (hideInactive && !t.gameObject.activeInHierarchy) continue;
mChildren.Add(t);
}
ResetChildPositions();
}
public override void WrapContent()
{
float extents = itemSize * (mChildren.Count / RowNum) * 0.5f;
Vector3[] corners = mPanel.worldCorners;
for (int i = 0; i < 4; ++i)
{
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;
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;
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;
mScroll.InvalidateBounds();
}
}
上面的代码是继承了UIWrapContent并对里面的几个函数进行重写的,主要的改动有:
1、各个Item的位置设置:t.localPosition = mHorizontal ? new Vector3(i * itemSize, 0f, 0f) : new Vector3((i % RowNum) * itemSize, -(i / RowNum) * itemSize, 0f);这里我只做了垂直方向的,(i%RowNum)*itemSize横向坐标,-(i/RowNum)*itemSize纵向坐标;
2、改变了 UpdateItem中realIndex的计算这个是要显示的数据角标:先算出要改变的item在的行号和列号,它要显示的数据的角标=行号*列数+列号;
3、WrapContent()里的float extents = itemSize * mChildren.Count / RowNum * 0.5f;这个是计算所有Item按照每行RowNum个排列纵向长度的一半;
主要就是这三个地方的改动,还有就是我在后显示做了处理,就是最后一行不满RowNum个时,将没有数据可替换的隐藏,当有时显示。代码如下:
void OnInitializeItem(GameObject go, int wrapIndex, int realIndex)
{
go.name = Mathf.Abs(realIndex).ToString();
UILabel label = go.transform.FindChild("Label").GetComponent();
int index = Mathf.Abs(realIndex);
if (index < nums.Count)
{
go.SetActive(true);
label.text = nums[index].ToString();
}
else
{
go.SetActive(false);
}
}
Ok,到此基本已经好了,这样就实现了多行多列的显示了。