一、相同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,這個是不能省的。