基于Unity·UGUI实现的RecycleList循环列表UI容器

  在UI功能开发实践中,列表UI容器是我们经常使用一种UI容器组件。这种组件就根据输入的数据集合生成对应数据项目。从显示的方向来说,一般就分为水平排布和垂直排布的列表容器两种。列表容器为了在有限的界面空间中显示全部的数据,都会搭配使用UGUI的ScrollRect和Mask组件,我们只需要上下滑动,就可以浏览所要呈现的信息。但是,在UGUI中有几条数据就生成对应条目数的数据视图项,未免有些太过于奢侈。因为,每个数据项目不仅仅是一个UGUI的显示组件,而是多个显示组件(比如几个Text和Image)构成,最终,瞬时需要生成的GameObject总数目将会是数据总是的倍数。比如,我们有100条联系人数据,需要显示人的名称、电话、拨号按钮,背景Image直接挂在作为排列计算的母节点上,那么一个数据项目就需要至少4个GameObject表达。100条的数据将会导致一般的列表容器UI不得不同时生成400个GameObject,而同一时刻生成这么的GameObject,可以想象,Unity的运行时,将迎来一个不小的性能尖峰时刻,甚至我们能感受到界面进入假死状态。针对这个问题,一种比较理想的方案,就是用少量的GameObject去表示庞大的数据,随着滑动Scroll,mask对应的可显示区域,始终都是使用这几个GameObject条目显示当前的数据项。我们只需要不断的更新GameObject对应的列表的位置,已经及时设置起数据内容,就可以达成我们的目的。


  原理是这样,那么现在谈及具体的实施细节。要实现这个功能,我们就需要确定用什么样的特征去衡量当前列表的状态。也就是,如何确保我们要怎样移动这些列表项的GameObject(后文将用Cell来指代这个条目的根节点GameObject)到达当前Scroll Position对应的位置,以及当前显示又是哪个条目的数据。
  我们先设定列表项目的UI层级结构:
    ScrollRect
      -> Mask
        --> Content
          --> Cell 1
          --> Cell 2
           .........
          --> Cell n

  ScrollRect设定是只有垂直方向的移动能力,手动添加几个项目到Content中,上下移动ScrollRect,我们就发现Content的Position就会对应有一个移动操作。那么,事情就简单了,Content位移量和Cell的大小我们可以知道。对于垂直方向的表单,如果想知道当前显示的Cell是第几个,只需要拿Content的y值去除以于Cell的高度。那么我们只需要记下当前的index,一旦发现当前index发生变化,那么就可以使用这个变化,去移动Cell到需要的位置,并且也能做到最优先移动和更新看不见的Cell,原有可用的Cell维持不变。


  那具体怎么看搬迁的方法,我们先看scroll向上的滑动。显然,这种状态下就是当前第一个cell的index小于Content能够被mask显示项目的currentIndex,那么就是找出哪些都是小于currentIndex的cell,并出队搬迁到cell列表的尾部,然后计算从哪个cell开始需要重新如何新的输入,并计算正确的偏移坐标。同理,scroll向下滑动也一样,只不过要补偿计算mask当前能显示多少个cell,从mask的底部开始计算找出可复用的cell节点。

基于Unity·UGUI实现的RecycleList循环列表UI容器

基于Unity·UGUI实现的RecycleList循环列表UI容器

 

  OK,简单说完方法,我就放出演示的gif动画和代码。这个代码仅仅是实现上上述的功能,但是代码组织上还有可以迭代改进的空间,比如,可否把滑动向上向下的处理合二为一,或者是赋值重新排布的代码统一成一个等等。

                        

基于Unity·UGUI实现的RecycleList循环列表UI容器

 

 

