VirtualVerticalLayoutGroup.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. using System;
  2. using UnityEngine;
  3. using UnityEngine.Events;
  4. using UnityEngine.EventSystems;
  5. using UnityEngine.UI;
  6. namespace SRF.UI.Layout
  7. {
  8. [AddComponentMenu("SRF/UI/Layout/VerticalLayoutGroup (Virtualizing)")]
  9. public class VirtualVerticalLayoutGroup : LayoutGroup, IEventSystemHandler, IPointerClickHandler
  10. {
  11. public VirtualVerticalLayoutGroup.SelectedItemChangedEvent SelectedItemChanged
  12. {
  13. get
  14. {
  15. return this._selectedItemChanged;
  16. }
  17. set
  18. {
  19. this._selectedItemChanged = value;
  20. }
  21. }
  22. public object SelectedItem
  23. {
  24. get
  25. {
  26. return this._selectedItem;
  27. }
  28. set
  29. {
  30. if (this._selectedItem == value || !this.EnableSelection)
  31. {
  32. return;
  33. }
  34. int num = (value != null) ? this._itemList.IndexOf(value) : -1;
  35. if (value != null && num < 0)
  36. {
  37. throw new InvalidOperationException("Cannot select item not present in layout");
  38. }
  39. if (this._selectedItem != null)
  40. {
  41. this.InvalidateItem(this._selectedIndex);
  42. }
  43. this._selectedItem = value;
  44. this._selectedIndex = num;
  45. if (this._selectedItem != null)
  46. {
  47. this.InvalidateItem(this._selectedIndex);
  48. }
  49. this.SetDirty();
  50. if (this._selectedItemChanged != null)
  51. {
  52. this._selectedItemChanged.Invoke(this._selectedItem);
  53. }
  54. }
  55. }
  56. public override float minHeight
  57. {
  58. get
  59. {
  60. return (float)this._itemList.Count * this.ItemHeight + (float)base.padding.top + (float)base.padding.bottom + this.Spacing * (float)this._itemList.Count;
  61. }
  62. }
  63. public void OnPointerClick(PointerEventData eventData)
  64. {
  65. if (!this.EnableSelection)
  66. {
  67. return;
  68. }
  69. GameObject gameObject = eventData.pointerPressRaycast.gameObject;
  70. if (gameObject == null)
  71. {
  72. return;
  73. }
  74. Vector3 position = gameObject.transform.position;
  75. int num = Mathf.FloorToInt(Mathf.Abs(base.rectTransform.InverseTransformPoint(position).y) / this.ItemHeight);
  76. if (num >= 0 && num < this._itemList.Count)
  77. {
  78. this.SelectedItem = this._itemList[num];
  79. }
  80. else
  81. {
  82. this.SelectedItem = null;
  83. }
  84. }
  85. protected override void Awake()
  86. {
  87. base.Awake();
  88. this.ScrollRect.onValueChanged.AddListener(new UnityAction<Vector2>(this.OnScrollRectValueChanged));
  89. Component component = this.ItemPrefab.GetComponent(typeof(IVirtualView));
  90. if (component == null)
  91. {
  92. UnityEngine.Debug.LogWarning("[VirtualVerticalLayoutGroup] ItemPrefab does not have a component inheriting from IVirtualView, so no data binding can occur");
  93. }
  94. }
  95. private void OnScrollRectValueChanged(Vector2 d)
  96. {
  97. if (d.y < 0f || d.y > 1f)
  98. {
  99. this._scrollRect.verticalNormalizedPosition = Mathf.Clamp01(d.y);
  100. }
  101. this.SetDirty();
  102. }
  103. protected override void Start()
  104. {
  105. base.Start();
  106. this.ScrollUpdate();
  107. }
  108. protected override void OnEnable()
  109. {
  110. base.OnEnable();
  111. this.SetDirty();
  112. }
  113. protected void Update()
  114. {
  115. if (!this.AlignBottom && !this.AlignTop)
  116. {
  117. UnityEngine.Debug.LogWarning("[VirtualVerticalLayoutGroup] Only Lower or Upper alignment is supported.", this);
  118. base.childAlignment = TextAnchor.UpperLeft;
  119. }
  120. if (this.SelectedItem != null && !this._itemList.Contains(this.SelectedItem))
  121. {
  122. this.SelectedItem = null;
  123. }
  124. if (this._isDirty)
  125. {
  126. this._isDirty = false;
  127. this.ScrollUpdate();
  128. }
  129. }
  130. protected void InvalidateItem(int itemIndex)
  131. {
  132. if (!this._visibleItemList.Contains(itemIndex))
  133. {
  134. return;
  135. }
  136. this._visibleItemList.Remove(itemIndex);
  137. for (int i = 0; i < this._visibleRows.Count; i++)
  138. {
  139. if (this._visibleRows[i].Index == itemIndex)
  140. {
  141. this.RecycleRow(this._visibleRows[i]);
  142. this._visibleRows.RemoveAt(i);
  143. break;
  144. }
  145. }
  146. }
  147. protected void RefreshIndexCache()
  148. {
  149. for (int i = 0; i < this._visibleRows.Count; i++)
  150. {
  151. this._visibleRows[i].Index = this._itemList.IndexOf(this._visibleRows[i].Data);
  152. }
  153. }
  154. protected void ScrollUpdate()
  155. {
  156. if (!Application.isPlaying)
  157. {
  158. return;
  159. }
  160. float y = base.rectTransform.anchoredPosition.y;
  161. float height = ((RectTransform)this.ScrollRect.transform).rect.height;
  162. int num = Mathf.FloorToInt(y / (this.ItemHeight + this.Spacing));
  163. int num2 = Mathf.CeilToInt((y + height) / (this.ItemHeight + this.Spacing));
  164. num -= this.RowPadding;
  165. num2 += this.RowPadding;
  166. num = Mathf.Max(0, num);
  167. num2 = Mathf.Min(this._itemList.Count, num2);
  168. bool flag = false;
  169. for (int i = 0; i < this._visibleRows.Count; i++)
  170. {
  171. VirtualVerticalLayoutGroup.Row row = this._visibleRows[i];
  172. if (row.Index < num || row.Index > num2)
  173. {
  174. this._visibleItemList.Remove(row.Index);
  175. this._visibleRows.Remove(row);
  176. this.RecycleRow(row);
  177. flag = true;
  178. }
  179. }
  180. for (int j = num; j < num2; j++)
  181. {
  182. if (j >= this._itemList.Count)
  183. {
  184. break;
  185. }
  186. if (!this._visibleItemList.Contains(j))
  187. {
  188. VirtualVerticalLayoutGroup.Row row2 = this.GetRow(j);
  189. this._visibleRows.Add(row2);
  190. this._visibleItemList.Add(j);
  191. flag = true;
  192. }
  193. }
  194. if (flag || this._visibleItemCount != this._visibleRows.Count)
  195. {
  196. LayoutRebuilder.MarkLayoutForRebuild(base.rectTransform);
  197. }
  198. this._visibleItemCount = this._visibleRows.Count;
  199. }
  200. public override void CalculateLayoutInputVertical()
  201. {
  202. base.SetLayoutInputForAxis(this.minHeight, this.minHeight, -1f, 1);
  203. }
  204. public override void SetLayoutHorizontal()
  205. {
  206. float num = base.rectTransform.rect.width - (float)base.padding.left - (float)base.padding.right;
  207. for (int i = 0; i < this._visibleRows.Count; i++)
  208. {
  209. VirtualVerticalLayoutGroup.Row row = this._visibleRows[i];
  210. base.SetChildAlongAxis(row.Rect, 0, (float)base.padding.left, num);
  211. }
  212. for (int j = 0; j < this._rowCache.Count; j++)
  213. {
  214. VirtualVerticalLayoutGroup.Row row2 = this._rowCache[j];
  215. base.SetChildAlongAxis(row2.Rect, 0, -num - (float)base.padding.left, num);
  216. }
  217. }
  218. public override void SetLayoutVertical()
  219. {
  220. if (!Application.isPlaying)
  221. {
  222. return;
  223. }
  224. for (int i = 0; i < this._visibleRows.Count; i++)
  225. {
  226. VirtualVerticalLayoutGroup.Row row = this._visibleRows[i];
  227. base.SetChildAlongAxis(row.Rect, 1, (float)row.Index * this.ItemHeight + (float)base.padding.top + this.Spacing * (float)row.Index, this.ItemHeight);
  228. }
  229. }
  230. private new void SetDirty()
  231. {
  232. base.SetDirty();
  233. if (!this.IsActive())
  234. {
  235. return;
  236. }
  237. this._isDirty = true;
  238. }
  239. public void AddItem(object item)
  240. {
  241. this._itemList.Add(item);
  242. this.SetDirty();
  243. if (this.StickToBottom && Mathf.Approximately(this.ScrollRect.verticalNormalizedPosition, 0f))
  244. {
  245. this.ScrollRect.normalizedPosition = new Vector2(0f, 0f);
  246. }
  247. }
  248. public void RemoveItem(object item)
  249. {
  250. if (this.SelectedItem == item)
  251. {
  252. this.SelectedItem = null;
  253. }
  254. int itemIndex = this._itemList.IndexOf(item);
  255. this.InvalidateItem(itemIndex);
  256. this._itemList.Remove(item);
  257. this.RefreshIndexCache();
  258. this.SetDirty();
  259. }
  260. public void ClearItems()
  261. {
  262. for (int i = this._visibleRows.Count - 1; i >= 0; i--)
  263. {
  264. this.InvalidateItem(this._visibleRows[i].Index);
  265. }
  266. this._itemList.Clear();
  267. this.SetDirty();
  268. }
  269. private ScrollRect ScrollRect
  270. {
  271. get
  272. {
  273. if (this._scrollRect == null)
  274. {
  275. this._scrollRect = base.GetComponentInParent<ScrollRect>();
  276. }
  277. return this._scrollRect;
  278. }
  279. }
  280. private bool AlignBottom
  281. {
  282. get
  283. {
  284. return base.childAlignment == TextAnchor.LowerRight || base.childAlignment == TextAnchor.LowerCenter || base.childAlignment == TextAnchor.LowerLeft;
  285. }
  286. }
  287. private bool AlignTop
  288. {
  289. get
  290. {
  291. return base.childAlignment == TextAnchor.UpperLeft || base.childAlignment == TextAnchor.UpperCenter || base.childAlignment == TextAnchor.UpperRight;
  292. }
  293. }
  294. private float ItemHeight
  295. {
  296. get
  297. {
  298. if (this._itemHeight <= 0f)
  299. {
  300. ILayoutElement layoutElement = this.ItemPrefab.GetComponent(typeof(ILayoutElement)) as ILayoutElement;
  301. if (layoutElement != null)
  302. {
  303. this._itemHeight = layoutElement.preferredHeight;
  304. }
  305. else
  306. {
  307. this._itemHeight = this.ItemPrefab.rect.height;
  308. }
  309. if (this._itemHeight.ApproxZero())
  310. {
  311. UnityEngine.Debug.LogWarning("[VirtualVerticalLayoutGroup] ItemPrefab must have a preferred size greater than 0");
  312. this._itemHeight = 10f;
  313. }
  314. }
  315. return this._itemHeight;
  316. }
  317. }
  318. private VirtualVerticalLayoutGroup.Row GetRow(int forIndex)
  319. {
  320. if (this._rowCache.Count == 0)
  321. {
  322. VirtualVerticalLayoutGroup.Row row = this.CreateRow();
  323. this.PopulateRow(forIndex, row);
  324. return row;
  325. }
  326. object obj = this._itemList[forIndex];
  327. VirtualVerticalLayoutGroup.Row row2 = null;
  328. VirtualVerticalLayoutGroup.Row row3 = null;
  329. int num = forIndex % 2;
  330. for (int i = 0; i < this._rowCache.Count; i++)
  331. {
  332. row2 = this._rowCache[i];
  333. if (row2.Data == obj)
  334. {
  335. this._rowCache.RemoveAt(i);
  336. this.PopulateRow(forIndex, row2);
  337. break;
  338. }
  339. if (row2.Index % 2 == num)
  340. {
  341. row3 = row2;
  342. }
  343. row2 = null;
  344. }
  345. if (row2 == null && row3 != null)
  346. {
  347. this._rowCache.Remove(row3);
  348. row2 = row3;
  349. this.PopulateRow(forIndex, row2);
  350. }
  351. else if (row2 == null)
  352. {
  353. row2 = this._rowCache.PopLast<VirtualVerticalLayoutGroup.Row>();
  354. this.PopulateRow(forIndex, row2);
  355. }
  356. return row2;
  357. }
  358. private void RecycleRow(VirtualVerticalLayoutGroup.Row row)
  359. {
  360. this._rowCache.Add(row);
  361. }
  362. private void PopulateRow(int index, VirtualVerticalLayoutGroup.Row row)
  363. {
  364. row.Index = index;
  365. row.Data = this._itemList[index];
  366. row.View.SetDataContext(this._itemList[index]);
  367. if (this.RowStyleSheet != null || this.AltRowStyleSheet != null || this.SelectedRowStyleSheet != null)
  368. {
  369. if (this.SelectedRowStyleSheet != null && this.SelectedItem == row.Data)
  370. {
  371. row.Root.StyleSheet = this.SelectedRowStyleSheet;
  372. }
  373. else
  374. {
  375. row.Root.StyleSheet = ((index % 2 != 0) ? this.AltRowStyleSheet : this.RowStyleSheet);
  376. }
  377. }
  378. }
  379. private VirtualVerticalLayoutGroup.Row CreateRow()
  380. {
  381. VirtualVerticalLayoutGroup.Row row = new VirtualVerticalLayoutGroup.Row();
  382. RectTransform rectTransform = SRInstantiate.Instantiate<RectTransform>(this.ItemPrefab);
  383. row.Rect = rectTransform;
  384. row.View = (rectTransform.GetComponent(typeof(IVirtualView)) as IVirtualView);
  385. if (this.RowStyleSheet != null || this.AltRowStyleSheet != null || this.SelectedRowStyleSheet != null)
  386. {
  387. row.Root = rectTransform.gameObject.GetComponentOrAdd<StyleRoot>();
  388. row.Root.StyleSheet = this.RowStyleSheet;
  389. }
  390. rectTransform.SetParent(base.rectTransform, false);
  391. return row;
  392. }
  393. private readonly SRList<object> _itemList = new SRList<object>();
  394. private readonly SRList<int> _visibleItemList = new SRList<int>();
  395. private bool _isDirty;
  396. private SRList<VirtualVerticalLayoutGroup.Row> _rowCache = new SRList<VirtualVerticalLayoutGroup.Row>();
  397. private ScrollRect _scrollRect;
  398. private int _selectedIndex;
  399. private object _selectedItem;
  400. [SerializeField]
  401. private VirtualVerticalLayoutGroup.SelectedItemChangedEvent _selectedItemChanged;
  402. private int _visibleItemCount;
  403. private SRList<VirtualVerticalLayoutGroup.Row> _visibleRows = new SRList<VirtualVerticalLayoutGroup.Row>();
  404. public StyleSheet AltRowStyleSheet;
  405. public bool EnableSelection = true;
  406. public RectTransform ItemPrefab;
  407. public int RowPadding = 2;
  408. public StyleSheet RowStyleSheet;
  409. public StyleSheet SelectedRowStyleSheet;
  410. public float Spacing;
  411. public bool StickToBottom = true;
  412. private float _itemHeight = -1f;
  413. [Serializable]
  414. public class SelectedItemChangedEvent : UnityEvent<object>
  415. {
  416. }
  417. [Serializable]
  418. private class Row
  419. {
  420. public object Data;
  421. public int Index;
  422. public RectTransform Rect;
  423. public StyleRoot Root;
  424. public IVirtualView View;
  425. }
  426. }
  427. }