基于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容器_第1张图片

基于Unity UGUI实现的RecycleList循环列表UI容器_第2张图片

 

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

                        基于Unity UGUI实现的RecycleList循环列表UI容器_第3张图片

 

 

  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 datas = new List();
 25 
 26         private List cellCaches = new List();
 27         private List tmpRemoveds = new List();
 28 
 29         private int indexOfList;
 30 
 31         public void SetUIData(List 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().sizeDelta;
 89             layoutSize = this.GetComponent().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();
207                 cellCaches.Add(cell);
208 
209                 newCellGo.gameObject.SetActiveEffectively(false);
210             }
211         }
212     }
213 }

 

作者:雨天

地址:http://www.cnblogs.com/HelloGalaxy/p/8697844.html

 

转载于:https://www.cnblogs.com/HelloGalaxy/p/8697844.html

你可能感兴趣的:(基于Unity UGUI实现的RecycleList循环列表UI容器)