using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace TyphoonUI
{
///
/// 滚动列表
///
[RequireComponent(typeof(Scroller))]
public abstract class UIScrollView : MonoBehaviour
{
protected virtual void Reset()
{
if (_scroller == null)
{
_scroller = transform.GetComponent();
}
}
[Header("基础参数")] [SerializeField] private RectTransform _cellContainer = default; //元素挂载节点
[SerializeField] private Scroller _scroller = default; //滑动器
[Header("吸附相关参数")]
[Range(0, 1)]
[Tooltip("吸附到viewport的百分比位置,取值范围:[0-1]\n横向排列时:0表示最左,1表示最右\n垂直排列时:0表示最上,1表示最下")]
public float SnapAlignment = 0.5f; //吸附参数-alignment
[Tooltip("吸附到元素指定锚点百分比位置,取值范围:[0-1]\n横向排列时:0表示最左,1表示最右\n垂直排列时:0表示最上,1表示最下")]
[Range(0, 1)]
public float SnapAnchor = 0.5f; //吸附参数-anchor
[Tooltip("预制物资源")] [HideInInspector] public List CellPrefabs = new List(); //预制物预设
[Tooltip("元素大小(默认值)")]
[HideInInspector]
public Vector2 CellSize = new Vector2(100, 100); //元素大小
#if UNITY_EDITOR
[SerializeField] [HideInInspector] private GameObject m_PreviewPrefab; //预览元素
[SerializeField] [HideInInspector] private List m_PreviewCells = new List(); //预览元素
[SerializeField]
[HideInInspector]
[Range(0, 1)]
private float m_previewScrollPercent; //预览进度
private bool _isValidate = false;
protected float PreviewScrollPercent => m_previewScrollPercent;
#endif
private Vector3 _cellLocalPositionOffset; //本地坐标偏移
private float _contentLength; //内容框长度
protected Vector2 ScrollRange { get; set; } //滚动范围,从最小到最大
protected IList ItemsSource { get; set; } //数据
protected List PositionInfos { get; set; } //元素坐标信息
protected List CellInfos { get; set; } //元素信息
private bool IsInitialized { get; set; } //是否完成初始化
protected ScrollCellPoolGroup PoolGroup { get; set; } //池对象组
public int DataCount => ItemsSource?.Count ?? 0; //数据数量
protected string DefaultCellName { get; set; } //默认元素名(第一个池为默认池)
public RectTransform CellContainer => _cellContainer;
//滚动组件
public Scroller Scroller
{
get
{
if (_scroller == null)
{
throw new Exception($"{gameObject} 缺少Scroller 组件");
}
return _scroller;
}
}
//当前滚动坐标
public float ScrollPosition => Scroller.Position;
public bool ScrollerIsExists => _scroller != null; //是否存在滚动组件
protected RectTransform ViewPort => Scroller.ViewPort; //视口
///
/// 元素更新时触发
///
public event Action OnCellUpdate = null;
///
/// 吸附开始时
///
public event Action OnSnapStart = null;
///
/// 吸附结束时
///
public event Action OnSnapEnd = null;
///
/// 视图变化时触发
///
public event Action OnViewPositionChanged;
public void FixScroller()
{
if (_scroller == null)
{
_scroller = transform.GetComponent();
}
}
///
/// 初始化(方式一)
/// 使用 prefab 进行创建绑定策略
/// [注意] prefab 需要挂载ScrollCell(子类)组件
///
public virtual void Initialize()
{
var bindings = new List();
var prefabs = CellPrefabs;
foreach (var prefab in prefabs)
{
bindings.Add(CreatePoolBinding(prefab));
}
Initialize(bindings);
}
///
/// 初始化(方式二)
///
/// 构建代理
/// 元素名(可选)
/// 预加载数量(可选)
public virtual void Initialize(Func createCellFunc, string cellName = "default",
int preloadCount = -1)
{
//创建绑定信息
var bindings = new List()
{ new ScrollCellPoolBinding(cellName, createCellFunc, preloadCount) };
Initialize(bindings);
}
///
/// 初始化(方式三)
/// 绑定UI组件,T 作为元素名
///
public virtual void Initialize() where T : UIComponent
{
Initialize(CreatePoolBinding());
}
///
/// 初始化(方式四)
///
/// 池绑定策略
public virtual void Initialize(ScrollCellPoolBinding binding)
{
Initialize(new List()
{
binding
});
}
///
/// 初始化(方式五)
///
/// 池绑定策略(集合)
public virtual void Initialize(List bindings)
{
if (IsInitialized)
{
Debug.LogError($"{this} 不支持重复初始化");
return;
}
IsInitialized = true;
InitializePools(bindings);
//绑定池对象
Scroller.OnValueChanged((pos) => CallUpdateView(pos, false));
Scroller.SetCalculateSnapPositionFunc(CalculateSnapPosition);
Scroller.SetSnapFunc(InvokeOnSnapStart, InvokeOnSnapEnd);
Canvas.ForceUpdateCanvases();
}
//初始化元素池
private void InitializePools(List bindings)
{
PoolGroup = new ScrollCellPoolGroup(); //构建元素对象池
DefaultCellName = bindings != null && bindings.Count > 0 ? bindings[0].CellName : string.Empty; //第一个为默认池对象名
if (bindings != null)
{
foreach (var element in bindings)
{
var poolName = element.CellName;
var func = element.CreateFunc;
PoolGroup.AddPool(poolName, new ScrollCellPool(func, _cellContainer, element.PreloadCount));
}
}
}
///
/// 创建池绑定
///
/// 元素名(可选,默认取组件名)
/// 预加载数量(可选)
/// UI组件类型
///
public ScrollCellPoolBinding CreatePoolBinding(string cellName = null, int preloadCount = -1)
where T : UIComponent
{
return new ScrollCellPoolBinding(cellName ?? typeof(T).Name,
() => UIManger.CreateComponent(_cellContainer), preloadCount);
}
///
/// 创建绑定策略
///
/// 预制物
/// 元素名(可选,默认取 prefab 名)
/// 预加载数量(可选)
///
public ScrollCellPoolBinding CreatePoolBinding(GameObject prefab, string cellName = null, int preloadCount = -1)
{
var cell = prefab.GetComponent();
if (cell == null)
{
throw new Exception($"{prefab} 缺少 ScrollCell 组件!");
}
return new ScrollCellPoolBinding(cellName ?? prefab.name, () =>
{
var clone = Instantiate(prefab.gameObject);
var scrollCell = clone.GetComponent();
return scrollCell;
}, preloadCount);
}
///
/// 填充数据
///
/// data 实现 IScrollCellData 接口可自定义元素大小
public virtual void UpdateData(IList data)
{
if (!IsInitialized)
{
throw new Exception($"{this}未执行初始化 Initialize");
}
PoolGroup?.RecycleAll();
ItemsSource = data; //绑定数据
Rebuild(); //重建所有
CallUpdateView(Scroller.Position, true); //强制更新所有元素
}
private void CallUpdateView(float scrollPosition, bool force)
{
UpdateView(scrollPosition, force);
OnViewPositionChanged?.Invoke(scrollPosition);
}
//更新视图
protected abstract void UpdateView(float scrollPosition, bool force);
//重建所有
protected void Rebuild()
{
//计算元素本地坐标偏移值
_cellLocalPositionOffset = RebuildCellLocalPositionOffset();
//重建元素信息
CellInfos = RebuildCellSizes();
//重建元素坐标信息
PositionInfos = RebuildCellPositionInfo();
//重建内容框长度
_contentLength = RebuildContentLength();
//重建滚动视窗范围
ScrollRange = RebuildScrollRange(_contentLength);
//重建Scroller
Scroller.Rebuild(ScrollRange, DataCount);
}
//重建元素大小
protected virtual List RebuildCellSizes()
{
var result = new List();
if (DataCount <= 0)
{
return result;
}
for (int i = 0; i < ItemsSource.Count; i++)
{
if (ItemsSource[i] is IScrollCellData element)
{
result.Add(new ScrollCellInfo()
{
CellSize = element.CellSize,
CellName = element.CellName,
});
continue;
}
result.Add(new ScrollCellInfo()
{
CellSize = CellSize, //使用默认大小
CellName = DefaultCellName, //使用默认元素
});
}
return result;
}
//重建元素坐标信息
protected abstract List RebuildCellPositionInfo();
//重建内容框大小
protected abstract float RebuildContentLength();
//重建滚动范围
protected abstract Vector2 RebuildScrollRange(float contentLength);
//计算cell localPosition 偏移量,排布位置参考Viewport节点,而cell属于在content节点下 为了减少计算,预先计算出相对偏移
protected Vector3 RebuildCellLocalPositionOffset()
{
//获取viewport 左上边界点
var worldCorners = new Vector3[4];
ViewPort.GetWorldCorners(worldCorners);
Vector3 worldOffset = worldCorners[1] - _cellContainer.position;
return ViewPort.InverseTransformVector(worldOffset);
}
//计算元素相对坐标(localPosition 属于ViewPort下的相对位置)
protected Vector3 CalculateCellLocalPosition(Vector3 localPosition)
{
return localPosition + _cellLocalPositionOffset;
}
//计算滚动位置初始元素
protected abstract int CalculateStartIndex(float scrollPosition);
//计算吸附坐标
public abstract SnapResult CalculateSnapPosition(float scrollPosition);
///
/// 获取元素信息
///
/// 元素索引
public ScrollCellInfo GetCellInfo(int index)
{
return CellInfos[index];
}
///
/// 获取数据
///
/// 数据索引
public object GetItemData(int index)
{
return ItemsSource[index];
}
///
/// 获取数据
///
/// 数据索引
/// 强转类型
///
public T GetItemDataAs(int index)
{
return (T)ItemsSource[index];
}
///
/// 强制刷新
///
public void Refresh()
{
if (!IsInitialized)
{
//未初始化
return;
}
if (DataCount <= 0)
{
return;
}
Rebuild();
CallUpdateView(Scroller.Position, true); //强制更新所有元素
}
///
/// 元素更新时
///
protected void InvokeOnCellUpdate()
{
OnCellUpdate?.Invoke();
}
//吸附开始时触发
protected void InvokeOnSnapStart(float position)
{
OnSnapStart?.Invoke(position);
}
//吸附结束时触发
protected void InvokeOnSnapEnd(float position)
{
OnSnapEnd?.Invoke(position);
}
#if UNITY_EDITOR
#region 预览布局
protected virtual void OnValidate()
{
_isValidate = true;
}
//更新预览元素布局
protected virtual void UpdatePreviewCellsLayoutOnValidate()
{
if (!_isValidate)
{
return;
}
_isValidate = false;
if (!Application.isPlaying && m_PreviewCells.Count > 0)
{
GeneratePreviewCells();
}
}
protected virtual bool CanPreview()
{
//缺少cell container
if (CellContainer == null)
{
Debug.LogError("缺少 cell container ");
return false;
}
//缺少scroller
if (!ScrollerIsExists)
{
Debug.LogError("缺少 Scroller 组件");
return false;
}
//缺少 view port
if (Scroller.ViewPort == null)
{
Debug.LogError("Scroller->ViewPort 不可为空");
return false;
}
//缺少预览prefab
if (m_PreviewPrefab == null)
{
Debug.LogError("缺少 PreviewPrefab");
return false;
}
return true;
}
//预览元素
protected virtual void GeneratePreviewCells()
{
}
//清理预览元素
protected virtual void ClearPreviewCells()
{
var list = m_PreviewCells.ToList();
for (int i = 0; i < list.Count; i++)
{
var cell = list[i];
if (cell != null)
{
DestroyImmediate(cell);
}
}
m_PreviewCells.Clear();
}
//实例化预览元素
protected virtual void InstantiatePreviewCells(List points)
{
var cellLocalPositionOffset = RebuildCellLocalPositionOffset();
//生成预览元素
for (int i = 0; i < points.Count; i++)
{
var element = points[i];
var clone = Instantiate(m_PreviewPrefab, _cellContainer, true);
clone.transform.localPosition = Vector3.zero;
clone.transform.localScale = Vector3.one;
clone.transform.localEulerAngles = Vector3.zero;
var localPos = element + cellLocalPositionOffset;
clone.transform.localPosition = localPos;
clone.SetActive(true);
m_PreviewCells.Add(clone);
}
}
#endregion
#endif
protected virtual void OnDestroy()
{
//释放所有
PoolGroup?.Release();
CellInfos = null;
PositionInfos = null;
ItemsSource = null;
OnCellUpdate = null;
OnSnapEnd = null;
OnSnapStart = null;
OnViewPositionChanged = null;
}
}
}