using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; namespace SimpleFileBrowser { [RequireComponent( typeof( ScrollRect ) )] public class RecycledListView : MonoBehaviour { // Cached components public RectTransform viewportTransform; public RectTransform contentTransform; private float itemHeight, _1OverItemHeight; private float viewportHeight; private readonly Dictionary items = new Dictionary(); private readonly Stack pooledItems = new Stack(); IListViewAdapter adapter = null; // Current indices of items shown on screen private int currentTopIndex = -1, currentBottomIndex = -1; void Start() { viewportHeight = viewportTransform.rect.height; GetComponent().onValueChanged.AddListener( ( pos ) => UpdateItemsInTheList() ); } public void SetAdapter( IListViewAdapter adapter ) { this.adapter = adapter; itemHeight = adapter.ItemHeight; _1OverItemHeight = 1f / itemHeight; } // Update the list public void UpdateList() { float newHeight = Mathf.Max( 1f, adapter.Count * itemHeight ); contentTransform.sizeDelta = new Vector2( 0f, newHeight ); viewportHeight = viewportTransform.rect.height; UpdateItemsInTheList( true ); } // Window is resized, update the list public void OnViewportDimensionsChanged() { viewportHeight = viewportTransform.rect.height; UpdateItemsInTheList(); } // Calculate the indices of items to show private void UpdateItemsInTheList( bool updateAllVisibleItems = false ) { // If there is at least one item to show if( adapter.Count > 0 ) { float contentPos = contentTransform.anchoredPosition.y - 1f; int newTopIndex = (int) ( contentPos * _1OverItemHeight ); int newBottomIndex = (int) ( ( contentPos + viewportHeight + 2f ) * _1OverItemHeight ); if( newTopIndex < 0 ) newTopIndex = 0; if( newBottomIndex > adapter.Count - 1 ) newBottomIndex = adapter.Count - 1; if( currentTopIndex == -1 ) { // There are no active items updateAllVisibleItems = true; currentTopIndex = newTopIndex; currentBottomIndex = newBottomIndex; CreateItemsBetweenIndices( newTopIndex, newBottomIndex ); } else { // There are some active items if( newBottomIndex < currentTopIndex || newTopIndex > currentBottomIndex ) { // If user scrolled a lot such that, none of the items are now within // the bounds of the scroll view, pool all the previous items and create // new items for the new list of visible entries updateAllVisibleItems = true; DestroyItemsBetweenIndices( currentTopIndex, currentBottomIndex ); CreateItemsBetweenIndices( newTopIndex, newBottomIndex ); } else { // User did not scroll a lot such that, some items are are still within // the bounds of the scroll view. Don't destroy them but update their content, // if necessary if( newTopIndex > currentTopIndex ) { DestroyItemsBetweenIndices( currentTopIndex, newTopIndex - 1 ); } if( newBottomIndex < currentBottomIndex ) { DestroyItemsBetweenIndices( newBottomIndex + 1, currentBottomIndex ); } if( newTopIndex < currentTopIndex ) { CreateItemsBetweenIndices( newTopIndex, currentTopIndex - 1 ); // If it is not necessary to update all the items, // then just update the newly created items. Otherwise, // wait for the major update if( !updateAllVisibleItems ) { UpdateItemContentsBetweenIndices( newTopIndex, currentTopIndex - 1 ); } } if( newBottomIndex > currentBottomIndex ) { CreateItemsBetweenIndices( currentBottomIndex + 1, newBottomIndex ); // If it is not necessary to update all the items, // then just update the newly created items. Otherwise, // wait for the major update if( !updateAllVisibleItems ) { UpdateItemContentsBetweenIndices( currentBottomIndex + 1, newBottomIndex ); } } } currentTopIndex = newTopIndex; currentBottomIndex = newBottomIndex; } if( updateAllVisibleItems ) { // Update all the items UpdateItemContentsBetweenIndices( currentTopIndex, currentBottomIndex ); } } else if( currentTopIndex != -1 ) { // There is nothing to show but some items are still visible; pool them DestroyItemsBetweenIndices( currentTopIndex, currentBottomIndex ); currentTopIndex = -1; } } private void CreateItemsBetweenIndices( int topIndex, int bottomIndex ) { for( int i = topIndex; i <= bottomIndex; i++ ) { CreateItemAtIndex( i ); } } // Create (or unpool) an item private void CreateItemAtIndex( int index ) { ListItem item; if( pooledItems.Count > 0 ) { item = pooledItems.Pop(); item.gameObject.SetActive( true ); } else { item = adapter.CreateItem(); item.transform.SetParent( contentTransform, false ); item.SetAdapter( adapter ); } // Reposition the item ( (RectTransform) item.transform ).anchoredPosition = new Vector2( 1f, -index * itemHeight ); // To access this item easily in the future, add it to the dictionary items[index] = item; } private void DestroyItemsBetweenIndices( int topIndex, int bottomIndex ) { for( int i = topIndex; i <= bottomIndex; i++ ) { ListItem item = items[i]; item.gameObject.SetActive( false ); pooledItems.Push( item ); } } private void UpdateItemContentsBetweenIndices( int topIndex, int bottomIndex ) { for( int i = topIndex; i <= bottomIndex; i++ ) { ListItem item = items[i]; item.Position = i; adapter.SetItemContent( item ); } } } }