1 using System; 2 using System.Collections.Generic; 3 using Neurons.SaturnUI.Data; 4 using Neurons.Util; 5 using UnityEngine; 6 using UnityEngine.UI; 7 8 namespace Neurons.SaturnUI.UGUI 9 { 10 [RequireComponent(typeof(ScrollRect))] 11 public class SaturnVerticalRecycleListLayoutGroup : MonoBehaviour, ICollectionUIHandler 12 { 13 [SerializeField] 14 private UICell uiCellPrefab; 15 [SerializeField] 16 private RectTransform contentRect; 17 [SerializeField] 18 private float spacing = 5f; 19 20 private Vector2 layoutSize; 21 private Vector2 cellSize; 22 private int displayContentCount; 23 24 private List<UIData> datas = new List<UIData>(); 25 26 private List<UICell> cellCaches = new List<UICell>(); 27 private List<UICell> tmpRemoveds = new List<UICell>(); 28 29 private int indexOfList; 30 31 public void SetUIData(List<UIData> newDatas) 32 { 33 this.datas.Clear(); 34 this.datas.AddRange(newDatas); 35 36 UpdateUI(); 37 } 38 39 public void AddUIData(UIData newData) 40 { 41 this.datas.Add(newData); 42 43 UpdateUI(); 44 } 45 46 public void RemoveUIData(UIData data) 47 { 48 this.RemoveUIData(data); 49 50 UpdateUI(); 51 } 52 53 public void CleanUIDatas() 54 { 55 this.datas.Clear(); 56 57 UpdateUI(); 58 } 59 60 public bool HasUIDatas() 61 { 62 return datas != null && datas.Count > 0; 63 } 64 65 public void UpdateUI() 66 { 67 this.contentRect.sizeDelta = new Vector2(this.contentRect.sizeDelta.x, (spacing + cellSize.y) * datas.Count); 68 69 var currentIndex = (int)(contentRect.localPosition.y / (cellSize.y + spacing)); 70 currentIndex = Math.Min(currentIndex, datas.Count - 1); 71 72 var j = currentIndex; 73 for (var i = 0; j < datas.Count && i < cellCaches.Count; i++, j++) 74 { 75 var newCellGo = cellCaches[i]; 76 77 newCellGo.gameObject.SetActiveEffectively(true); 78 newCellGo.transform.localPosition = new Vector3(0, -j * (spacing + cellSize.y), 0); 79 80 newCellGo.SetUIData(datas[j]); 81 } 82 83 indexOfList = currentIndex; 84 } 85 86 private void Awake() 87 { 88 cellSize = uiCellPrefab.GetComponent<RectTransform>().sizeDelta; 89 layoutSize = this.GetComponent<RectTransform>().sizeDelta; 90 displayContentCount = (int)(layoutSize.y / cellSize.y); 91 92 CreateCellCaches(); 93 } 94 95 private void Update() 96 { 97 var currentIndex = (int)(contentRect.localPosition.y / (cellSize.y + spacing)); 98 99 if (indexOfList != currentIndex && currentIndex >= 0) 100 { 101 UpdateCellUI(currentIndex); 102 indexOfList = currentIndex; 103 } 104 } 105 106 private void UpdateCellUI(int currentIndex) 107 { 108 if (cellCaches.Count > 0) 109 { 110 var cellIndex = (int)Math.Abs(cellCaches[0].transform.localPosition.y / (cellSize.y + spacing)); 111 112 if (cellIndex < currentIndex) 113 { 114 MoveForDown(currentIndex); 115 } 116 else if (cellIndex > currentIndex) 117 { 118 MoveForUp(currentIndex); 119 } 120 } 121 } 122 123 private void MoveForUp(int currentIndex) 124 { 125 tmpRemoveds.Clear(); 126 127 for (var i = cellCaches.Count - 1; i >= 0; i--) 128 { 129 var cell = cellCaches[i]; 130 var cellIndex = (int)Math.Abs(cell.transform.localPosition.y / (cellSize.y + spacing)); 131 132 if (cellIndex > currentIndex + displayContentCount) 133 { 134 tmpRemoveds.Add(cell); 135 } 136 } 137 138 for (var i = 0; i < tmpRemoveds.Count; i++) 139 { 140 cellCaches.Remove(tmpRemoveds[i]); 141 cellCaches.Insert(0, tmpRemoveds[i]); 142 } 143 144 var j = 0; 145 for (var i = currentIndex; i < datas.Count && j < cellCaches.Count; i++, j++) 146 { 147 var cell = cellCaches[j]; 148 cell.gameObject.SetActiveEffectively(true); 149 cell.SetUIData(datas[i]); 150 cell.transform.localPosition = new Vector3(0, -i * (spacing + cellSize.y), 0); 151 } 152 153 for (; j < cellCaches.Count; j++) 154 { 155 cellCaches[j].gameObject.SetActiveEffectively(false); 156 } 157 } 158 159 private void MoveForDown(int currentIndex) 160 { 161 tmpRemoveds.Clear(); 162 163 for (var i = 0; i < cellCaches.Count; i++) 164 { 165 var cell = cellCaches[i]; 166 var cellIndex = (int)Math.Abs(cell.transform.localPosition.y / (cellSize.y + spacing)); 167 168 if (cellIndex < currentIndex) 169 { 170 tmpRemoveds.Add(cell); 171 } 172 } 173 174 for (var i = 0; i < tmpRemoveds.Count; i++) 175 { 176 cellCaches.Remove(tmpRemoveds[i]); 177 cellCaches.Add(tmpRemoveds[i]); 178 } 179 180 var j = cellCaches.Count - tmpRemoveds.Count; 181 for (var i = currentIndex + cellCaches.Count - tmpRemoveds.Count; i < datas.Count && j < cellCaches.Count; i++, j++) 182 { 183 var cell = cellCaches[j]; 184 cell.gameObject.SetActiveEffectively(true); 185 cell.SetUIData(datas[i]); 186 cell.transform.localPosition = new Vector3(0, -i * (spacing + cellSize.y), 0); 187 } 188 189 for (; j < cellCaches.Count; j++) 190 { 191 cellCaches[j].gameObject.SetActiveEffectively(false); 192 } 193 } 194 195 private void CreateCellCaches() 196 { 197 var cacheCount = (int)(layoutSize.y / cellSize.y + 2); 198 199 for (var i = 0; i < cacheCount; i++) 200 { 201 var newCellGo = uiCellPrefab.gameObject.Clone(false); 202 203 newCellGo.DockTo(contentRect, true); 204 newCellGo.transform.localPosition = new Vector3(0, -i * (spacing + cellSize.y), 0); 205 206 var cell = newCellGo.GetComponent<UICell>(); 207 cellCaches.Add(cell); 208 209 newCellGo.gameObject.SetActiveEffectively(false); 210 } 211 } 212 } 213 }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpxzpz.html