之所以产生这个需求,是因为有些滚动面板里面的内容过多,会出现开始实例化的时候时间过长造成卡顿,且 DrawCall暴增。所以解决方案是只实例化出当前能显示的数目多一行, 然后动态设置内容的位置。
昨晚上尝试做一个Demo,但是失败了,主要是复用的内容位置的计算移动比较麻烦,感觉代价太大了。因为这样做的目的主要是为了解决初始化的时候实例对象太多,造成卡顿,和DrawCall的增加, 后面想一个折中的办法,就是界面初始化的时候只实例化当前界面所能显示的内容,其他的延迟显示。但是这方法感觉太别扭了,然后在网上找,已经有人做出来了。待会贴上来
见过很多版本,这个是最完美的版本。感谢作者:http://blog.csdn.net/mutou_222/article/details/50455729?ref=myread
暑假的时候参照NGUI的循环ScrollView的思路写的,当时给上传到了资源下载频道,因为平时自己也要在CSDN下东西,有些资源要积分才能下载,所以就挂了3分,想着供自己以后下载可以用。到现在也过了将近半年的时间了,去看了下也有近100次的下载量了。
自己平时上来CSDN比较少,写文章就更少了,再加上文笔真的是太烂了,人又懒。。。公司里一直都用的NGUI,自己也很久没有去碰过UGUI了,今天再打开这个工程,发现好多地方都不熟悉了啊,想想果然还是需要记录一下的,今天打开代码又看了几遍,理理思路。
主要就几个函数。
这个函数主要就是获取各个对象引用还有计算ScrollRect的四角坐标
设置mRTrans.pivot的目的是,如果按照默认的pivot在中心也就是(0.5,0.5)的话,当Item的项数量增多时Item的会向两段延伸,但是需要的是向一端延伸,如果有Item增加进来只需要加载末尾就好了而前面的Item不动。所以需要设置pivot在一端而不是在中心,那么在延伸尺寸的时候只会向另一端延伸。
- void InitValue()
- {
- if (ConstraintCount <= 0)<span style="white-space:pre"> </span>//ConstraintCount表示横纵排列的个数 若是横向排列 就表示行数 纵向排列就表示列数
- ConstraintCount = 1;
- if (minIndex > maxIndex) minIndex = maxIndex;
- mTrans = transform;
- mRTrans = transform.GetComponent<RectTransform>();
- mScroll = transform.parent.GetComponent<ScrollRect>();
- mHorizontal = mScroll.horizontal;
- SR_size =transform.parent.GetComponent<RectTransform>().rect.size;
- //四角坐标 横着数
- conners[0] = new Vector3(-SR_size.x / 2f, SR_size.y / 2f,0);<span style="white-space:pre"> </span>//这里主要是计算一下ScrollRect 四个角的坐标 后面计算各个Item的坐标要用到
- conners[1] = new Vector3(SR_size.x / 2f, SR_size.y / 2f,0);
- conners[2] = new Vector3(-SR_size.x / 2f, -SR_size.y / 2f,0);
- conners[3] = new Vector3(SR_size.x / 2f, -SR_size.y / 2f,0);
- for (int i = 0; i < 4; i++)//将四角坐标转换为世界坐标
- {
- Vector3 temp = transform.parent.TransformPoint(conners[i]);
- conners[i].x = temp.x;
- conners[i].y = temp.y;
- }
- mRTrans.pivot = new Vector2(0, 1);//设置panel的中心在左上角
- mScroll.onValueChanged.AddListener(delegate { WrapContent(); });//添加滚动事件回调
- startPos = mTrans.localPosition;
- }
初始化的时候对Item进行一次排列
- void ResetChildPosition()
- {
- int rows = 1, cols = 1;
- Vector2 startAxis = new Vector2(cell_x / 2f, -cell_y / 2f);//起始位置 按照Item尺寸计算第一个Item排列的初始位置
- int imax = mChild.Count;//Item元素数量
- //初始化行列数 <span style="font-family: Arial, Helvetica, sans-serif;">根据排列方式计算行数和列数 </span>
- if (arrangeType == ArrangeType.Vertical) //垂直排列 则适应行数
- {
- rows = ConstraintCount;
- cols = (int)Mathf.Ceil((float)imax / (float)rows);
- extents = (float)(cols * cell_x) * 0.5f; //extents是预设置的所有Item所占的长度或高度的一半 用在循环的时候计算Item的坐标
- }
- else if (arrangeType == ArrangeType.Horizontal) //水平排列则适应列数
- {
- cols = ConstraintCount;
- rows = (int)Mathf.Ceil((float)imax / (float)cols);
- extents = (float)(rows * cell_y) * 0.5f;
- }
- for (int i = 0; i < imax; i++)//对Item进行一次排列
- {
- Transform temp = mChild[i];
- int x = 0, y = 0;//行列号
- if (arrangeType == ArrangeType.Horizontal) { x = i / cols; y = i % cols; }//根据Item的序号计算在排列中的行号和列号
- else if (arrangeType == ArrangeType.Vertical) { x = i % rows; y = i / rows; }
- temp.localPosition = new Vector2(startAxis.x + y * cell_x, startAxis.y - x * cell_y);
- if (minIndex == maxIndex || (i >= minIndex && i <= maxIndex))
- {
- cullContent = true;
- temp.gameObject.SetActive(true);//若当前Item的序号在要显示的范围之内 则设置当前Item为可见 并更新Panel的尺寸以适应Item
- UpdateRectsize(temp.localPosition);//更新panel的尺寸
- UpdateItem(temp, i, i);
- }
- else
- {
- temp.gameObject.SetActive(false);
- cullContent = temp.gameObject.activeSelf;
- }//若预设值的Item的数量超过了 想要显示的Item数量 则隐藏所有超出的Item 并不再更新Panel的尺寸,设置cullContent为false,
- //因为 所有要显示的Item都已经包含在Panel的范围内了
- }
- }
- int getRealIndex(Vector2 pos)//计算realindex
- {
- int x = (int)Mathf.Ceil(-pos.y / cell_y) - 1;//行号
- int y = (int)Mathf.Ceil(pos.x / cell_x) - 1;//列号
- int realIndex;
- if (arrangeType == ArrangeType.Horizontal) realIndex = x * ConstraintCount + y;
- else realIndex = x + ConstraintCount * y;
- return realIndex;
- }
最主要的就是这个函数了,在ScrollRect的滚动的时候就会调用这个函数 ,每次滚动都会遍历一遍所有Item,计算它们的下标,坐标,设置是否显示等
- void WrapContent()
- {
- Vector3[] conner_local = new Vector3[4];//mTrans是所有Item的父级,
- for (int i = 0; i < 4; i++) //这里计算了ScrollRect即显示区域的中心坐标(用上面求得的四角坐标)
- { //并转换为相对Item父级Panel的相对坐标 因为接下来将要计算各个Item的坐标,
- conner_local[i] = mTrans.InverseTransformPoint(conners[i]);//所以要设置坐标的参照相同
- }
- //计算ScrollRect的中心坐标 相对于local的坐标
- Vector2 center = (conner_local[3] + conner_local[0]) / 2f;
- if (mHorizontal)//当排列方式是水平排列的时候
- {
- float min = conner_local[0].x - cell_x;//显示区域用显示区域的左上角和右下角坐标计算显示区域的边界坐标
- float max = conner_local[3].x + cell_x;//考虑到会出现Item位于边界上 出现一般在区域内 一般在区域外的情况,//所以这里将显示边界向外扩展一个Item的尺寸
- for (int i = 0, imax = mChild.Count; i < imax; i++)//遍历所有Item
- {
- Transform temp = mChild[i];
- float distance = temp.localPosition.x - center.x;
- if (distance <-extents)//根据Item距离中心的距离是否超出显示范围(extents为前面求得的显示区域尺寸的一半) 判断是否应该重设Item的坐标
- {
- Vector2 pos = temp.localPosition;
- pos.x += extents * 2f; //加一个显示尺寸,设置Item到列表末去
- int realIndex = getRealIndex(pos); //根据更改后的坐标计算当前Item的序号
- if (minIndex == maxIndex || (realIndex >= minIndex && realIndex < maxIndex))//若当前Item的序号在需要显示的范围内
- {
- UpdateRectsize(pos); //则重新设置Item的坐标和内容 并更新Panel的尺寸
- temp.localPosition = pos; //设置Item内容
- UpdateItem(temp, i, realIndex);
- }
- }
- if (distance > extents)//向右滚动的情况
- {
- Vector2 pos = temp.localPosition;
- pos.x -= extents * 2f;
- int realIndex = getRealIndex(pos);
- if (minIndex == maxIndex || (realIndex >= minIndex && realIndex < maxIndex))
- {
- temp.localPosition = pos; //设置Item内容
- UpdateItem(temp, i, realIndex);
- }
- }
- if (cullContent)//设置裁剪部分是否隐藏或显示根据Item是否在显示边界内 控制是否显示或隐藏
- {
- Vector2 pos=temp.localPosition;
- temp.gameObject.SetActive((pos.x>min&&pos.x<max)?true:false);
- }
- }
- }
- else
- {
- float min = conner_local[3].y - cell_y;//显示区域
- float max = conner_local[0].y + cell_y;
- for (int i = 0, imax = mChild.Count; i < imax; i++)
- {
- Transform temp = mChild[i];
- float distance = temp.localPosition.y - center.y;
- if (distance < -extents)
- {
- Vector2 pos = temp.localPosition;
- pos.y += extents * 2f;
- int realIndex = getRealIndex(pos);
- if (minIndex == maxIndex || (realIndex >= minIndex && realIndex < maxIndex))
- {
- temp.localPosition = pos; //设置Item内容
- UpdateItem(temp, i, realIndex);
- }
- }
- if (distance > extents)
- {
- Vector2 pos = temp.localPosition;
- pos.y -= extents * 2f;
- int x = (int)Mathf.Ceil(-pos.y / cell_y)-1;//行号
- int y = (int)Mathf.Ceil(pos.x / cell_x)-1;//列号
- int realIndex;
- if (arrangeType == ArrangeType.Horizontal)
- realIndex = x * ConstraintCount + y;
- else realIndex = x + ConstraintCount * y;
- if (minIndex == maxIndex || (realIndex >= minIndex && realIndex < maxIndex))
- {
- UpdateRectsize(pos);
- temp.localPosition = pos; //设置Item内容
- UpdateItem(temp, i, realIndex);
- }
- }
- if (cullContent)//设置裁剪部分是否隐藏
- {
- Vector2 pos = temp.localPosition;
- temp.gameObject.SetActive((pos.y > min && pos.y < max) ? true : false);
- }
- }
- }
- }
完整代码
- using UnityEngine;
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine.UI;
- [ExecuteInEditMode]
- public class GridAndLoop : MonoBehaviour
- {
- /// <summary>
- /// 设置Item内容的委托
- /// </summary>
- /// <param name="item">Item对象</param>
- /// <param name="wrapIndex">Item在Grid中的序号</param>
- /// <param name="realIndex">当前Item在List中的序号</param>
- public delegate void OnInitializeItem(GameObject item, int wrapIndex, int realIndex);
- /// <summary>
- /// 排列方式枚举
- /// </summary>
- public enum ArrangeType
- {
- Horizontal=0,//水平排列
- Vertical=1,//垂直排列
- }
- /// <summary>
- /// Item的尺寸
- /// </summary>
- public int cell_x = 100, cell_y = 100;
- /// <summary>
- /// 是否隐藏裁剪部分
- /// </summary>
- public bool cullContent = true;
- /// <summary>
- /// Item最小序号
- /// </summary>
- public int minIndex = 0;
- /// <summary>
- /// Item最大序号
- /// </summary>
- public int maxIndex = 0;
- /// <summary>
- /// 排列方式
- /// </summary>
- public ArrangeType arrangeType=ArrangeType.Horizontal;
- /// <summary>
- /// 行列个数 0表示1列
- /// </summary>
- public int ConstraintCount = 0;
- /// <summary>
- /// 设置Item的委托
- /// </summary>
- public OnInitializeItem onInitializeItem;
- /// <summary>
- ///当前对象
- /// </summary>
- Transform mTrans;
- /// <summary>
- /// 当前RectTransform对象
- /// </summary>
- RectTransform mRTrans;
- /// <summary>
- /// ScrollRect
- /// </summary>
- ScrollRect mScroll;
- /// <summary>
- /// 滚动方向
- /// </summary>
- bool mHorizontal;
- /// <summary>
- /// 元素链表
- /// </summary>
- List<Transform> mChild=new List<Transform>();
- /// <summary>
- /// 显示区域长度或高度的一半
- /// </summary>
- float extents=0;
- Vector2 SR_size = Vector2.zero;//SrollRect的尺寸
- Vector3[] conners = new Vector3[4];//ScrollRect四角的世界坐标
- Vector2 startPos;//ScrollRect的初始位置
- void Start()
- {
- InitList();
- }
- int sortByName(Transform a, Transform b) { return string.Compare(a.name, b.name); }
- /// <summary>
- /// 初始化mChild链表
- /// </summary>
- void InitList()
- {
- int i,ChildCount;
- InitValue();
- mChild.Clear();
- for (i = 0, ChildCount = mTrans.childCount; i < ChildCount; i++)
- mChild.Add(mTrans.GetChild(i));
- ResetChildPosition();
- // mChild.Sort(sortByName);//按照Item名字排序
- }
- void InitValue()
- {
- if (ConstraintCount <= 0)
- ConstraintCount = 1;
- if (minIndex > maxIndex) minIndex = maxIndex;
- mTrans = transform;
- mRTrans = transform.GetComponent<RectTransform>();
- mScroll = transform.parent.GetComponent<ScrollRect>();
- mHorizontal = mScroll.horizontal;
- SR_size =transform.parent.GetComponent<RectTransform>().rect.size;
- //四角坐标 横着数
- conners[0] = new Vector3(-SR_size.x / 2f, SR_size.y / 2f,0);
- conners[1] = new Vector3(SR_size.x / 2f, SR_size.y / 2f,0);
- conners[2] = new Vector3(-SR_size.x / 2f, -SR_size.y / 2f,0);
- conners[3] = new Vector3(SR_size.x / 2f, -SR_size.y / 2f,0);
- for (int i = 0; i < 4; i++)
- {
- Vector3 temp = transform.parent.TransformPoint(conners[i]);
- conners[i].x = temp.x;
- conners[i].y = temp.y;
- }
- mRTrans.pivot = new Vector2(0, 1);//设置panel的中心在左上角
- mScroll.onValueChanged.AddListener(delegate { WrapContent(); });//添加滚动事件回调
- startPos = mTrans.localPosition;
- }
- //初始化各Item的坐标
- [ContextMenu("RePosition")]
- public virtual void RePosition()
- {
- InitList();
- }
- void Update()
- {
- if (Application.isPlaying) enabled = false;
- RePosition();
- }
- void ResetChildPosition()
- {
- int rows=1, cols=1;
- Vector2 startAxis = new Vector2(cell_x/2f,-cell_y/2f);//起始位置
- int i;
- int imax = mChild.Count;//Item元素数量
- //初始化行列数
- if (arrangeType == ArrangeType.Vertical) //垂直排列 则适应行数
- {
- rows = ConstraintCount;
- cols = (int)Mathf.Ceil((float)imax/(float)rows);
- extents = (float)(cols * cell_x)* 0.5f;
- }
- else if (arrangeType == ArrangeType.Horizontal) //水平排列则适应列数
- {
- cols = ConstraintCount;
- rows = (int)Mathf.Ceil((float)imax / (float)cols);
- extents = (float)(rows * cell_y)* 0.5f;
- }
- for (i = 0; i < imax; i++)
- {
- Transform temp = mChild[i];
- int x=0,y=0;//行列号
- if (arrangeType == ArrangeType.Horizontal) { x = i / cols; y = i % cols; }
- else if (arrangeType == ArrangeType.Vertical) { x = i % rows; y = i / rows; }
- temp.localPosition = new Vector2(startAxis.x + y * cell_x, startAxis.y - x * cell_y);
- if (minIndex == maxIndex || (i >= minIndex && i <= maxIndex))
- {
- cullContent = true;
- temp.gameObject.SetActive(true);
- UpdateRectsize(temp.localPosition);//更新panel的尺寸
- UpdateItem(temp, i, i);
- }
- else
- {
- temp.gameObject.SetActive(false);
- cullContent =temp.gameObject.activeSelf;//如果预制Item数超过maxIndex则将超过部分隐藏 并 设置cullCintent为ufalse 并且不再更新 panel尺寸
- }
- }
- }
- /// <summary>
- /// ScrollRect复位
- /// </summary>
- public void ResetPosition()
- {
- mTrans.localPosition = startPos;
- }
- /// <summary>
- /// 更新panel的尺寸
- /// </summary>
- /// <param name="pos"></param>
- void UpdateRectsize(Vector2 pos)
- {
- if (arrangeType == ArrangeType.Vertical)
- {
- // if(mRTrans.rect.width<pos.x+cell_x)
- mRTrans.sizeDelta = new Vector2(pos.x + cell_x, ConstraintCount*cell_y);
- }
- else
- {
- // if(mRTrans.rect.height<-pos.y+cell_y)
- mRTrans.sizeDelta = new Vector2(ConstraintCount * cell_x, -pos.y + cell_y);
- }
- }
- //Vector2 calculatePos(Vector2 world,Vector2 target,Vector2 lcal)
- //{
- // Vector2 temp = world - target;
- // temp.x /= (target.x/lcal.x);
- // temp.y /= (target.y/lcal.y);
- // return temp;
- //}
- int getRealIndex(Vector2 pos)//计算realindex
- {
- int x = (int)Mathf.Ceil(-pos.y / cell_y) - 1;//行号
- int y = (int)Mathf.Ceil(pos.x / cell_x) - 1;//列号
- int realIndex;
- if (arrangeType == ArrangeType.Horizontal) realIndex = x * ConstraintCount + y;
- else realIndex = x + ConstraintCount * y;
- return realIndex;
- }
- void WrapContent()
- {
- Vector3[] conner_local = new Vector3[4];
- for (int i = 0; i < 4; i++)
- {
- conner_local[i]=mTrans.InverseTransformPoint(conners[i]);
- }
- //计算ScrollRect的中心坐标 相对于this的坐标
- Vector2 center = (conner_local[3] + conner_local[0]) / 2f;
- if (mHorizontal)
- {
- float min = conner_local[0].x - cell_x;//显示区域
- float max = conner_local[3].x + cell_x;
- for (int i = 0, imax = mChild.Count; i < imax; i++)
- {
- Transform temp = mChild[i];
- float distance = temp.localPosition.x - center.x;
- if (distance <-extents)
- {
- Vector2 pos = temp.localPosition;
- pos.x += extents * 2f;
- int realIndex = getRealIndex(pos);
- if (minIndex == maxIndex || (realIndex >= minIndex && realIndex < maxIndex))
- {
- UpdateRectsize(pos);
- temp.localPosition = pos;
- //设置Item内容
- UpdateItem(temp, i, realIndex);
- }
- }
- if (distance > extents)
- {
- Vector2 pos = temp.localPosition;
- pos.x -= extents * 2f;
- int realIndex = getRealIndex(pos);
- if (minIndex == maxIndex || (realIndex >= minIndex && realIndex < maxIndex))
- {
- temp.localPosition = pos;
- //设置Item内容
- UpdateItem(temp, i, realIndex);
- }
- }
- if (cullContent)//设置裁剪部分是否隐藏
- {
- Vector2 pos=temp.localPosition;
- temp.gameObject.SetActive((pos.x>min&&pos.x<max)?true:false);
- }
- }
- }
- else
- {
- float min = conner_local[3].y - cell_y;//显示区域
- float max = conner_local[0].y + cell_y;
- for (int i = 0, imax = mChild.Count; i < imax; i++)
- {
- Transform temp = mChild[i];
- float distance = temp.localPosition.y - center.y;
- if (distance < -extents)
- {
- Vector2 pos = temp.localPosition;
- pos.y += extents * 2f;
- int realIndex = getRealIndex(pos);
- if (minIndex == maxIndex || (realIndex >= minIndex && realIndex < maxIndex))
- {
- temp.localPosition = pos;
- //设置Item内容
- UpdateItem(temp, i, realIndex);
- }
- }
- if (distance > extents)
- {
- Vector2 pos = temp.localPosition;
- pos.y -= extents * 2f;
- int x = (int)Mathf.Ceil(-pos.y / cell_y)-1;//行号
- int y = (int)Mathf.Ceil(pos.x / cell_x)-1;//列号
- int realIndex;
- if (arrangeType == ArrangeType.Horizontal) realIndex = x * ConstraintCount + y;
- else realIndex = x + ConstraintCount * y;
- if (minIndex == maxIndex || (realIndex >= minIndex && realIndex < maxIndex))
- {
- UpdateRectsize(pos);
- temp.localPosition = pos;
- //设置Item内容
- UpdateItem(temp, i, realIndex);
- }
- }
- if (cullContent)//设置裁剪部分是否隐藏
- {
- Vector2 pos = temp.localPosition;
- temp.gameObject.SetActive((pos.y > min && pos.y < max) ? true : false);
- }
- }
- }
- }
- void UpdateItem(Transform item,int index,int realIndex)//跟新Item的内容
- {
- if (onInitializeItem != null)
- {
- onInitializeItem(item.gameObject, index, realIndex);
- }
- }
- }
效果截图
我的例子的下载地址
http://download.csdn.net/detail/mutou_222/9015017