需要注意的有下面幾點:
1.
區分好表現上的index和邏輯上的index。表現上的index是指這個go是go列表中的第幾項,但實際上這個index的意義並不大,因為在滾動的過程中go列表是輪轉的;邏輯上的index是指這個go對應數據中的第幾項,在滑動的過程中不斷地更新邏輯上的index,然后取對應的數據去刷新顯示即可。在一般的滑動列表中,有幾項數據就生成幾個go,因此表現上的index和邏輯上的index是一致的;而在循環利用的循環列表中,這兩個是不一致的。
那么,在實現上,就是需要知道每個go對應的邏輯index是多少了。而這個可以簡化為,只需要知道第一個對應的邏輯index是多少,因為后面的就是依次遞增的。
2.
做好緩存策略。對於循環利用的列表,需要生成的個數等於能顯示的最大個數加上2-3個的緩存個數,防止滑動過快時出現穿幫。
3.
關於循環利用的實現。其實就是在滑動過程中,收集被移除顯示的go,然后對這些go重新調整位置,並刷新go上的控件顯示。那么如何收集呢,就是將go對應的邏輯index和當前的邏輯index范圍進行比較,將不在這個范圍內的go收集即可。然后在需要的時候取出來,刷新這些go即可。
代碼如下:
1 using UnityEngine; 2 using System.Collections.Generic; 3 using System; 4 using UnityEngine.UI; 5 6 [RequireComponent(typeof(ScrollRect))] 7 public class LoopScrollView : MonoBehaviour { 8 9 private List<GameObject> goList;//當前顯示的go列表 10 private Queue<GameObject> freeGoQueue;//空閑的go隊列,存放未顯示的go 11 private Dictionary<GameObject, int> goIndexDic;//key:所有的go value:真實索引 12 private ScrollRect scrollRect; 13 private RectTransform contentRectTra; 14 private Vector2 scrollRectSize; 15 private Vector2 cellSize; 16 private int startIndex;//起始索引 17 private int maxCount;//創建的最大數量 18 private int createCount;//當前顯示的數量 19 20 private const int cacheCount = 2;//緩存數目 21 private const int invalidStartIndex = -1;//非法的起始索引 22 23 private int dataCount; 24 private GameObject prefabGo; 25 private Action<GameObject, int> updateCellCB; 26 private float cellPadding; 27 28 //初始化SV並刷新 29 public void Show(int dataCount, GameObject prefabGo, Action<GameObject, int> updateCellCB, float cellPadding = 0f) 30 { 31 //數據和組件初始化 32 this.dataCount = dataCount; 33 this.prefabGo = prefabGo; 34 this.updateCellCB = updateCellCB; 35 this.cellPadding = cellPadding; 36 37 goList = new List<GameObject>(); 38 freeGoQueue = new Queue<GameObject>(); 39 goIndexDic = new Dictionary<GameObject, int>(); 40 scrollRect = GetComponent<ScrollRect>(); 41 contentRectTra = scrollRect.content; 42 scrollRectSize = scrollRect.GetComponent<RectTransform>().sizeDelta; 43 cellSize = prefabGo.GetComponent<RectTransform>().sizeDelta; 44 startIndex = 0; 45 maxCount = GetMaxCount(); 46 createCount = 0; 47 48 if (scrollRect.horizontal) 49 { 50 contentRectTra.anchorMin = new Vector2(0, 0); 51 contentRectTra.anchorMax = new Vector2(0, 1); 52 } 53 else 54 { 55 contentRectTra.anchorMin = new Vector2(0, 1); 56 contentRectTra.anchorMax = new Vector2(1, 1); 57 } 58 scrollRect.onValueChanged.RemoveAllListeners(); 59 scrollRect.onValueChanged.AddListener(OnValueChanged); 60 ResetSize(dataCount); 61 } 62 63 //重置數量 64 public void ResetSize(int dataCount) 65 { 66 this.dataCount = dataCount; 67 contentRectTra.sizeDelta = GetContentSize(); 68 69 //回收顯示的go 70 for (int i = goList.Count - 1; i >= 0; i--) 71 { 72 GameObject go = goList[i]; 73 RecoverItem(go); 74 } 75 76 //創建或顯示需要的go 77 createCount = Mathf.Min(dataCount, maxCount); 78 for (int i = 0; i < createCount; i++) 79 { 80 CreateItem(i); 81 } 82 83 //刷新數據 84 startIndex = -1; 85 contentRectTra.anchoredPosition = Vector3.zero; 86 OnValueChanged(Vector2.zero); 87 } 88 89 //更新當前顯示的列表 90 public void UpdateList() 91 { 92 for (int i = 0; i < goList.Count; i++) 93 { 94 GameObject go = goList[i]; 95 int index = goIndexDic[go]; 96 updateCellCB(go, index); 97 } 98 } 99 100 //創建或顯示一個item 101 private void CreateItem(int index) 102 { 103 GameObject go; 104 if (freeGoQueue.Count > 0)//使用原來的 105 { 106 go = freeGoQueue.Dequeue(); 107 goIndexDic[go] = index; 108 go.SetActive(true); 109 } 110 else//創建新的 111 { 112 go = Instantiate<GameObject>(prefabGo); 113 goIndexDic.Add(go, index); 114 go.transform.SetParent(contentRectTra.transform); 115 116 RectTransform rect = go.GetComponent<RectTransform>(); 117 rect.pivot = new Vector2(0, 1); 118 rect.anchorMin = new Vector2(0, 1); 119 rect.anchorMax = new Vector2(0, 1); 120 } 121 goList.Add(go); 122 go.transform.localPosition = GetPosition(index); 123 updateCellCB(go, index); 124 } 125 126 //回收一個item 127 private void RecoverItem(GameObject go) 128 { 129 go.SetActive(false); 130 goList.Remove(go); 131 freeGoQueue.Enqueue(go); 132 goIndexDic[go] = invalidStartIndex; 133 } 134 135 //滑動回調 136 private void OnValueChanged(Vector2 vec) 137 { 138 int curStartIndex = GetStartIndex(); 139 //Debug.LogWarning(curStartIndex); 140 141 if ((startIndex != curStartIndex) && (curStartIndex > invalidStartIndex)) 142 { 143 startIndex = curStartIndex; 144 145 //收集被移出去的go 146 //索引的范圍:[startIndex, startIndex + createCount - 1] 147 for (int i = goList.Count - 1; i >= 0; i--) 148 { 149 GameObject go = goList[i]; 150 int index = goIndexDic[go]; 151 if (index < startIndex || index > (startIndex + createCount - 1)) 152 { 153 RecoverItem(go); 154 } 155 } 156 157 //對移除出的go進行重新排列 158 for (int i = startIndex; i < startIndex + createCount; i++) 159 { 160 if (i >= dataCount) 161 { 162 break; 163 } 164 165 bool isExist = false; 166 for (int j = 0; j < goList.Count; j++) 167 { 168 GameObject go = goList[j]; 169 int index = goIndexDic[go]; 170 if (index == i) 171 { 172 isExist = true; 173 break; 174 } 175 } 176 if (isExist) 177 { 178 continue; 179 } 180 181 CreateItem(i); 182 } 183 } 184 } 185 186 //獲取需要創建的最大prefab數目 187 private int GetMaxCount() 188 { 189 if (scrollRect.horizontal) 190 { 191 return Mathf.CeilToInt(scrollRectSize.x / (cellSize.x + cellPadding)) + cacheCount; 192 } 193 else 194 { 195 return Mathf.CeilToInt(scrollRectSize.y / (cellSize.y + cellPadding)) + cacheCount; 196 } 197 } 198 199 //獲取起始索引 200 private int GetStartIndex() 201 { 202 if (scrollRect.horizontal) 203 { 204 return Mathf.FloorToInt(-contentRectTra.anchoredPosition.x / (cellSize.x + cellPadding)); 205 } 206 else 207 { 208 return Mathf.FloorToInt(contentRectTra.anchoredPosition.y / (cellSize.y + cellPadding)); 209 } 210 } 211 212 //獲取索引所在位置 213 private Vector3 GetPosition(int index) 214 { 215 if (scrollRect.horizontal) 216 { 217 return new Vector3(index * (cellSize.x + cellPadding), 0, 0); 218 } 219 else 220 { 221 return new Vector3(0, index * -(cellSize.y + cellPadding), 0); 222 } 223 } 224 225 //獲取內容長寬 226 private Vector2 GetContentSize() 227 { 228 if (scrollRect.horizontal) 229 { 230 return new Vector2(cellSize.x * dataCount + cellPadding * (dataCount - 1), contentRectTra.sizeDelta.y); 231 } 232 else 233 { 234 return new Vector2(contentRectTra.sizeDelta.x, cellSize.y * dataCount + cellPadding * (dataCount - 1)); 235 } 236 } 237 }
1 using UnityEngine; 2 using System.Collections; 3 using UnityEngine.UI; 4 5 public class TestLoopScrollView : MonoBehaviour { 6 7 public LoopScrollView loopScrollView; 8 public LoopScrollView loopScrollView2; 9 public GameObject prefabGo; 10 public GameObject prefabGo2; 11 12 private void Start () 13 { 14 15 } 16 17 private void Update() 18 { 19 if (Input.GetKeyDown(KeyCode.Q)) 20 { 21 loopScrollView.Show(100, prefabGo, UpdateSV); 22 } 23 else if(Input.GetKeyDown(KeyCode.W)) 24 { 25 loopScrollView.ResetSize(5); 26 } 27 else if (Input.GetKeyDown(KeyCode.E)) 28 { 29 loopScrollView.ResetSize(50); 30 } 31 else if (Input.GetKeyDown(KeyCode.R)) 32 { 33 loopScrollView.UpdateList(); 34 } 35 36 if (Input.GetKeyDown(KeyCode.A)) 37 { 38 loopScrollView2.Show(100, prefabGo2, UpdateSV); 39 } 40 else if (Input.GetKeyDown(KeyCode.S)) 41 { 42 loopScrollView2.ResetSize(5); 43 } 44 else if (Input.GetKeyDown(KeyCode.D)) 45 { 46 loopScrollView2.ResetSize(50); 47 } 48 else if (Input.GetKeyDown(KeyCode.F)) 49 { 50 loopScrollView.UpdateList(); 51 } 52 } 53 54 private void UpdateSV(GameObject go, int index) 55 { 56 Text text = go.transform.Find("Text").GetComponent<Text>(); 57 text.text = index.ToString(); 58 } 59 }
效果: