RecycledListView.cs 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. using System.Collections.Generic;
  2. using UnityEngine;
  3. using UnityEngine.UI;
  4. namespace SimpleFileBrowser
  5. {
  6. [RequireComponent( typeof( ScrollRect ) )]
  7. public class RecycledListView : MonoBehaviour
  8. {
  9. // Cached components
  10. public RectTransform viewportTransform;
  11. public RectTransform contentTransform;
  12. private float itemHeight, _1OverItemHeight;
  13. private float viewportHeight;
  14. private readonly Dictionary<int, ListItem> items = new Dictionary<int, ListItem>();
  15. private readonly Stack<ListItem> pooledItems = new Stack<ListItem>();
  16. IListViewAdapter adapter = null;
  17. // Current indices of items shown on screen
  18. private int currentTopIndex = -1, currentBottomIndex = -1;
  19. void Start()
  20. {
  21. viewportHeight = viewportTransform.rect.height;
  22. GetComponent<ScrollRect>().onValueChanged.AddListener( ( pos ) => UpdateItemsInTheList() );
  23. }
  24. public void SetAdapter( IListViewAdapter adapter )
  25. {
  26. this.adapter = adapter;
  27. itemHeight = adapter.ItemHeight;
  28. _1OverItemHeight = 1f / itemHeight;
  29. }
  30. // Update the list
  31. public void UpdateList()
  32. {
  33. float newHeight = Mathf.Max( 1f, adapter.Count * itemHeight );
  34. contentTransform.sizeDelta = new Vector2( 0f, newHeight );
  35. viewportHeight = viewportTransform.rect.height;
  36. UpdateItemsInTheList( true );
  37. }
  38. // Window is resized, update the list
  39. public void OnViewportDimensionsChanged()
  40. {
  41. viewportHeight = viewportTransform.rect.height;
  42. UpdateItemsInTheList();
  43. }
  44. // Calculate the indices of items to show
  45. private void UpdateItemsInTheList( bool updateAllVisibleItems = false )
  46. {
  47. // If there is at least one item to show
  48. if( adapter.Count > 0 )
  49. {
  50. float contentPos = contentTransform.anchoredPosition.y - 1f;
  51. int newTopIndex = (int) ( contentPos * _1OverItemHeight );
  52. int newBottomIndex = (int) ( ( contentPos + viewportHeight + 2f ) * _1OverItemHeight );
  53. if( newTopIndex < 0 )
  54. newTopIndex = 0;
  55. if( newBottomIndex > adapter.Count - 1 )
  56. newBottomIndex = adapter.Count - 1;
  57. if( currentTopIndex == -1 )
  58. {
  59. // There are no active items
  60. updateAllVisibleItems = true;
  61. currentTopIndex = newTopIndex;
  62. currentBottomIndex = newBottomIndex;
  63. CreateItemsBetweenIndices( newTopIndex, newBottomIndex );
  64. }
  65. else
  66. {
  67. // There are some active items
  68. if( newBottomIndex < currentTopIndex || newTopIndex > currentBottomIndex )
  69. {
  70. // If user scrolled a lot such that, none of the items are now within
  71. // the bounds of the scroll view, pool all the previous items and create
  72. // new items for the new list of visible entries
  73. updateAllVisibleItems = true;
  74. DestroyItemsBetweenIndices( currentTopIndex, currentBottomIndex );
  75. CreateItemsBetweenIndices( newTopIndex, newBottomIndex );
  76. }
  77. else
  78. {
  79. // User did not scroll a lot such that, some items are are still within
  80. // the bounds of the scroll view. Don't destroy them but update their content,
  81. // if necessary
  82. if( newTopIndex > currentTopIndex )
  83. {
  84. DestroyItemsBetweenIndices( currentTopIndex, newTopIndex - 1 );
  85. }
  86. if( newBottomIndex < currentBottomIndex )
  87. {
  88. DestroyItemsBetweenIndices( newBottomIndex + 1, currentBottomIndex );
  89. }
  90. if( newTopIndex < currentTopIndex )
  91. {
  92. CreateItemsBetweenIndices( newTopIndex, currentTopIndex - 1 );
  93. // If it is not necessary to update all the items,
  94. // then just update the newly created items. Otherwise,
  95. // wait for the major update
  96. if( !updateAllVisibleItems )
  97. {
  98. UpdateItemContentsBetweenIndices( newTopIndex, currentTopIndex - 1 );
  99. }
  100. }
  101. if( newBottomIndex > currentBottomIndex )
  102. {
  103. CreateItemsBetweenIndices( currentBottomIndex + 1, newBottomIndex );
  104. // If it is not necessary to update all the items,
  105. // then just update the newly created items. Otherwise,
  106. // wait for the major update
  107. if( !updateAllVisibleItems )
  108. {
  109. UpdateItemContentsBetweenIndices( currentBottomIndex + 1, newBottomIndex );
  110. }
  111. }
  112. }
  113. currentTopIndex = newTopIndex;
  114. currentBottomIndex = newBottomIndex;
  115. }
  116. if( updateAllVisibleItems )
  117. {
  118. // Update all the items
  119. UpdateItemContentsBetweenIndices( currentTopIndex, currentBottomIndex );
  120. }
  121. }
  122. else if( currentTopIndex != -1 )
  123. {
  124. // There is nothing to show but some items are still visible; pool them
  125. DestroyItemsBetweenIndices( currentTopIndex, currentBottomIndex );
  126. currentTopIndex = -1;
  127. }
  128. }
  129. private void CreateItemsBetweenIndices( int topIndex, int bottomIndex )
  130. {
  131. for( int i = topIndex; i <= bottomIndex; i++ )
  132. {
  133. CreateItemAtIndex( i );
  134. }
  135. }
  136. // Create (or unpool) an item
  137. private void CreateItemAtIndex( int index )
  138. {
  139. ListItem item;
  140. if( pooledItems.Count > 0 )
  141. {
  142. item = pooledItems.Pop();
  143. item.gameObject.SetActive( true );
  144. }
  145. else
  146. {
  147. item = adapter.CreateItem();
  148. item.transform.SetParent( contentTransform, false );
  149. item.SetAdapter( adapter );
  150. }
  151. // Reposition the item
  152. ( (RectTransform) item.transform ).anchoredPosition = new Vector2( 1f, -index * itemHeight );
  153. // To access this item easily in the future, add it to the dictionary
  154. items[index] = item;
  155. }
  156. private void DestroyItemsBetweenIndices( int topIndex, int bottomIndex )
  157. {
  158. for( int i = topIndex; i <= bottomIndex; i++ )
  159. {
  160. ListItem item = items[i];
  161. item.gameObject.SetActive( false );
  162. pooledItems.Push( item );
  163. }
  164. }
  165. private void UpdateItemContentsBetweenIndices( int topIndex, int bottomIndex )
  166. {
  167. for( int i = topIndex; i <= bottomIndex; i++ )
  168. {
  169. ListItem item = items[i];
  170. item.Position = i;
  171. adapter.SetItemContent( item );
  172. }
  173. }
  174. }
  175. }