using System; using UnityEngine; using UnityEngine.Serialization; using UnityEngine.UI; namespace AlicizaX.UI.RecyclerView { public class RecyclerView : MonoBehaviour { [SerializeField] private Direction direction; public Direction Direction { get => direction; set => direction = value; } [SerializeField] private Alignment alignment; public Alignment Alignment { get => alignment; set => alignment = value; } [SerializeField] private RectTransform content; public RectTransform Content { get { if (content == null) { content = transform.GetChild(0).GetComponent(); } return content; } } [SerializeField] private Vector2 spacing; public Vector2 Spacing { get => spacing; set => spacing = value; } [SerializeField] private Vector2 padding; public Vector2 Padding { get => padding; set => padding = value; } [SerializeField] private bool scroll; public bool Scroll { get => scroll; set => scroll = value; } [SerializeField] private bool snap; public bool Snap { get => snap; set => snap = value & scroll; } [SerializeField, Range(1f, 50f)] private float scrollSpeed = 7f; public float ScrollSpeed { get => scrollSpeed; set => scrollSpeed = value; } [SerializeField, Range(10f, 50f)] private float wheelSpeed = 30f; public float WheeelSpeed { get => wheelSpeed; set => wheelSpeed = value; } [SerializeField] private ViewHolder[] templates; public ViewHolder[] Templates { get => templates; set => templates = value; } [SerializeField] private string _scrollerTypeName; [SerializeReference] private Scroller _scroller; [SerializeField] private bool _showScrollBar; [SerializeReference] private Scrollbar _scrollbar; private ViewProvider viewProvider; [SerializeField] private string _layoutManagerTypeName; [SerializeReference] private LayoutManager _layoutManager; private int startIndex, endIndex; private int currentIndex; public Scroller Scroller => _scroller; public int CurrentIndex { get => currentIndex; } public bool CanScroll => true; public ViewProvider ViewProvider { get { viewProvider ??= templates.Length > 1 ? new MixedViewProvider(this, templates) : new SimpleViewProvider(this, templates); return viewProvider; } } public Scrollbar Scrollbar => _scrollbar; private IAdapter _adapter; public LayoutManager LayoutManager => _layoutManager; public Action OnIndexChanged; public Action OnScrollValueChanged; private void OnValidate() { if (_scroller != null) { _scroller.ScrollSpeed = scrollSpeed; _scroller.WheelSpeed = wheelSpeed; } } private void OnScrollChanged(float pos) { _layoutManager.UpdateLayout(); if (Scrollbar != null) { Scrollbar.SetValueWithoutNotify(pos / _scroller.MaxPosition); } if (_layoutManager.IsFullInvisibleStart(startIndex)) { viewProvider.RemoveViewHolder(startIndex); startIndex += _layoutManager.Unit; } else if (_layoutManager.IsFullVisibleStart(startIndex)) { if (startIndex == 0) { // TODO Do something, eg: Refresh } else { startIndex -= _layoutManager.Unit; viewProvider.CreateViewHolder(startIndex); } } if (_layoutManager.IsFullInvisibleEnd(endIndex)) { viewProvider.RemoveViewHolder(endIndex); endIndex -= _layoutManager.Unit; } else if (_layoutManager.IsFullVisibleEnd(endIndex)) { if (endIndex >= viewProvider.GetItemCount() - _layoutManager.Unit) { // TODO Do something, eg: Load More } else { endIndex += _layoutManager.Unit; viewProvider.CreateViewHolder(endIndex); } } // 使用滚动条快速定位时,刷新整个列表 if (!_layoutManager.IsVisible(startIndex) || !_layoutManager.IsVisible(endIndex)) { Refresh(); } _layoutManager.DoItemAnimation(); OnScrollValueChanged?.Invoke(); } private void OnMoveStoped() { if (Snap) { SnapTo(); } } private void OnScrollbarChanged(float ratio) { _scroller.ScrollToRatio(ratio); } private void OnScrollbarDragEnd() { if (_scroller.Position < _scroller.MaxPosition) { if (Snap) { SnapTo(); } } } private void Awake() { for (int i = 0; i < templates.Length; i++) { templates[i].gameObject.SetActive(false); } ConfigScroller(); ConfigScrollbar(); } private void OnDestroy() { viewProvider?.Dispose(); } public void Reset() { viewProvider?.Reset(); if (_scroller != null) { _scroller.Position = 0; } if (_scrollbar != null) { _scrollbar.SetValueWithoutNotify(0); } } public void SetAdapter(IAdapter adapter) { _adapter = adapter; ViewProvider.Adapter = _adapter; ViewProvider.LayoutManager = _layoutManager; ViewProvider.LayoutManager = _layoutManager; _layoutManager.RecyclerView = this; _layoutManager.Adapter = _adapter; _layoutManager.ViewProvider = viewProvider; _layoutManager.Direction = direction; _layoutManager.Alignment = alignment; _layoutManager.Spacing = spacing; _layoutManager.Padding = padding; _layoutManager.CanScroll = CanScroll; } private void ConfigScroller() { if (_scroller != null) { _scroller.ScrollSpeed = scrollSpeed; _scroller.WheelSpeed = wheelSpeed; _scroller.Snap = Snap; _scroller.OnValueChanged.AddListener(OnScrollChanged); _scroller.OnMoveStoped.AddListener(OnMoveStoped); } } private void ConfigScrollbar() { if (_showScrollBar && _scrollbar != null) { _scrollbar.gameObject.SetActive(scroll); _scrollbar.onValueChanged.AddListener(OnScrollbarChanged); _scrollbar.gameObject.AddComponent().OnDragEnd = OnScrollbarDragEnd; } } public void Refresh() { ViewProvider.Clear(); startIndex = _layoutManager.GetStartIndex(); endIndex = _layoutManager.GetEndIndex(); for (int i = startIndex; i <= endIndex; i += _layoutManager.Unit) { ViewProvider.CreateViewHolder(i); } _layoutManager.DoItemAnimation(); } public void RequestLayout() { _layoutManager.SetContentSize(); if (_scroller == null) return; _scroller.Direction = direction; _scroller.ViewSize = _layoutManager.ViewportSize; _scroller.ContentSize = _layoutManager.ContentSize; if (Scrollbar != null && _scroller.ContentSize != Vector2.zero) { if ((direction == Direction.Vertical && _layoutManager.ContentSize.y <= _layoutManager.ViewportSize.y) || (direction == Direction.Horizontal && _layoutManager.ContentSize.x <= _layoutManager.ViewportSize.x) || (direction == Direction.Custom)) { Scrollbar.gameObject.SetActive(false); } else { Scrollbar.gameObject.SetActive(true); Scrollbar.direction = direction == Direction.Vertical ? Scrollbar.Direction.TopToBottom : Scrollbar.Direction.LeftToRight; Scrollbar.size = direction == Direction.Vertical ? _scroller.ViewSize.y / _scroller.ContentSize.y : _scroller.ViewSize.x / _scroller.ContentSize.x; } } } public float GetScrollPosition() { return _scroller ? _scroller.Position : 0; } public void ScrollTo(int index, bool smooth = false) { if (!scroll) return; _scroller.ScrollTo(_layoutManager.IndexToPosition(index), smooth); if (!smooth) { Refresh(); } index %= _adapter.GetItemCount(); index = index < 0 ? _adapter.GetItemCount() + index : index; if (currentIndex != index) { currentIndex = index; OnIndexChanged?.Invoke(currentIndex); } } private void SnapTo() { var index = _layoutManager.PositionToIndex(GetScrollPosition()); ScrollTo(index, true); } } }