[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,这个是不能省的。

posted @ 2017-02-12 17:54  DrAsh9N  阅读(1856)  评论(0编辑  收藏  举报