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