Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 715|回复: 0

[默认分类] 基于Unity·UGUI实现的RecycleList循环列表UI容器

[复制链接]
  • TA的每日心情
    开心
    2021-12-13 21:45
  • 签到天数: 15 天

    [LV.4]偶尔看看III

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


    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-4-27 05:14 , Processed in 0.466807 second(s), 37 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表