[UGUI]ListLayoutGroup--可重用的滾動列表


一、相同cell size的可重用列表:

為了不生成太多的GameObject,當滾動的時候,需要將出框的item重復利用起來。這個網上已經有了很多例子。我為了項目使用方便,在GridLayoutGroup基礎上修改了一下,配合ScrollRect使用

首先在傳入數據的時候,需要知道要顯示多少數據,為了拖動時看不到突然消失的item,會多顯示一個:

    private float GetScrollRectSize()
    {
        var rectSize = m_scrollRect.rectTransform.rect.size;
        return IsVertical ? rectSize.y : rect.y;
    }
    private float GetCellSize()
    {
        return IsVertical ? cellSize.y + spacing.y : cellSize.x + spacing.x;
    }
    public void SetData<P, D>(ICollection<D> dataList, System.Action<int, P, D> setContentHandler)
        where P : MonoBehaviour
    {
        ........
        displayListCount = Mathf.CeilToInt(GetScrollRectSize () / GetCellSize()) + 1;
        .......
    }

 

在滾動的時候,首先需要知道最左上角的數據index:

    public int GetStartIndex()
    {
        if (m_dataList == null || m_dataList.Count == 0)
            return 0;
        float anchorPosition = IsVertical ? rectTransform.anchoredPosition.y : rectTransform.anchoredPosition.x;
        if(!IsVertical)
            anchorPosition *= -1;
        anchorPosition -=  GetCellSize() * 0.5f - GetPadding();
        return (int)(anchorPosition / GetCellSize()) * constraintCount;
    }

 

為了不產生其他開銷,Item的實際順序不會改動,這就需要在已有的順序中對其在數據中的實際順序做映射,舉個例子,如果一屏可以顯示四個數據,那么對應不同的startIndex,四個item的映射關系如下:

Start Index Actual Index
0 0 1 2 3
1 4 1 2 3
2 4 5 2 3

 

 

 

 

如下公式可得到Actual Index(對於無限滾動的列表,start index可能小於0):

    private  int GetActualIndex(int startIndex, int index)
    {
        var count = GetChildCount();
        return ((startIndex + (startIndex >= 0 ? (count - index - 1) : -index)) / count * count + index);
    }

 

滾動時,可根據Actual Index設置Item的位置,並對比滾動前的Actual Index是否改變,決定是否更新數據:

    private void SetCellsAlongAxis (int axis)
    {
        ......
        for(int i = 0; i < childCount; ++i) 
        {
            var child = childList[i];
                        ...
            if (IsVertical) {
                positionX = Mathf.Abs(actualIndex) % cellsPerMainAxis;
                positionY = actualIndex / cellsPerMainAxis;
            } else {
                positionX = actualIndex / cellsPerMainAxis;
                positionY = Mathf.Abs(actualIndex) % cellsPerMainAxis;
            }

            if (cornerX == 1)
                positionX = actualCellCountX - 1 - positionX;
            if (cornerY == 1)
                positionY = actualCellCountY - 1 - positionY;
            
......
if(actualIndex != previousIndex) { SetItemContent(acutalIndex, child, m_dataList[i]); } SetChildAlongAxis (child, 0, startOffset.x + (cellSize [0] + spacing [0]) * positionX, cellSize [0]); SetChildAlongAxis (child, 1, startOffset.y + (cellSize [1] + spacing [1]) * positionY, cellSize [1]); }
    }

 

二 不同cell size的可重用列表

在一的基礎上稍微做一下修改

為了滾動條大小正確,需要提前知道計算出data對應的cell size

protected List<Vector2> m_sizeList = new List<Vector2>();
protected List<float> m_positionList = new List<float>();
protected float m_minSize = float.MaxValue;//同屏item的最大數量由這個決定

 

        public void ReCalcItemSize(bool rebuildSizeList = false)
        {
            if (m_getSizeHandler == null)//外部傳入,計算data對應的cell size
                return;
            if(rebuildSizeList)
                m_sizeList.Clear ();
            m_positionList.Clear ();
            m_minSize = float.MaxValue;
            m_otherMaxSize = 0f;
            float totalSize = 0f;
            m_positionList.Add (0);
            for (int i = 0; i < m_dataList.Count; ++i)
            {
                var size = Vector2.zero;

                if (rebuildSizeList)
                {
                    size = m_getSizeHandler (i, m_dataList [i]);
                } else
                {
                    size = m_sizeList [i];
                }
                var cur = (isVertical ? size.y : size.x);
                m_minSize = Mathf.Min (cur, m_minSize);
                m_otherMaxSize = Mathf.Max ((isVertical ? size.x : size.y), m_otherMaxSize);
                if(rebuildSizeList)
                    m_sizeList.Add (size);
                totalSize += cur + spacing;
                m_positionList.Add (totalSize);
            }

            m_displayListCount = Mathf.CeilToInt(GetScrollRectSize() / (m_minSize + spacing));
            ...
        }

接着需要重寫方法,讓ScrollRect得到正確的滾動范圍:

    public override void CalculateLayoutInputHorizontal()
    {
        CalcAlongAxis(0, isVertical);
    }

    public override void CalculateLayoutInputVertical()
    {
        CalcAlongAxis(1, isVertical);
    }
        protected void CalcAlongAxis(int axis, bool calcVertical)
        {
            if (m_ScrollRect == null)
                return;
            float combinedPadding = (axis == 0 ? padding.horizontal : padding.vertical);

            float totalMin = combinedPadding;

            bool alongOtherAxis = (calcVertical ^ (axis == 1));
            if (alongOtherAxis)
                totalMin += m_otherMaxSize;
            
            if (!alongOtherAxis && m_positionList.Count > 0)
            {
                totalMin += m_positionList[m_positionList.Count - 1] - spacing;
            }
            SetLayoutInputForAxis(totalMin, totalMin, totalMin, axis);
        }    

最后計算StartIndex也要做一下修改:

        protected int GetStartIndex()
        {
            if (m_dataList == null || m_dataList.Count == 0)
                return 0;
            var curPosition = (isVertical ? rectTransform.anchoredPosition.y : rectTransform.anchoredPosition.x);
            if (isVertical)
                curPosition -= padding.top;
            else
                curPosition = -curPosition - padding.left;

            int index = 0;
            for (int i = 0; i < m_positionList.Count; ++i)
            {
                if (m_positionList [i] > curPosition)
                    break;
                index = i;
            }
            return index;
        }

 

比較繁瑣,主要是需要提前計算好所有data對應的cell size,這個是不能省的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM