需要注意的有下面几点:
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 }
效果: