UGUI ScrollRect滚动优化:无限循环利用Item
发表于2019-01-18
本篇文章给大家分享下UGUI ScrollRect的滚动优化,如何实现无限循环利用Item,一起来看看吧。
主要就几个函数。
这个函数主要就是获取各个对象引用还有计算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的范围内了 } }
根据当前Item 的坐标来计算滚动到当前Item的序号
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); } } }
效果截图: