天天看點

UGUI ScrollRect滾動優化:無限循環利用Item

暑假的時候參照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的範圍内了

        }
    }
           

根據目前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);
        }
    }
    
}
           

效果截圖

UGUI ScrollRect滾動優化:無限循環利用Item
UGUI ScrollRect滾動優化:無限循環利用Item

我的例子的下載下傳位址

http://download.csdn.net/detail/mutou_222/9015017