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; } } }