/// Credit setchi (https://github.com/setchi) /// Sourced from - https://github.com/setchi/FancyScrollView using System.Collections.Generic; namespace UnityEngine.UI.Extensions { /// /// スクロールビューを実装するための抽象基底クラス. /// 無限スクロールおよびスナップに対応しています. /// が不要な場合は /// 代わりに を使用します. /// /// アイテムのデータ型. /// の型. public abstract class FancyScrollView : MonoBehaviour where TContext : class, new() { /// /// セル同士の間隔. /// [SerializeField, Range(1e-2f, 1f)] protected float cellInterval = 0.2f; /// /// スクロール位置の基準. /// /// /// たとえば、 0.5 を指定してスクロール位置が 0 の場合, 中央に最初のセルが配置されます. /// [SerializeField, Range(0f, 1f)] protected float scrollOffset = 0.5f; /// /// セルを循環して配置させるどうか. /// /// /// true にすると最後のセルの後に最初のセル, 最初のセルの前に最後のセルが並ぶようになります. /// 無限スクロールを実装する場合は true を指定します. /// [SerializeField] protected bool loop = false; /// /// セルの親要素となる Transform. /// [SerializeField] protected Transform cellContainer = default; readonly IList> pool = new List>(); /// /// 初期化済みかどうか. /// protected bool initialized; /// /// 現在のスクロール位置. /// protected float currentPosition; /// /// セルの Prefab. /// protected abstract GameObject CellPrefab { get; } /// /// アイテム一覧のデータ. /// protected IList ItemsSource { get; set; } = new List(); /// /// のインスタンス. /// セルとスクロールビュー間で同じインスタンスが共有されます. 情報の受け渡しや状態の保持に使用します. /// protected TContext Context { get; } = new TContext(); /// /// 初期化を行います. /// /// /// 最初にセルが生成される直前に呼び出されます. /// protected virtual void Initialize() { } /// /// 渡されたアイテム一覧に基づいて表示内容を更新します. /// /// アイテム一覧. protected virtual void UpdateContents(IList itemsSource) { ItemsSource = itemsSource; Refresh(); } /// /// セルのレイアウトを強制的に更新します. /// protected virtual void Relayout() => UpdatePosition(currentPosition, false); /// /// セルのレイアウトと表示内容を強制的に更新します. /// protected virtual void Refresh() => UpdatePosition(currentPosition, true); /// /// スクロール位置を更新します. /// /// スクロール位置. protected virtual void UpdatePosition(float position) => UpdatePosition(position, false); void UpdatePosition(float position, bool forceRefresh) { if (!initialized) { Initialize(); initialized = true; } currentPosition = position; var p = position - scrollOffset / cellInterval; var firstIndex = Mathf.CeilToInt(p); var firstPosition = (Mathf.Ceil(p) - p) * cellInterval; if (firstPosition + pool.Count * cellInterval < 1f) { ResizePool(firstPosition); } UpdateCells(firstPosition, firstIndex, forceRefresh); } void ResizePool(float firstPosition) { Debug.Assert(CellPrefab != null); Debug.Assert(cellContainer != null); var addCount = Mathf.CeilToInt((1f - firstPosition) / cellInterval) - pool.Count; for (var i = 0; i < addCount; i++) { var cell = Instantiate(CellPrefab, cellContainer).GetComponent>(); if (cell == null) { throw new MissingComponentException(string.Format( "FancyCell<{0}, {1}> component not found in {2}.", typeof(TItemData).FullName, typeof(TContext).FullName, CellPrefab.name)); } cell.SetContext(Context); cell.Initialize(); cell.SetVisible(false); pool.Add(cell); } } void UpdateCells(float firstPosition, int firstIndex, bool forceRefresh) { for (var i = 0; i < pool.Count; i++) { var index = firstIndex + i; var position = firstPosition + i * cellInterval; var cell = pool[CircularIndex(index, pool.Count)]; if (loop) { index = CircularIndex(index, ItemsSource.Count); } if (index < 0 || index >= ItemsSource.Count || position > 1f) { cell.SetVisible(false); continue; } if (forceRefresh || cell.Index != index || !cell.IsVisible) { cell.Index = index; cell.SetVisible(true); cell.UpdateContent(ItemsSource[index]); } cell.UpdatePosition(position); } } int CircularIndex(int i, int size) => size < 1 ? 0 : i < 0 ? size - 1 + (i + 1) % size : i % size; #if UNITY_EDITOR bool cachedLoop; float cachedCellInterval, cachedScrollOffset; void LateUpdate() { if (cachedLoop != loop || cachedCellInterval != cellInterval || cachedScrollOffset != scrollOffset) { cachedLoop = loop; cachedCellInterval = cellInterval; cachedScrollOffset = scrollOffset; UpdatePosition(currentPosition); } } #endif } /// /// のコンテキストクラス. /// public sealed class NullContext { } /// /// スクロールビューを実装するための抽象基底クラス. /// 無限スクロールおよびスナップに対応しています. /// /// /// public abstract class FancyScrollView : FancyScrollView { } }