161 lines
4.9 KiB
C#
161 lines
4.9 KiB
C#
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<RectTransform>();
|
|
}
|
|
|
|
const float _placeholderLength = 100;
|
|
int _firstIndex, _lastIndex;
|
|
readonly Stack<GameObject> _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);
|
|
}
|
|
}
|
|
}
|
|
}
|