zoukankan      html  css  js  c++  java
  • [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,这个是不能省的。

  • 相关阅读:
    Delphi XE2 之 FireMonkey 入门(36) 控件基础: TForm
    Delphi XE2 之 FireMonkey 入门(35) 控件基础: TFmxObject: 其它
    Delphi XE2 之 FireMonkey 入门(39) 控件基础: TScrollBox、TVertScrollBox、TFramedScrollBox、TFramedVertScrollBox
    人月神话之编程行业的乐趣与苦恼
    基于NHibernate的三层结构应用程序开发初步
    .NET设计模式(9):桥接模式(Bridge Pattern)
    Grove,.NET中的又一个ORM实现
    近期学习计划
    .NET设计模式(8):适配器模式(Adapter Pattern)
    [声明]关于春节回家期间不能更新Blog的说明
  • 原文地址:https://www.cnblogs.com/drashnane/p/6391435.html
Copyright © 2011-2022 走看看