FirstVillain_DLL/FirstVillainLibrary/InfiniteScrollView.cs
2023-10-21 18:07:01 +09:00

181 lines
5.5 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace FirstVillain.ScrollView
{
[RequireComponent(typeof(ScrollRect))]
public abstract class InfiniteScrollView : UIBehaviour
{
[SerializeField] private int _cellPoolSize = 20;
[SerializeField] protected float _spacing = 0f;
[SerializeField] protected Vector2 _padding;
[SerializeField] protected float _extendVisibleRange;
[SerializeField] private InfiniteCell _cellPrefab;
[SerializeField] protected ScrollRect _scrollRect;
protected List<InfiniteCellData> _dataList = new List<InfiniteCellData>();
protected List<InfiniteCell> _cellList = new List<InfiniteCell>();
protected Queue<InfiniteCell> _cellPool = new Queue<InfiniteCell>();
protected YieldInstruction _waitEndOfFrame = new WaitForEndOfFrame();
private Coroutine _snappingProcesser;
public event Action OnRectTransformUpdate;
public event Action<GameObject> OnCellSelected;
public Action OnRefresh;
public bool IsInitialized
{
get;
private set;
}
public virtual void Initialize()
{
if (IsInitialized)
return;
_scrollRect = GetComponent<ScrollRect>();
_scrollRect.onValueChanged.AddListener(OnValueChanged);
for (int i = 0; i < _cellPoolSize; i++)
{
var newCell = Instantiate(_cellPrefab, _scrollRect.content);
newCell.gameObject.SetActive(false);
_cellPool.Enqueue(newCell);
}
IsInitialized = true;
}
protected abstract void OnValueChanged(Vector2 normalizedPosition);
public abstract void Refresh();
public virtual void Add(InfiniteCellData data)
{
if (!IsInitialized)
{
Initialize();
}
data.Index = _dataList.Count;
_dataList.Add(data);
_cellList.Add(null);
}
public virtual void Remove(int index)
{
if (!IsInitialized)
{
Initialize();
}
if (_dataList.Count == 0)
return;
_dataList.RemoveAt(index);
Refresh();
}
public abstract void Snap(int index, float duration);
public void SnapLast(float duration)
{
Snap(_dataList.Count - 1, duration);
}
protected void DoSnapping(Vector2 target, float duration)
{
if (!gameObject.activeInHierarchy)
return;
StopSnapping();
_snappingProcesser = StartCoroutine(ProcessSnapping(target, duration));
}
public void StopSnapping()
{
if (_snappingProcesser != null)
{
StopCoroutine(_snappingProcesser);
_snappingProcesser = null;
}
}
private IEnumerator ProcessSnapping(Vector2 target, float duration)
{
_scrollRect.velocity = Vector2.zero;
Vector2 startPos = _scrollRect.content.anchoredPosition;
float t = 0;
while (t < 1f)
{
if (duration <= 0)
t = 1;
else
t += Time.deltaTime / duration;
_scrollRect.content.anchoredPosition = Vector2.Lerp(startPos, target, t);
var normalizedPos = _scrollRect.normalizedPosition;
if (normalizedPos.y < 0 || normalizedPos.x > 1)
{
break;
}
yield return null;
}
if (duration <= 0)
OnValueChanged(_scrollRect.normalizedPosition);
_snappingProcesser = null;
}
protected void SetupCell(int index, Vector2 pos)
{
if (_cellList[index] == null)
{
var cell = _cellPool.Dequeue();
cell.gameObject.SetActive(true);
cell.CellData = _dataList[index];
cell.RectTransform.anchoredPosition = pos;
_cellList[index] = cell;
cell.OnSelected += OnCellObjSelected;
}
}
protected void RecycleCell(int index)
{
if (_cellList[index] != null)
{
var cell = _cellList[index];
_cellList[index] = null;
_cellPool.Enqueue(cell);
cell.gameObject.SetActive(false);
cell.OnSelected -= OnCellObjSelected;
}
}
private void OnCellObjSelected(GameObject selectedCell)
{
OnCellSelected?.Invoke(selectedCell);
}
public virtual void Clear()
{
if (IsInitialized == false)
Initialize();
_scrollRect.velocity = Vector2.zero;
_scrollRect.content.anchoredPosition = Vector2.zero;
_dataList.Clear();
for (int i = 0; i < _cellList.Count; i++)
{
RecycleCell(i);
}
_cellList.Clear();
Refresh();
}
protected override void OnRectTransformDimensionsChange()
{
base.OnRectTransformDimensionsChange();
if (_scrollRect)
{
OnRectTransformUpdate?.Invoke();
}
}
}
}