This commit is contained in:
陈思海 2025-12-26 14:22:46 +08:00
parent fc993a1dbe
commit 26c205ddc0
39 changed files with 1273 additions and 1004 deletions

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,7 @@ GameObject:
- component: {fileID: 409256563818501030}
- component: {fileID: 4967247000384896254}
- component: {fileID: 2527097672867102998}
- component: {fileID: 144409482669617178}
- component: {fileID: 222070472718835377}
m_Layer: 5
m_Name: ScrollView
m_TagString: Untagged
@ -90,9 +90,8 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 7efd8e83d2092b347952108134dc37eb, type: 3}
m_Name:
m_EditorClassIdentifier:
direction: 1
direction: 0
alignment: 1
content: {fileID: 7227160576944475251}
spacing: {x: 0, y: 0}
padding: {x: 0, y: 0}
scroll: 1
@ -100,20 +99,21 @@ MonoBehaviour:
scrollSpeed: 10
wheelSpeed: 30
templates: []
_scrollerTypeName: AlicizaX.UI.RecyclerView.Scroller
_scroller: {fileID: 144409482669617178}
_showScrollBar: 0
_scrollbar: {fileID: 0}
content: {fileID: 7227160576944475251}
showScrollBar: 0
scrollbar: {fileID: 0}
_layoutManagerTypeName: AlicizaX.UI.LinearLayoutManager
_layoutManager:
rid: 7492395943315111994
layoutManager:
rid: 6739296571988901898
_scrollerTypeName: AlicizaX.UI.Scroller
scroller: {fileID: 222070472718835377}
references:
version: 2
RefIds:
- rid: 7492395943315111994
- rid: 6739296571988901898
type: {class: LinearLayoutManager, ns: AlicizaX.UI, asm: AlicizaX.UI.Extension}
data:
--- !u!114 &144409482669617178
--- !u!114 &222070472718835377
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@ -125,6 +125,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 7b7de4cb3a1546e4a9ade6b8dbf8af92, type: 3}
m_Name:
m_EditorClassIdentifier:
dragStopTime: 0
--- !u!1 &9220717789715235424
GameObject:
m_ObjectHideFlags: 0

View File

