TA的每日心情 | 开心 2021-12-13 21:45 |
---|
签到天数: 15 天 [LV.4]偶尔看看III
|
在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节点。
OK,简单说完方法,我就放出演示的gif动画和代码。这个代码仅仅是实现上上述的功能,但是代码组织上还有可以迭代改进的空间,比如,可否把滑动向上向下的处理合二为一,或者是赋值重新排布的代码统一成一个等等。
- 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 }
复制代码
|
|