using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; namespace Cryville.Common.Unity.UI { public delegate void LoadItemHandler(int index, GameObject gameObject); [RequireComponent(typeof(RectTransform))] public class RecyclerView : UIBehaviour { [SerializeField] private GameObject m_itemTemplate; public GameObject ItemTemplate { get { return m_itemTemplate; } set { m_itemTemplate = value; /*OnTemplateUpdate();*/ } } public LoadItemHandler LoadItem { private get; set; } public enum Axis { Horizontal = 0, Vertical = 1, } [SerializeField] private Axis m_direction; public Axis Direction { get { return m_direction; } set { m_direction = value; /*OnFrameUpdate();*/ } } [SerializeField] RectOffset m_padding; public RectOffset Padding { get => m_padding; set => m_padding = value; } [SerializeField] float m_spacing; public float Spacing { get => m_spacing; set => m_spacing = value; } [SerializeField] private int m_itemCount = 3; public int ItemCount { get { return m_itemCount; } set { m_itemCount = value; /*OnRefresh();*/ } } RectTransform _rectTransform; protected override void Awake() { _rectTransform = GetComponent(); } const float _placeholderLength = 100; int _firstIndex, _lastIndex; readonly Stack _pool = new(); void LateUpdate() { int axis = (int)m_direction; int sign = m_direction == 0 ? 1 : -1; float padding = axis == 0 ? m_padding.left : m_padding.top; if (_rectTransform.parent is not RectTransform parentTransform) throw new InvalidOperationException("Parent transform is not RectTransform"); Vector2 apos = _rectTransform.anchoredPosition; float pos = apos[axis] + padding; float childPos = _firstIndex * _placeholderLength + padding; float visibleLength = parentTransform.rect.size[axis]; //// Add preceding //while (_firstIndex > 0 && childPos > pos) { // var child = Rent(); // child.transform.SetAsFirstSibling(); // LoadItem(--_firstIndex, child); // pos += GetLength(axis, child.transform) - _placeholderLength; // childPos -= _placeholderLength; //} //// Remove preceding //while (_firstIndex < _lastIndex) { // var child = transform.GetChild(0); // float len = GetLength(axis, child.transform); // if (childPos + len > pos) break; // Return(child.gameObject); // _firstIndex++; // pos += _placeholderLength - len; // childPos += _placeholderLength; //} //apos[axis] = pos; //_rectTransform.anchoredPosition = apos; // Layout existing int index = _firstIndex; float layoutToPos = pos + visibleLength; for (; index < _lastIndex && childPos < layoutToPos; index++) { var child = (RectTransform)transform.GetChild(index - _firstIndex); LayoutChild(axis, sign, ref childPos, child); } // Remove following for (; _lastIndex > index; --_lastIndex) { var child = (RectTransform)transform.GetChild(index - _firstIndex); Return(child.gameObject); } // Add following (and layout) while (_lastIndex < m_itemCount && childPos < layoutToPos) { var child = Rent(); if (child.transform is not RectTransform childTransform) throw new InvalidOperationException("Child transform is not RectTransform"); childTransform.SetSiblingIndex(_lastIndex - _firstIndex); LoadItem(_lastIndex++, child); LayoutChild(axis, sign, ref childPos, childTransform); } Vector2 gsize = _rectTransform.sizeDelta; gsize[axis] = childPos + (m_itemCount - _lastIndex) * _placeholderLength + (axis == 0 ? m_padding.horizontal : m_padding.vertical); _rectTransform.sizeDelta = gsize; } static float GetLength(int axis, Transform child) { if (child is not RectTransform childTransform) throw new InvalidOperationException("Child transform is not RectTransform"); return LayoutUtility.GetPreferredSize(childTransform, axis); } void LayoutChild(int axis, int sign, ref float childPos, RectTransform childTransform) { Vector2 cpos = childTransform.anchoredPosition; cpos[axis] = childPos * sign; cpos[axis ^ 1] = axis == 1 ? m_padding.left : m_padding.top; childTransform.anchoredPosition = cpos; Vector2 size = childTransform.sizeDelta; size[axis ^ 1] = _rectTransform.rect.size[axis ^ 1] - (axis == 1 ? m_padding.horizontal : m_padding.vertical); childTransform.sizeDelta = size; childPos += GetLength(axis, childTransform) + m_spacing; } GameObject Rent() { if (_pool.TryPop(out var ret)) { ret.SetActive(true); return ret; } return Instantiate(m_itemTemplate, transform, false); } void Return(GameObject child) { child.transform.SetAsLastSibling(); child.SetActive(false); _pool.Push(child); } public void InvalidateAll() { for (; _lastIndex > _firstIndex; --_lastIndex) { var child = (RectTransform)transform.GetChild(0); Return(child.gameObject); } } } }