@ -69,7 +69,7 @@ namespace AlicizaX.UI
recyclerView.Refresh();
}
protected internal virtual void SetList(List<T> list)
public virtual void SetList(List<T> list)
{
this.list = list;
recyclerView.Reset();

View File

@ -87,7 +87,7 @@ namespace AlicizaX.UI
base.NotifyDataChanged();
}
protected internal override void SetList(List<TData> list)
public override void SetList(List<TData> list)
{
showList.Clear();
base.SetList(list);

View File

@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: 05736c35a54b467a966b416c1461ba61
timeCreated: 1763617414
guid: d43a3e6aece04b86ab8eb139abb89e46
timeCreated: 1766661642

View File

@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: a059b7119e284e37a88a569f76b40579
timeCreated: 1763617420
guid: cacaed2312884ce9ba739399b8271b93
timeCreated: 1766661647

View File

@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: d42e2db77425447490cb9f68003e818b
timeCreated: 1741771999
guid: fdf59811e23f47f19be6398684473df2
timeCreated: 1766647995

View File

@ -1,48 +0,0 @@
using System;
using UnityEngine;
namespace AlicizaX.UI
{
[Serializable]
public class AlignableLinearLayoutManager : LinearLayoutManager
{
[SerializeField] private float alignmentCount = 0f; // 对齐比例 (0=顶部, 1=底部, 0.5=居中)
public override Vector2 CalculatePosition(int index)
{
float position;
if (direction == Direction.Vertical)
{
position = index * (lineHeight + spacing.y) - ScrollPosition ;
return new Vector2(0, position + padding.y);
}
position = index * (lineHeight + spacing.x) - ScrollPosition ;
var a = new Vector2(position + padding.x, 0);
return a;
}
public override float IndexToPosition(int index)
{
if (index < 0 || index >= adapter.GetItemCount()) return 0;
float len, viewLength, position;
if (direction == Direction.Vertical)
{
len = index * (lineHeight + spacing.y) - ((lineHeight + spacing.y) * alignmentCount);
viewLength = viewportSize.y;
position = len + viewLength > contentSize.y ? contentSize.y - viewportSize.y : len;
}
else
{
len = index * (lineHeight + spacing.x);
viewLength = viewportSize.x;
position = len + viewLength > contentSize.x ? contentSize.x - viewportSize.x : len;
}
return position;
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: e5cb3f76e0f84a7aa75959e194f524b5
timeCreated: 1748588163

View File

@ -3,23 +3,20 @@ using UnityEngine;
namespace AlicizaX.UI
{
[System.Serializable]
public class CircleLayoutManager : LayoutManager
{
private float radius;
private float intervalAngle;
private new CircleDirection direction;
[SerializeField]
private CircleDirection circleDirection= CircleDirection.Positive;
[SerializeField]
private float intervalAngle=0;
private float radius;
private float initalAngle;
public CircleLayoutManager(CircleDirection direction = CircleDirection.Positive)
{
this.direction = direction;
}
public CircleLayoutManager()
{
}
public override Vector2 CalculateContentSize()
@ -49,7 +46,7 @@ namespace AlicizaX.UI
public override Vector2 CalculatePosition(int index)
{
float angle = index * intervalAngle;
angle = direction == CircleDirection.Positive ? angle : -angle;
angle = circleDirection == CircleDirection.Positive ? angle : -angle;
angle += initalAngle + ScrollPosition;
float radian = angle * (Mathf.PI / 180f);
float x = radius * Mathf.Sin(radian);
@ -97,7 +94,7 @@ namespace AlicizaX.UI
for (int i = 0; i < viewHolders.Count; i++)
{
float angle = i * intervalAngle + initalAngle;
angle = direction == CircleDirection.Positive ? angle + ScrollPosition : angle - ScrollPosition;
angle = circleDirection == CircleDirection.Positive ? angle + ScrollPosition : angle - ScrollPosition;
float delta = (angle - initalAngle) % 360;
delta = delta < 0 ? delta + 360 : delta;
delta = delta > 180 ? 360 - delta : delta;

View File

@ -1,18 +1,17 @@
using System;
using UnityEngine;
namespace AlicizaX.UI
{
[Serializable]
[System.Serializable]
public class GridLayoutManager : LayoutManager
{
private Vector2 cellSize;
[SerializeField] private int cellCount;
[SerializeField] private int cellCounnt = 1;
public GridLayoutManager()
{
unit = cellCount;
this.unit = cellCounnt;
}
public override Vector2 CalculateContentSize()
@ -63,7 +62,6 @@ namespace AlicizaX.UI
width = viewportSize.x;
height = viewportSize.y;
}
return new Vector2((width - cellSize.x) / 2, (height - cellSize.y) / 2);
}
@ -80,7 +78,6 @@ namespace AlicizaX.UI
width = viewportSize.x;
height = viewportSize.y;
}
return new Vector2((width - cellSize.x) / 2, (height - cellSize.y) / 2);
}

View File

@ -1,13 +1,11 @@
using System;
using UnityEngine;
namespace AlicizaX.UI
{
[Serializable]
[System.Serializable]
public abstract class LayoutManager : ILayoutManager
{
protected Vector2 viewportSize;
public Vector2 ViewportSize
{
get => viewportSize;
@ -15,7 +13,6 @@ namespace AlicizaX.UI
}
protected Vector2 contentSize;
public Vector2 ContentSize
{
get => contentSize;
@ -23,7 +20,6 @@ namespace AlicizaX.UI
}
protected Vector2 contentOffset;
public Vector2 ContentOffset
{
get => contentOffset;
@ -31,7 +27,6 @@ namespace AlicizaX.UI
}
protected Vector2 viewportOffset;
public Vector2 ViewportOffset
{
get => viewportOffset;
@ -39,7 +34,6 @@ namespace AlicizaX.UI
}
protected IAdapter adapter;
public IAdapter Adapter
{
get => adapter;
@ -47,7 +41,6 @@ namespace AlicizaX.UI
}
protected ViewProvider viewProvider;
public ViewProvider ViewProvider
{
get => viewProvider;
@ -55,7 +48,6 @@ namespace AlicizaX.UI
}
protected RecyclerView recyclerView;
public virtual RecyclerView RecyclerView
{
get => recyclerView;
@ -63,7 +55,6 @@ namespace AlicizaX.UI
}
protected Direction direction;
public Direction Direction
{
get => direction;
@ -71,7 +62,6 @@ namespace AlicizaX.UI
}
protected Alignment alignment;
public Alignment Alignment
{
get => alignment;
@ -79,7 +69,6 @@ namespace AlicizaX.UI
}
protected Vector2 spacing;
public Vector2 Spacing
{
get => spacing;
@ -87,7 +76,6 @@ namespace AlicizaX.UI
}
protected Vector2 padding;
public Vector2 Padding
{
get => padding;
@ -95,26 +83,16 @@ namespace AlicizaX.UI
}
protected int unit = 1;
public int Unit
{
get => unit;
set => unit = value;
}
protected bool canScroll;
public bool CanScroll
{
get => canScroll;
set => canScroll = value;
}
public float ScrollPosition => recyclerView.GetScrollPosition();
public LayoutManager()
{
}
public LayoutManager() { }
public void SetContentSize()
{
@ -135,7 +113,9 @@ namespace AlicizaX.UI
public virtual void Layout(ViewHolder viewHolder, int index)
{
Vector2 pos = CalculatePosition(index);
Vector3 position = direction == Direction.Vertical ? new Vector3(pos.x - contentOffset.x, -pos.y + contentOffset.y, 0) : new Vector3(pos.x - contentOffset.x, -pos.y + contentOffset.y, 0);
Vector3 position = direction == Direction.Vertical ?
new Vector3(pos.x - contentOffset.x, -pos.y + contentOffset.y, 0) :
new Vector3(pos.x - contentOffset.x, -pos.y + contentOffset.y, 0);
viewHolder.RectTransform.anchoredPosition3D = position;
}
@ -155,9 +135,7 @@ namespace AlicizaX.UI
public abstract int PositionToIndex(float position);
public virtual void DoItemAnimation()
{
}
public virtual void DoItemAnimation() { }
public virtual bool IsFullVisibleStart(int index)
{
@ -222,7 +200,6 @@ namespace AlicizaX.UI
{
len = alignment == Alignment.Center ? Mathf.Min(contentSize.x, viewportSize.x) : viewportSize.x;
}
return len;
}
@ -234,9 +211,9 @@ namespace AlicizaX.UI
public enum Direction
{
Vertical,
Horizontal,
Custom,
Vertical = 0,
Horizontal = 1,
Custom = 2
}
public enum Alignment

View File

@ -1,14 +1,14 @@
using System;
using UnityEngine;
namespace AlicizaX.UI
{
[Serializable]
public class LinearLayoutManager : LayoutManager
{
protected float lineHeight;
public LinearLayoutManager()
{
}
public LinearLayoutManager() { }
public override Vector2 CalculateContentSize()
{
@ -22,7 +22,6 @@ namespace AlicizaX.UI
position = index * (lineHeight + spacing.y) - spacing.y;
return new Vector2(contentSize.x, position + padding.y * 2);
}
position = index * (lineHeight + spacing.x) - spacing.x;
return new Vector2(position + padding.x * 2, contentSize.y);
}
@ -35,7 +34,6 @@ namespace AlicizaX.UI
position = index * (lineHeight + spacing.y) - ScrollPosition;
return new Vector2(0, position + padding.y);
}
position = index * (lineHeight + spacing.x) - ScrollPosition;
return new Vector2(position + padding.x, 0);
}
@ -47,7 +45,6 @@ namespace AlicizaX.UI
{
return new Vector2(0, (len - lineHeight) / 2);
}
return new Vector2((len - lineHeight) / 2, 0);
}
@ -57,7 +54,6 @@ namespace AlicizaX.UI
{
return new Vector2(0, (viewportSize.y - lineHeight) / 2);
}
return new Vector2((viewportSize.x - lineHeight) / 2, 0);
}

View File

@ -1,7 +1,9 @@
using System;
using UnityEngine;
namespace AlicizaX.UI
{
[Serializable]
public class MixedLayoutManager : LayoutManager
{
public MixedLayoutManager() { }

View File

@ -4,10 +4,10 @@ using UnityEngine;
namespace AlicizaX.UI
{
[Serializable]
public class PageLayoutManager : LinearLayoutManager
{
[SerializeField]
private float minScale;
[SerializeField] private float minScale = 0.9f;
public PageLayoutManager()
{

View File

@ -1,3 +1,8 @@
fileFormatVersion: 2
guid: 3a9bb27da68d4451a7d79cdb7abc1506
timeCreated: 1748431180
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,8 +1,6 @@
namespace SimpleObjectPool
namespace AlicizaX.UI
{
internal interface IMixedObjectFactory<T> where T : class
public interface IMixedObjectFactory<T> where T : class
{
T Create(string typeName);
@ -12,5 +10,4 @@ namespace SimpleObjectPool
bool Validate(string typeName, T obj);
}
}

View File

@ -1,8 +1,8 @@
namespace SimpleObjectPool
namespace AlicizaX.UI
{
using System;
internal interface IMixedObjectPool<T> : IDisposable where T : class
public interface IMixedObjectPool<T> : IDisposable where T : class
{
T Allocate(string typeName);

View File

@ -1,8 +1,6 @@
namespace SimpleObjectPool
namespace AlicizaX.UI
{
internal interface IObjectFactory<T> where T : class
public interface IObjectFactory<T> where T : class
{
/// <summary>
/// 创建对象
@ -29,5 +27,4 @@ namespace SimpleObjectPool
/// <returns></returns>
bool Validate(T obj);
}
}

View File

@ -1,8 +1,8 @@
namespace SimpleObjectPool
namespace AlicizaX.UI
{
using System;
internal interface IObjectPool : IDisposable
public interface IObjectPool : IDisposable
{
/// <summary>
/// 从池子中分配一个可用对象,没有的话就创建一个
@ -17,7 +17,7 @@ namespace SimpleObjectPool
void Free(object obj);
}
internal interface IObjectPool<T> : IObjectPool, IDisposable where T : class
public interface IObjectPool<T> : IObjectPool, IDisposable where T : class
{
new T Allocate();

View File

@ -1,10 +1,7 @@
namespace SimpleObjectPool
namespace AlicizaX.UI
{
internal interface IPooledObject
public interface IPooledObject
{
void Free();
}
}

View File

@ -1,10 +1,10 @@
namespace SimpleObjectPool
namespace AlicizaX.UI
{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
internal class MixedObjectPool<T> : IMixedObjectPool<T> where T : class
public class MixedObjectPool<T> : IMixedObjectPool<T> where T : class
{
private const int DEFAULT_MAX_SIZE_PER_TYPE = 10;

View File

@ -1,9 +1,9 @@
namespace SimpleObjectPool
namespace AlicizaX.UI
{
using System;
using System.Threading;
internal class ObjectPool<T> : IObjectPool<T> where T : class
public class ObjectPool<T> : IObjectPool<T> where T : class
{
private int maxSize;
private int initialSize;

View File

@ -1,8 +1,8 @@
namespace SimpleObjectPool
namespace AlicizaX.UI
{
using UnityEngine;
internal class UnityComponentFactory<T> : IObjectFactory<T> where T : Component
public class UnityComponentFactory<T> : IObjectFactory<T> where T : Component
{
private T template;
private Transform parent;

View File

@ -1,8 +1,8 @@
namespace SimpleObjectPool
namespace AlicizaX.UI
{
using UnityEngine;
internal class UnityGameObjectFactory : IObjectFactory<GameObject>
public class UnityGameObjectFactory : IObjectFactory<GameObject>
{
protected GameObject template;
protected Transform parent;

View File

@ -1,10 +1,10 @@
namespace SimpleObjectPool
namespace AlicizaX.UI
{
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
internal class UnityMixedComponentFactory<T> : IMixedObjectFactory<T> where T : Component
public class UnityMixedComponentFactory<T> : IMixedObjectFactory<T> where T : Component
{
protected T template;
protected Transform parent;

View File

@ -1,8 +1,8 @@
namespace SimpleObjectPool
namespace AlicizaX.UI
{
using UnityEngine;
internal class UnityMixedGameObjectFactory : IMixedObjectFactory<GameObject>
public class UnityMixedGameObjectFactory : IMixedObjectFactory<GameObject>
{
protected GameObject template;
protected Transform parent;
@ -40,5 +40,4 @@ namespace SimpleObjectPool
return true;
}
}
}

View File

@ -1,13 +1,61 @@
using System;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UI;
namespace AlicizaX.UI
{
public class RecyclerView : MonoBehaviour
{
[SerializeField] private Direction direction;
#region Serialized Fields - Layout Settings
[HideInInspector] [SerializeField] private Direction direction;
[HideInInspector] [SerializeField] private Alignment alignment;
[HideInInspector] [SerializeField] private Vector2 spacing;
[HideInInspector] [SerializeField] private Vector2 padding;
#endregion
#region Serialized Fields - Scroll Settings
[HideInInspector] [SerializeField] private bool scroll;
[HideInInspector] [SerializeField] private bool snap;
[HideInInspector] [SerializeField, Range(1f, 50f)]
private float scrollSpeed = 7f;
[HideInInspector] [SerializeField, Range(10f, 50f)]
private float wheelSpeed = 30f;
#endregion
#region Serialized Fields - Components
[HideInInspector] [SerializeField] private ViewHolder[] templates;
[HideInInspector] [SerializeField] private RectTransform content;
[HideInInspector] [SerializeField] private bool showScrollBar;
[HideInInspector] [SerializeField] private Scrollbar scrollbar;
#endregion
#region Serialized Fields - Internal (Hidden in Inspector)
[HideInInspector] [SerializeField] private string _layoutManagerTypeName;
[SerializeReference] private LayoutManager layoutManager;
[HideInInspector] [SerializeField] private string _scrollerTypeName;
[HideInInspector] [SerializeReference] private Scroller scroller;
#endregion
#region Private Fields
private ViewProvider viewProvider;
private int startIndex;
private int endIndex;
private int currentIndex;
#endregion
#region Public Properties - Layout Settings
public Direction Direction
{
@ -15,15 +63,119 @@ namespace AlicizaX.UI
set => direction = value;
}
[SerializeField] private Alignment alignment;
public Alignment Alignment
{
get => alignment;
set => alignment = value;
}
[SerializeField] private RectTransform content;
public Vector2 Spacing
{
get => spacing;
set => spacing = value;
}
public Vector2 Padding
{
get => padding;
set => padding = value;
}
#endregion
#region Public Properties - Scroll Settings
public bool Scroll
{
get => scroll;
set
{
if (scroll == value) return;
scroll = value;
// 启/停 scroller如果存在
if (scroller != null)
{
// 如果 Scroller 是 MonoBehaviour可以启/停组件;否则回退到设置一个 Position/flags
scroller.enabled = scroll;
// 将当前的滚动相关配置下发到 scroller保持一致性
scroller.ScrollSpeed = scrollSpeed;
scroller.WheelSpeed = wheelSpeed;
scroller.Snap = snap;
}
// 更新 scrollbar 显示(只有在 showScrollBar 为 true 时才显示)
if (scrollbar != null)
{
scrollbar.gameObject.SetActive(showScrollBar && scroll);
}
// 如果启用/禁用滚动后需要调整布局或滚动条大小,刷新布局
RequestLayout();
}
}
public bool Snap
{
get => snap;
set
{
// Snap 依赖于 Scroll与原逻辑保持一致
bool newSnap = value & scroll;
if (snap == newSnap) return;
snap = newSnap;
if (scroller != null)
{
scroller.Snap = snap;
}
// 如果开启了 snap可以选做立即对齐到最近项
// if (snap && scroller != null) SnapToNearestItem();
}
}
public float ScrollSpeed
{
get => scrollSpeed;
set
{
if (Mathf.Approximately(scrollSpeed, value)) return;
scrollSpeed = value;
if (scroller != null)
{
scroller.ScrollSpeed = scrollSpeed;
}
}
}
public float WheelSpeed
{
get => wheelSpeed;
set
{
if (Mathf.Approximately(wheelSpeed, value)) return;
wheelSpeed = value;
if (scroller != null)
{
scroller.WheelSpeed = wheelSpeed;
}
}
}
#endregion
#region Public Properties - Components
public ViewHolder[] Templates
{
get => templates;
set => templates = value;
}
public RectTransform Content
{
@ -38,328 +190,354 @@ namespace AlicizaX.UI
}
}
[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;
}
[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 Scrollbar Scrollbar => scrollbar;
public Scroller Scroller => scroller;
public ViewProvider ViewProvider
{
get
{
viewProvider ??= templates.Length > 1 ? new MixedViewProvider(this, templates) : new SimpleViewProvider(this, templates);
if (viewProvider == null)
{
viewProvider = templates.Length > 1
? new MixedViewProvider(this, templates)
: new SimpleViewProvider(this, templates);
}
return viewProvider;
}
}
public Scrollbar Scrollbar => _scrollbar;
#endregion
private IAdapter _adapter;
#region Public Properties - State
public IAdapter RecyclerViewAdapter { get; set; }
public int CurrentIndex
{
get => currentIndex;
set => currentIndex = value;
}
#endregion
#region Events
public Action<int> OnIndexChanged;
public Action OnScrollValueChanged;
public Action OnMoveingChanged;
private void OnValidate()
{
if (_scroller != null)
{
_scroller.ScrollSpeed = scrollSpeed;
_scroller.WheelSpeed = wheelSpeed;
}
}
#endregion
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 OnMoveing()
{
OnMoveingChanged?.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();
}
}
}
#region Unity Lifecycle
private void Awake()
{
for (int i = 0; i < templates.Length; i++)
{
templates[i].gameObject.SetActive(false);
}
ConfigScroller();
ConfigScrollbar();
InitializeTemplates();
ConfigureScroller();
ConfigureScrollbar();
}
private void OnDestroy()
#endregion
#region Initialization
private void InitializeTemplates()
{
viewProvider?.Dispose();
if (templates == null) return;
for (int i = 0; i < templates.Length; i++)
{
if (templates[i] != null)
{
templates[i].gameObject.SetActive(false);
}
}
}
private void ConfigureScroller()
{
if (scroller == null) return;
scroller.ScrollSpeed = scrollSpeed;
scroller.WheelSpeed = wheelSpeed;
scroller.Snap = snap;
scroller.OnValueChanged.AddListener(OnScrollChanged);
scroller.OnMoveStoped.AddListener(OnMoveStoped);
}
private void ConfigureScrollbar()
{
if (!showScrollBar || scrollbar == null) return;
scrollbar.gameObject.SetActive(scroll);
scrollbar.onValueChanged.AddListener(OnScrollbarChanged);
var scrollbarEx = scrollbar.gameObject.GetComponent<ScrollbarEx>();
if (scrollbarEx == null)
{
scrollbarEx = scrollbar.gameObject.AddComponent<ScrollbarEx>();
}
scrollbarEx.OnDragEnd = OnScrollbarDragEnd;
}
#endregion
#region Public Methods - Setup
public void SetAdapter(IAdapter adapter)
{
if (adapter == null)
{
Debug.LogError("Adapter cannot be null");
return;
}
RecyclerViewAdapter = adapter;
ViewProvider.Adapter = adapter;
ViewProvider.LayoutManager = layoutManager;
layoutManager.RecyclerView = this;
layoutManager.Adapter = adapter;
layoutManager.ViewProvider = viewProvider;
layoutManager.Direction = direction;
layoutManager.Alignment = alignment;
layoutManager.Spacing = spacing;
layoutManager.Padding = padding;
}
public void Reset()
{
viewProvider?.Reset();
if (_scroller != null)
if (scroller != null)
{
_scroller.Position = 0;
scroller.Position = 0;
}
if (_scrollbar != null)
if (scrollbar != null)
{
_scrollbar.SetValueWithoutNotify(0);
scrollbar.SetValueWithoutNotify(0);
}
}
public void SetAdapter(IAdapter adapter)
{
_adapter = adapter;
ViewProvider.Adapter = _adapter;
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;
}
#endregion
private void ConfigScroller()
{
if (_scroller != null)
{
_scroller.ScrollSpeed = scrollSpeed;
_scroller.WheelSpeed = wheelSpeed;
_scroller.Snap = Snap;
_scroller.OnValueChanged.AddListener(OnScrollChanged);
_scroller.OnMoveStoped.AddListener(OnMoveStoped);
_scroller.OnMoveing.AddListener(OnMoveing);
}
}
private void ConfigScrollbar()
{
if (_showScrollBar && _scrollbar != null)
{
_scrollbar.gameObject.SetActive(scroll);
_scrollbar.onValueChanged.AddListener(OnScrollbarChanged);
_scrollbar.gameObject.AddComponent<ScrollbarEx>().OnDragEnd = OnScrollbarDragEnd;
}
}
#region Public Methods - Layout
public void Refresh()
{
ViewProvider.Clear();
startIndex = _layoutManager.GetStartIndex();
endIndex = _layoutManager.GetEndIndex();
for (int i = startIndex; i <= endIndex; i += _layoutManager.Unit)
startIndex = layoutManager.GetStartIndex();
endIndex = layoutManager.GetEndIndex();
for (int i = startIndex; i <= endIndex; i += layoutManager.Unit)
{
ViewProvider.CreateViewHolder(i);
}
_layoutManager.DoItemAnimation();
layoutManager.DoItemAnimation();
}
public void RequestLayout()
{
_layoutManager.SetContentSize();
layoutManager.SetContentSize();
if (_scroller == null) return;
if (scroller == null) return;
_scroller.Direction = direction;
_scroller.ViewSize = _layoutManager.ViewportSize;
_scroller.ContentSize = _layoutManager.ContentSize;
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;
}
}
UpdateScrollbarVisibility();
}
#endregion
#region Public Methods - Scrolling
public float GetScrollPosition()
{
return _scroller ? _scroller.Position : 0;
return scroller != null ? scroller.Position : 0;
}
public void ScrollTo(int index, bool smooth = false)
{
if (!scroll) return;
if (!scroll || scroller == null) return;
scroller.ScrollTo(layoutManager.IndexToPosition(index), smooth);
_scroller.ScrollTo(_layoutManager.IndexToPosition(index), smooth);
if (!smooth)
{
Refresh();
}
index %= _adapter.GetItemCount();
index = index < 0 ? _adapter.GetItemCount() + index : index;
UpdateCurrentIndex(index);
}
#endregion
#region Private Methods - Scroll Callbacks
private void OnScrollChanged(float position)
{
layoutManager.UpdateLayout();
UpdateScrollbarValue(position);
UpdateVisibleRange();
layoutManager.DoItemAnimation();
OnScrollValueChanged?.Invoke();
}
private void OnMoveStoped()
{
if (snap)
{
SnapToNearestItem();
}
}
private void OnScrollbarChanged(float ratio)
{
if (scroller != null)
{
scroller.ScrollToRatio(ratio);
}
}
private void OnScrollbarDragEnd()
{
if (scroller == null) return;
if (scroller.Position < scroller.MaxPosition && snap)
{
SnapToNearestItem();
}
}
#endregion
#region Private Methods - Scroll Helpers
private void UpdateScrollbarValue(float position)
{
if (scrollbar != null && scroller != null)
{
float ratio = scroller.MaxPosition > 0 ? position / scroller.MaxPosition : 0;
scrollbar.SetValueWithoutNotify(ratio);
}
}
private void UpdateVisibleRange()
{
// Handle start index
if (layoutManager.IsFullInvisibleStart(startIndex))
{
viewProvider.RemoveViewHolder(startIndex);
startIndex += layoutManager.Unit;
}
else if (layoutManager.IsFullVisibleStart(startIndex))
{
if (startIndex == 0)
{
// TODO: Implement refresh logic
}
else
{
startIndex -= layoutManager.Unit;
viewProvider.CreateViewHolder(startIndex);
}
}
// Handle end index
if (layoutManager.IsFullInvisibleEnd(endIndex))
{
viewProvider.RemoveViewHolder(endIndex);
endIndex -= layoutManager.Unit;
}
else if (layoutManager.IsFullVisibleEnd(endIndex))
{
if (endIndex >= viewProvider.GetItemCount() - layoutManager.Unit)
{
// TODO: Implement load more logic
}
else
{
endIndex += layoutManager.Unit;
viewProvider.CreateViewHolder(endIndex);
}
}
// Refresh if out of visible range
if (!layoutManager.IsVisible(startIndex) || !layoutManager.IsVisible(endIndex))
{
Refresh();
}
}
private void UpdateScrollbarVisibility()
{
if (scrollbar == null || scroller == null || layoutManager.ContentSize == Vector2.zero)
return;
bool shouldShow = ShouldShowScrollbar();
scrollbar.gameObject.SetActive(shouldShow);
if (shouldShow)
{
ConfigureScrollbarDirection();
ConfigureScrollbarSize();
}
}
private bool ShouldShowScrollbar()
{
if (direction == Direction.Custom) return false;
if (direction == Direction.Vertical)
{
return layoutManager.ContentSize.y > layoutManager.ViewportSize.y;
}
else // Horizontal
{
return layoutManager.ContentSize.x > layoutManager.ViewportSize.x;
}
}
private void ConfigureScrollbarDirection()
{
scrollbar.direction = direction == Direction.Vertical
? Scrollbar.Direction.TopToBottom
: Scrollbar.Direction.LeftToRight;
}
private void ConfigureScrollbarSize()
{
if (direction == Direction.Vertical)
{
scrollbar.size = scroller.ViewSize.y / scroller.ContentSize.y;
}
else
{
scrollbar.size = scroller.ViewSize.x / scroller.ContentSize.x;
}
}
private void SnapToNearestItem()
{
int index = layoutManager.PositionToIndex(GetScrollPosition());
ScrollTo(index, true);
}
private void UpdateCurrentIndex(int index)
{
if (RecyclerViewAdapter == null) return;
int itemCount = RecyclerViewAdapter.GetItemCount();
index %= itemCount;
index = index < 0 ? itemCount + index : index;
if (currentIndex != index)
{
@ -368,11 +546,6 @@ namespace AlicizaX.UI
}
}
private void SnapTo()
{
var index = _layoutManager.PositionToIndex(GetScrollPosition());
ScrollTo(index, true);
}
#endregion
}
}

View File

@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: a01ce47da31a2e0438fd8d38b203c0d5, type: 3}
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,10 +1,8 @@
using System;
using UnityEngine;
using UnityEngine.EventSystems;
namespace AlicizaX.UI
{
[Serializable]
public class CircleScroller : Scroller
{
private Vector2 centerPosition;

View File

@ -9,19 +9,7 @@ namespace AlicizaX.UI
void ScrollTo(float position, bool smooth = false);
}
public class ScrollerEvent : UnityEvent<float>
{
}
public class MoveStopEvent : UnityEvent
{
}
public class DraggingEvent : UnityEvent<bool>
{
}
public class MoveingEvent : UnityEvent
{
}
public class ScrollerEvent : UnityEvent<float> { }
public class MoveStopEvent : UnityEvent { }
public class DraggingEvent : UnityEvent<bool> { }
}

View File

@ -1,26 +1,18 @@
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.EventSystems;
namespace AlicizaX.UI
{
[Serializable]
public class Scroller : MonoBehaviour, IScroller, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler
{
protected float position;
public float Position
{
get => position;
set => position = value;
}
public float Position { get => position; set => position = value; }
protected float velocity;
public float Velocity => velocity;
protected Direction direction;
public Direction Direction
{
get => direction;
@ -31,7 +23,6 @@ namespace AlicizaX.UI
/// 内容所需要大小
/// </summary>
protected Vector2 contentSize;
public Vector2 ContentSize
{
get => contentSize;
@ -42,7 +33,6 @@ namespace AlicizaX.UI
/// 所在 View 的真实大小
/// </summary>
protected Vector2 viewSize;
public Vector2 ViewSize
{
get => viewSize;
@ -50,7 +40,6 @@ namespace AlicizaX.UI
}
protected float scrollSpeed = 1f;
public float ScrollSpeed
{
get => scrollSpeed;
@ -58,7 +47,6 @@ namespace AlicizaX.UI
}
protected float wheelSpeed = 30f;
public float WheelSpeed
{
get => wheelSpeed;
@ -66,7 +54,6 @@ namespace AlicizaX.UI
}
protected bool snap;
public bool Snap
{
get => snap;
@ -76,38 +63,21 @@ namespace AlicizaX.UI
protected ScrollerEvent scrollerEvent = new();
protected MoveStopEvent moveStopEvent = new();
protected DraggingEvent draggingEvent = new();
protected MoveingEvent moveingEvent = new();
public float MaxPosition => direction == Direction.Vertical ? Mathf.Max(contentSize.y - viewSize.y, 0) : Mathf.Max(contentSize.x - viewSize.x, 0);
public float MaxPosition => direction == Direction.Vertical ?
Mathf.Max(contentSize.y - viewSize.y, 0) :
Mathf.Max(contentSize.x - viewSize.x, 0);
public float ViewLength => direction == Direction.Vertical ? viewSize.y : viewSize.x;
public MoveingEvent OnMoveing
{
get => moveingEvent;
set => moveingEvent = value;
}
public ScrollerEvent OnValueChanged { get => scrollerEvent; set => scrollerEvent = value; }
public ScrollerEvent OnValueChanged
{
get => scrollerEvent;
set => scrollerEvent = value;
}
public MoveStopEvent OnMoveStoped { get => moveStopEvent; set => moveStopEvent = value; }
public MoveStopEvent OnMoveStoped
{
get => moveStopEvent;
set => moveStopEvent = value;
}
public DraggingEvent OnDragging
{
get => draggingEvent;
set => draggingEvent = value;
}
public DraggingEvent OnDragging { get => draggingEvent; set => draggingEvent = value; }
// 停止滑动的时间,但此时并未释放鼠标按键
private float dragStopTime = 0f;
public float dragStopTime = 0f;
public virtual void ScrollTo(float position, bool smooth = false)
{
@ -151,10 +121,9 @@ namespace AlicizaX.UI
position += velocity;
OnValueChanged?.Invoke(position);
OnMoveing?.Invoke();
}
public virtual void OnScrolled(PointerEventData eventData)
public void OnScroll(PointerEventData eventData)
{
StopAllCoroutines();
@ -163,7 +132,7 @@ namespace AlicizaX.UI
position += velocity;
OnValueChanged?.Invoke(position);
OnMoveing?.Invoke();
Inertia();
Elastic();
}
@ -184,7 +153,6 @@ namespace AlicizaX.UI
{
rate = Mathf.Max(0, 1 - (Mathf.Abs(position - MaxPosition) / ViewLength));
}
return rate;
}
@ -273,11 +241,5 @@ namespace AlicizaX.UI
position = targetPos;
OnValueChanged?.Invoke(position);
}
void IScrollHandler.OnScroll(PointerEventData eventData)
{
OnScrolled(eventData);
}
}
}

View File

@ -74,7 +74,7 @@ namespace AlicizaX.UI
}
}
public static class UGList
public static class UGListCreateHelper
{
public static UGList<TData> Create<TData>(RecyclerView recyclerView, Action<TData> onItemClick = null) where TData : ISimpleViewData
=> new UGList<TData>(recyclerView, onItemClick);

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fd094e3cb2194cb193221ea9436a169a
timeCreated: 1766729620

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 369d2829a7a2462ba93212b3d8afe0fa
timeCreated: 1763615925

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 19892a10438348b3b7e7cac3e2201b9e
timeCreated: 1763615934

View File

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using SimpleObjectPool;
using UnityEngine;
namespace AlicizaX.UI

View File

@ -1,5 +1,4 @@
using System;
using SimpleObjectPool;
namespace AlicizaX.UI
{

View File

@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using AlicizaX.UI.Runtime;
using UnityEngine;
namespace AlicizaX.UI
@ -8,7 +6,7 @@ namespace AlicizaX.UI
/// <summary>
/// 提供和管理 ViewHolder
/// </summary>
public abstract class ViewProvider : IDisposable
public abstract class ViewProvider
{
private readonly List<ViewHolder> viewHolders = new();
@ -44,10 +42,10 @@ namespace AlicizaX.UI
string viewName = Adapter.GetViewName(i);
var viewHolder = Allocate(viewName);
viewHolder.OnStart();
viewHolder.Name = viewName;
viewHolder.Index = i;
viewHolders.Add(viewHolder);
LayoutManager.Layout(viewHolder, i);
Adapter.OnBindViewHolder(viewHolder, i);
}
@ -131,15 +129,5 @@ namespace AlicizaX.UI
{
return Adapter == null ? 0 : Adapter.GetItemCount();
}
public void Dispose()
{
foreach (var viewHolder in viewHolders)
{
Free(viewHolder.Name, viewHolder);
}
viewHolders.Clear();
}
}
}