This commit is contained in:
陈思海 2026-03-11 14:18:07 +08:00
parent 623edbeed6
commit 2471317801
11 changed files with 608 additions and 16 deletions

View File

@ -3,6 +3,10 @@ using System.Collections.Generic;
namespace AlicizaX.UI
{
/// <summary>
/// RecyclerView 的通用适配器,支持数据绑定、选中状态和列表操作
/// </summary>
/// <typeparam name="T">数据类型,必须实现 ISimpleViewData 接口</typeparam>
public class Adapter<T> : IAdapter where T : ISimpleViewData
{
protected RecyclerView recyclerView;
@ -12,20 +16,38 @@ namespace AlicizaX.UI
protected int choiceIndex = -1;
/// <summary>
/// 当前选中项的索引
/// </summary>
public int ChoiceIndex
{
get => choiceIndex;
set { SetChoiceIndex(value); }
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="recyclerView">RecyclerView 实例</param>
public Adapter(RecyclerView recyclerView) : this(recyclerView, new List<T>(), null)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="recyclerView">RecyclerView 实例</param>
/// <param name="list">数据列表</param>
public Adapter(RecyclerView recyclerView, List<T> list) : this(recyclerView, list, null)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="recyclerView">RecyclerView 实例</param>
/// <param name="list">数据列表</param>
/// <param name="onItemClick">列表项点击回调</param>
public Adapter(RecyclerView recyclerView, List<T> list, Action<T> onItemClick)
{
this.recyclerView = recyclerView;
@ -33,21 +55,33 @@ namespace AlicizaX.UI
this.onItemClick = onItemClick;
}
/// <summary>
/// 获取列表项总数
/// </summary>
public virtual int GetItemCount()
{
return list == null ? 0 : list.Count;
}
/// <summary>
/// 获取实际数据项数量
/// </summary>
public virtual int GetRealCount()
{
return GetItemCount();
}
/// <summary>
/// 获取指定索引位置的视图名称
/// </summary>
public virtual string GetViewName(int index)
{
return "";
}
/// <summary>
/// 绑定视图持有者与数据
/// </summary>
public virtual void OnBindViewHolder(ViewHolder viewHolder, int index)
{
if (index < 0 || index >= GetItemCount()) return;
@ -63,12 +97,19 @@ namespace AlicizaX.UI
viewHolder.BindChoiceState(index == choiceIndex);
}
/// <summary>
/// 通知数据已更改
/// </summary>
public virtual void NotifyDataChanged()
{
recyclerView.RequestLayout();
recyclerView.Refresh();
}
/// <summary>
/// 设置数据列表并刷新视图
/// </summary>
/// <param name="list">新的数据列表</param>
public virtual void SetList(List<T> list)
{
this.list = list;
@ -76,6 +117,11 @@ namespace AlicizaX.UI
NotifyDataChanged();
}
/// <summary>
/// 获取指定索引的数据
/// </summary>
/// <param name="index">数据索引</param>
/// <returns>数据对象,索引无效时返回 default</returns>
public T GetData(int index)
{
if (index < 0 || index >= GetItemCount()) return default;
@ -83,36 +129,62 @@ namespace AlicizaX.UI
return list[index];
}
/// <summary>
/// 添加单个数据项
/// </summary>
/// <param name="item">要添加的数据项</param>
public void Add(T item)
{
list.Add(item);
NotifyDataChanged();
}
/// <summary>
/// 批量添加数据项
/// </summary>
/// <param name="collection">要添加的数据集合</param>
public void AddRange(IEnumerable<T> collection)
{
list.AddRange(collection);
NotifyDataChanged();
}
/// <summary>
/// 在指定位置插入数据项
/// </summary>
/// <param name="index">插入位置</param>
/// <param name="item">要插入的数据项</param>
public void Insert(int index, T item)
{
list.Insert(index, item);
NotifyDataChanged();
}
/// <summary>
/// 在指定位置批量插入数据项
/// </summary>
/// <param name="index">插入位置</param>
/// <param name="collection">要插入的数据集合</param>
public void InsertRange(int index, IEnumerable<T> collection)
{
list.InsertRange(index, collection);
NotifyDataChanged();
}
/// <summary>
/// 移除指定的数据项
/// </summary>
/// <param name="item">要移除的数据项</param>
public void Remove(T item)
{
int index = list.IndexOf(item);
RemoveAt(index);
}
/// <summary>
/// 移除指定索引位置的数据项
/// </summary>
/// <param name="index">要移除的索引</param>
public void RemoveAt(int index)
{
if (index < 0 || index >= GetItemCount()) return;
@ -121,47 +193,79 @@ namespace AlicizaX.UI
NotifyDataChanged();
}
/// <summary>
/// 移除指定范围的数据项
/// </summary>
/// <param name="index">起始索引</param>
/// <param name="count">移除数量</param>
public void RemoveRange(int index, int count)
{
list.RemoveRange(index, count);
NotifyDataChanged();
}
/// <summary>
/// 移除所有符合条件的数据项
/// </summary>
/// <param name="match">匹配条件</param>
public void RemoveAll(Predicate<T> match)
{
list.RemoveAll(match);
NotifyDataChanged();
}
/// <summary>
/// 清空所有数据
/// </summary>
public void Clear()
{
list.Clear();
NotifyDataChanged();
}
/// <summary>
/// 反转指定范围的数据项顺序
/// </summary>
/// <param name="index">起始索引</param>
/// <param name="count">反转数量</param>
public void Reverse(int index, int count)
{
list.Reverse(index, count);
NotifyDataChanged();
}
/// <summary>
/// 反转所有数据项顺序
/// </summary>
public void Reverse()
{
list.Reverse();
NotifyDataChanged();
}
/// <summary>
/// 使用指定的比较器排序数据
/// </summary>
/// <param name="comparison">比较器</param>
public void Sort(Comparison<T> comparison)
{
list.Sort(comparison);
NotifyDataChanged();
}
/// <summary>
/// 设置列表项点击回调
/// </summary>
/// <param name="onItemClick">点击回调</param>
public void SetOnItemClick(Action<T> onItemClick)
{
this.onItemClick = onItemClick;
}
/// <summary>
/// 设置选中项索引
/// </summary>
/// <param name="index">要选中的索引</param>
protected void SetChoiceIndex(int index)
{
if (index == choiceIndex) return;
@ -185,6 +289,12 @@ namespace AlicizaX.UI
}
}
/// <summary>
/// 尝试获取指定索引的视图持有者
/// </summary>
/// <param name="index">数据索引</param>
/// <param name="viewHolder">输出的视图持有者</param>
/// <returns>是否成功获取</returns>
private bool TryGetViewHolder(int index, out ViewHolder viewHolder)
{
viewHolder = recyclerView.ViewProvider.GetViewHolder(index);

View File

@ -1,15 +1,39 @@
namespace AlicizaX.UI
{
/// <summary>
/// RecyclerView 适配器接口,负责提供数据和绑定视图
/// </summary>
public interface IAdapter
{
/// <summary>
/// 获取列表项总数(包括循环或分组后的虚拟数量)
/// </summary>
/// <returns>列表项总数</returns>
int GetItemCount();
/// <summary>
/// 获取实际数据项数量(不包括循环或分组的虚拟数量)
/// </summary>
/// <returns>实际数据项数量</returns>
int GetRealCount();
/// <summary>
/// 获取指定索引位置的视图名称,用于视图类型区分
/// </summary>
/// <param name="index">列表项索引</param>
/// <returns>视图名称</returns>
string GetViewName(int index);
/// <summary>
/// 绑定视图持有者与数据
/// </summary>
/// <param name="viewHolder">视图持有者</param>
/// <param name="index">数据索引</param>
void OnBindViewHolder(ViewHolder viewHolder, int index);
/// <summary>
/// 通知数据已更改,触发视图刷新
/// </summary>
void NotifyDataChanged();
}
}

View File

@ -1,12 +1,23 @@
using System;
/// <summary>
/// 缓动函数工具类
/// 提供各种常用的缓动函数,用于实现平滑的动画效果
/// 基于 https://easings.net/ 的标准缓动函数
/// </summary>
public class EaseUtil
{
/// <summary>
/// 正弦缓入函数
/// </summary>
public static double EaseInSine(float x)
{
return 1 - Math.Cos(x * Math.PI / 2);
}
/// <summary>
/// 正弦缓出函数
/// </summary>
public static double EaseOutSine(float x)
{
return Math.Sin(x * Math.PI / 2);

View File

@ -2,10 +2,18 @@ using UnityEngine;
namespace AlicizaX.UI
{
/// <summary>
/// 布局管理器抽象基类
/// 负责计算和管理 RecyclerView 中列表项的位置、大小和可见性
/// 子类需要实现具体的布局算法(如线性、网格、圆形等)
/// </summary>
[System.Serializable]
public abstract class LayoutManager : ILayoutManager
{
protected Vector2 viewportSize;
/// <summary>
/// 获取视口大小(可见区域的尺寸)
/// </summary>
public Vector2 ViewportSize
{
get => viewportSize;
@ -13,6 +21,9 @@ namespace AlicizaX.UI
}
protected Vector2 contentSize;
/// <summary>
/// 获取内容总大小(所有列表项占据的总尺寸)
/// </summary>
public Vector2 ContentSize
{
get => contentSize;
@ -20,6 +31,9 @@ namespace AlicizaX.UI
}
protected Vector2 contentOffset;
/// <summary>
/// 获取内容偏移量(用于对齐计算)
/// </summary>
public Vector2 ContentOffset
{
get => contentOffset;
@ -27,6 +41,9 @@ namespace AlicizaX.UI
}
protected Vector2 viewportOffset;
/// <summary>
/// 获取视口偏移量(用于对齐计算)
/// </summary>
public Vector2 ViewportOffset
{
get => viewportOffset;
@ -34,6 +51,9 @@ namespace AlicizaX.UI
}
protected IAdapter adapter;
/// <summary>
/// 获取或设置数据适配器
/// </summary>
public IAdapter Adapter
{
get => adapter;
@ -41,6 +61,9 @@ namespace AlicizaX.UI
}
protected ViewProvider viewProvider;
/// <summary>
/// 获取或设置视图提供器
/// </summary>
public ViewProvider ViewProvider
{
get => viewProvider;
@ -48,6 +71,9 @@ namespace AlicizaX.UI
}
protected RecyclerView recyclerView;
/// <summary>
/// 获取或设置关联的 RecyclerView 实例
/// </summary>
public virtual RecyclerView RecyclerView
{
get => recyclerView;
@ -55,6 +81,9 @@ namespace AlicizaX.UI
}
protected Direction direction;
/// <summary>
/// 获取或设置滚动方向
/// </summary>
public Direction Direction
{
get => direction;
@ -62,6 +91,9 @@ namespace AlicizaX.UI
}
protected Alignment alignment;
/// <summary>
/// 获取或设置对齐方式
/// </summary>
public Alignment Alignment
{
get => alignment;
@ -69,6 +101,9 @@ namespace AlicizaX.UI
}
protected Vector2 spacing;
/// <summary>
/// 获取或设置列表项间距
/// </summary>
public Vector2 Spacing
{
get => spacing;
@ -76,6 +111,9 @@ namespace AlicizaX.UI
}
protected Vector2 padding;
/// <summary>
/// 获取或设置内边距
/// </summary>
public Vector2 Padding
{
get => padding;
@ -83,6 +121,9 @@ namespace AlicizaX.UI
}
protected int unit = 1;
/// <summary>
/// 获取或设置布局单元(用于网格布局等,表示一次处理多少个项)
/// </summary>
public int Unit
{
get => unit;
@ -90,10 +131,17 @@ namespace AlicizaX.UI
}
/// <summary>
/// 获取当前滚动位置
/// </summary>
public float ScrollPosition => recyclerView.GetScrollPosition();
public LayoutManager() { }
/// <summary>
/// 设置内容大小
/// 计算视口大小、内容大小以及各种偏移量
/// </summary>
public void SetContentSize()
{
viewportSize = recyclerView.GetComponent<RectTransform>().rect.size;
@ -102,6 +150,10 @@ namespace AlicizaX.UI
viewportOffset = CalculateViewportOffset();
}
/// <summary>
/// 更新所有可见 ViewHolder 的布局
/// 遍历所有当前显示的 ViewHolder 并重新计算其位置
/// </summary>
public void UpdateLayout()
{
foreach (var viewHolder in viewProvider.ViewHolders)
@ -110,6 +162,11 @@ namespace AlicizaX.UI
}
}
/// <summary>
/// 为指定的 ViewHolder 设置布局位置
/// </summary>
/// <param name="viewHolder">要布局的 ViewHolder</param>
/// <param name="index">ViewHolder 对应的数据索引</param>
public virtual void Layout(ViewHolder viewHolder, int index)
{
Vector2 pos = CalculatePosition(index);
@ -119,24 +176,67 @@ namespace AlicizaX.UI
viewHolder.RectTransform.anchoredPosition3D = position;
}
/// <summary>
/// 计算内容总大小(抽象方法,由子类实现)
/// </summary>
/// <returns>内容的总尺寸</returns>
public abstract Vector2 CalculateContentSize();
/// <summary>
/// 计算指定索引的 ViewHolder 位置(抽象方法,由子类实现)
/// </summary>
/// <param name="index">数据索引</param>
/// <returns>ViewHolder 的位置</returns>
public abstract Vector2 CalculatePosition(int index);
/// <summary>
/// 计算内容偏移量(抽象方法,由子类实现)
/// </summary>
/// <returns>内容偏移量</returns>
public abstract Vector2 CalculateContentOffset();
/// <summary>
/// 计算视口偏移量(抽象方法,由子类实现)
/// </summary>
/// <returns>视口偏移量</returns>
public abstract Vector2 CalculateViewportOffset();
/// <summary>
/// 获取当前可见区域的起始索引(抽象方法,由子类实现)
/// </summary>
/// <returns>起始索引</returns>
public abstract int GetStartIndex();
/// <summary>
/// 获取当前可见区域的结束索引(抽象方法,由子类实现)
/// </summary>
/// <returns>结束索引</returns>
public abstract int GetEndIndex();
/// <summary>
/// 将数据索引转换为滚动位置(抽象方法,由子类实现)
/// </summary>
/// <param name="index">数据索引</param>
/// <returns>对应的滚动位置</returns>
public abstract float IndexToPosition(int index);
/// <summary>
/// 将滚动位置转换为数据索引(抽象方法,由子类实现)
/// </summary>
/// <param name="position">滚动位置</param>
/// <returns>对应的数据索引</returns>
public abstract int PositionToIndex(float position);
/// <summary>
/// 执行列表项动画(虚方法,子类可选择性重写)
/// </summary>
public virtual void DoItemAnimation() { }
/// <summary>
/// 判断起始位置的 ViewHolder 是否完全可见
/// </summary>
/// <param name="index">数据索引</param>
/// <returns>如果完全可见返回 true否则返回 false</returns>
public virtual bool IsFullVisibleStart(int index)
{
Vector2 vector2 = CalculatePosition(index);
@ -144,6 +244,11 @@ namespace AlicizaX.UI
return position + GetOffset() >= 0;
}
/// <summary>
/// 判断起始位置的 ViewHolder 是否完全不可见
/// </summary>
/// <param name="index">数据索引</param>
/// <returns>如果完全不可见返回 true否则返回 false</returns>
public virtual bool IsFullInvisibleStart(int index)
{
Vector2 vector2 = CalculatePosition(index + unit);
@ -151,6 +256,11 @@ namespace AlicizaX.UI
return position + GetOffset() < 0;
}
/// <summary>
/// 判断结束位置的 ViewHolder 是否完全可见
/// </summary>
/// <param name="index">数据索引</param>
/// <returns>如果完全可见返回 true否则返回 false</returns>
public virtual bool IsFullVisibleEnd(int index)
{
Vector2 vector2 = CalculatePosition(index + unit);
@ -159,6 +269,11 @@ namespace AlicizaX.UI
return position + GetOffset() <= viewLength;
}
/// <summary>
/// 判断结束位置的 ViewHolder 是否完全不可见
/// </summary>
/// <param name="index">数据索引</param>
/// <returns>如果完全不可见返回 true否则返回 false</returns>
public virtual bool IsFullInvisibleEnd(int index)
{
Vector2 vector2 = CalculatePosition(index);
@ -167,6 +282,11 @@ namespace AlicizaX.UI
return position + GetOffset() > viewLength;
}
/// <summary>
/// 判断指定索引的 ViewHolder 是否可见(部分或完全)
/// </summary>
/// <param name="index">数据索引</param>
/// <returns>如果可见返回 true否则返回 false</returns>
public virtual bool IsVisible(int index)
{
float position, viewLength;
@ -189,6 +309,11 @@ namespace AlicizaX.UI
return false;
}
/// <summary>
/// 获取适配内容大小
/// 根据对齐方式计算实际显示的内容长度
/// </summary>
/// <returns>适配后的内容大小</returns>
protected virtual float GetFitContentSize()
{
float len;
@ -203,23 +328,40 @@ namespace AlicizaX.UI
return len;
}
/// <summary>
/// 获取偏移量
/// 计算内容偏移和视口偏移的组合值
/// </summary>
/// <returns>总偏移量</returns>
protected virtual float GetOffset()
{
return direction == Direction.Vertical ? -contentOffset.y + viewportOffset.y : -contentOffset.x + viewportOffset.x;
}
}
/// <summary>
/// 滚动方向枚举
/// </summary>
public enum Direction
{
/// <summary>垂直滚动</summary>
Vertical = 0,
/// <summary>水平滚动</summary>
Horizontal = 1,
/// <summary>自定义滚动</summary>
Custom = 2
}
/// <summary>
/// 对齐方式枚举
/// </summary>
public enum Alignment
{
/// <summary>左对齐</summary>
Left,
/// <summary>居中对齐</summary>
Center,
/// <summary>顶部对齐</summary>
Top
}
}

View File

@ -4,6 +4,11 @@ using UnityEngine.UI;
namespace AlicizaX.UI
{
/// <summary>
/// RecyclerView 核心组件,用于高效显示大量列表数据
/// 通过视图回收和复用机制,只渲染可见区域的项目,大幅提升性能
/// 支持垂直/水平滚动、网格布局、循环滚动等多种布局模式
/// </summary>
public class RecyclerView : MonoBehaviour
{
#region Serialized Fields - Layout Settings
@ -57,24 +62,36 @@ namespace AlicizaX.UI
#region Public Properties - Layout Settings
/// <summary>
/// 获取或设置列表的滚动方向(垂直、水平或自定义)
/// </summary>
public Direction Direction
{
get => direction;
set => direction = value;
}
/// <summary>
/// 获取或设置列表项的对齐方式(起始、居中或结束)
/// </summary>
public Alignment Alignment
{
get => alignment;
set => alignment = value;
}
/// <summary>
/// 获取或设置列表项之间的间距X轴和Y轴
/// </summary>
public Vector2 Spacing
{
get => spacing;
set => spacing = value;
}
/// <summary>
/// 获取或设置列表内容的内边距X轴和Y轴
/// </summary>
public Vector2 Padding
{
get => padding;
@ -85,6 +102,10 @@ namespace AlicizaX.UI
#region Public Properties - Scroll Settings
/// <summary>
/// 获取或设置是否启用滚动功能
/// 启用时会激活 Scroller 组件并显示滚动条(如果配置了)
/// </summary>
public bool Scroll
{
get => scroll;
@ -116,6 +137,11 @@ namespace AlicizaX.UI
}
}
/// <summary>
/// 获取或设置是否启用吸附功能
/// 启用时滚动停止后会自动对齐到最近的列表项
/// 注意:此功能依赖于 Scroll 属性,只有在滚动启用时才生效
/// </summary>
public bool Snap
{
get => snap;
@ -136,6 +162,10 @@ namespace AlicizaX.UI
}
}
/// <summary>
/// 获取或设置滚动速度范围1-50
/// 值越大,滚动响应越快
/// </summary>
public float ScrollSpeed
{
get => scrollSpeed;
@ -151,6 +181,10 @@ namespace AlicizaX.UI
}
}
/// <summary>
/// 获取或设置鼠标滚轮的滚动速度范围10-50
/// 值越大,滚轮滚动的距离越大
/// </summary>
public float WheelSpeed
{
get => wheelSpeed;
@ -171,12 +205,20 @@ namespace AlicizaX.UI
#region Public Properties - Components
/// <summary>
/// 获取或设置 ViewHolder 模板数组
/// 用于创建和复用列表项视图
/// </summary>
public ViewHolder[] Templates
{
get => templates;
set => templates = value;
}
/// <summary>
/// 获取内容容器的 RectTransform
/// 所有列表项都会作为此容器的子对象
/// </summary>
public RectTransform Content
{
get
@ -190,10 +232,21 @@ namespace AlicizaX.UI
}
}
/// <summary>
/// 获取滚动条组件
/// </summary>
public Scrollbar Scrollbar => scrollbar;
/// <summary>
/// 获取滚动控制器组件
/// </summary>
public Scroller Scroller => scroller;
/// <summary>
/// 获取视图提供器
/// 负责创建、回收和管理 ViewHolder 实例
/// 根据模板数量自动选择 SimpleViewProvider 或 MixedViewProvider
/// </summary>
public ViewProvider ViewProvider
{
get
@ -213,8 +266,15 @@ namespace AlicizaX.UI
#region Public Properties - State
/// <summary>
/// 获取或设置当前绑定的适配器
/// 适配器负责提供数据和创建 ViewHolder
/// </summary>
public IAdapter RecyclerViewAdapter { get; set; }
/// <summary>
/// 获取或设置当前显示的列表项索引
/// </summary>
public int CurrentIndex
{
get => currentIndex;
@ -225,7 +285,15 @@ namespace AlicizaX.UI
#region Events
/// <summary>
/// 当前索引改变时触发的事件
/// 参数为新的索引值
/// </summary>
public Action<int> OnIndexChanged;
/// <summary>
/// 滚动位置改变时触发的事件
/// </summary>
public Action OnScrollValueChanged;
#endregion
@ -287,6 +355,10 @@ namespace AlicizaX.UI
#region Public Methods - Setup
/// <summary>
/// 设置数据适配器并初始化布局管理器
/// </summary>
/// <param name="adapter">要绑定的适配器实例</param>
public void SetAdapter(IAdapter adapter)
{
if (adapter == null)
@ -308,6 +380,10 @@ namespace AlicizaX.UI
layoutManager.Padding = padding;
}
/// <summary>
/// 重置列表状态
/// 清空所有 ViewHolder 并将滚动位置重置为起始位置
/// </summary>
public void Reset()
{
viewProvider?.Reset();
@ -327,6 +403,10 @@ namespace AlicizaX.UI
#region Public Methods - Layout
/// <summary>
/// 刷新列表显示
/// 清空当前所有 ViewHolder 并根据当前滚动位置重新创建可见项
/// </summary>
public void Refresh()
{
ViewProvider.Clear();
@ -342,6 +422,10 @@ namespace AlicizaX.UI
layoutManager.DoItemAnimation();
}
/// <summary>
/// 请求重新布局
/// 重新计算内容大小、视口大小,并更新滚动条显示状态
/// </summary>
public void RequestLayout()
{
layoutManager.SetContentSize();
@ -359,11 +443,20 @@ namespace AlicizaX.UI
#region Public Methods - Scrolling
/// <summary>
/// 获取当前的滚动位置
/// </summary>
/// <returns>当前滚动位置值,如果没有 Scroller 则返回 0</returns>
public float GetScrollPosition()
{
return scroller != null ? scroller.Position : 0;
}
/// <summary>
/// 滚动到指定索引的列表项
/// </summary>
/// <param name="index">目标列表项的索引</param>
/// <param name="smooth">是否使用平滑滚动动画</param>
public void ScrollTo(int index, bool smooth = false)
{
if (!scroll || scroller == null) return;
@ -378,6 +471,14 @@ namespace AlicizaX.UI
UpdateCurrentIndex(index);
}
/// <summary>
/// 滚动到指定索引的列表项,并使用指定的对齐方式
/// </summary>
/// <param name="index">目标列表项的索引</param>
/// <param name="alignment">对齐方式(起始、居中或结束)</param>
/// <param name="offset">额外的偏移量</param>
/// <param name="smooth">是否使用平滑滚动动画</param>
/// <param name="duration">滚动动画持续时间(秒)</param>
public void ScrollToWithAlignment(int index, ScrollAlignment alignment, float offset = 0f, bool smooth = false, float duration = 0.3f)
{
if (!scroll || scroller == null) return;

View File

@ -1,22 +1,22 @@
namespace AlicizaX.UI
{
/// <summary>
/// Defines how an item should be aligned when scrolling to it
/// 定义滚动到列表项时的对齐方式
/// </summary>
public enum ScrollAlignment
{
/// <summary>
/// Align item to the top/left of the viewport
/// 将列表项对齐到视口的顶部/左侧
/// </summary>
Start,
/// <summary>
/// Align item to the center of the viewport
/// 将列表项对齐到视口的中心
/// </summary>
Center,
/// <summary>
/// Align item to the bottom/right of the viewport
/// 将列表项对齐到视口的底部/右侧
/// </summary>
End
}

View File

@ -2,14 +2,37 @@ using UnityEngine.Events;
namespace AlicizaX.UI
{
/// <summary>
/// 滚动控制器接口
/// 定义滚动行为的基本契约
/// </summary>
public interface IScroller
{
/// <summary>
/// 获取或设置当前滚动位置
/// </summary>
float Position { get; set; }
/// <summary>
/// 滚动到指定位置
/// </summary>
/// <param name="position">目标位置</param>
/// <param name="smooth">是否使用平滑滚动</param>
void ScrollTo(float position, bool smooth = false);
}
/// <summary>
/// 滚动位置改变事件
/// </summary>
public class ScrollerEvent : UnityEvent<float> { }
/// <summary>
/// 滚动停止事件
/// </summary>
public class MoveStopEvent : UnityEvent { }
/// <summary>
/// 拖拽状态改变事件
/// </summary>
public class DraggingEvent : UnityEvent<bool> { }
}

View File

@ -3,14 +3,29 @@ using System.Collections.Generic;
namespace AlicizaX.UI
{
/// <summary>
/// UGList 基类
/// 提供简化的列表操作接口,封装 RecyclerView 和 Adapter 的交互
/// </summary>
/// <typeparam name="TData">数据类型</typeparam>
/// <typeparam name="TAdapter">适配器类型</typeparam>
public abstract class UGListBase<TData, TAdapter> where TAdapter : Adapter<TData> where TData : ISimpleViewData
{
protected readonly RecyclerView _recyclerView;
protected readonly TAdapter _adapter;
/// <summary>
/// 获取关联的 RecyclerView 实例
/// </summary>
public RecyclerView RecyclerView => _recyclerView;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="recyclerView">RecyclerView 实例</param>
/// <param name="adapter">适配器实例</param>
/// <param name="onItemClick">列表项点击回调</param>
public UGListBase(RecyclerView recyclerView, TAdapter adapter, Action<TData> onItemClick = null)
{
_recyclerView = recyclerView;
@ -27,10 +42,17 @@ namespace AlicizaX.UI
}
}
/// <summary>
/// 获取适配器实例
/// </summary>
public TAdapter Adapter => _adapter;
private List<TData> _datas;
/// <summary>
/// 获取或设置数据列表
/// 设置时会自动更新适配器
/// </summary>
public List<TData> Data
{
get => _datas;
@ -42,49 +64,106 @@ namespace AlicizaX.UI
}
}
/// <summary>
/// 通用列表类
/// 用于显示简单的单一类型数据列表
/// </summary>
/// <typeparam name="TData">数据类型,必须实现 ISimpleViewData</typeparam>
public class UGList<TData> : UGListBase<TData, Adapter<TData>> where TData : ISimpleViewData
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="recyclerView">RecyclerView 实例</param>
/// <param name="onItemClick">列表项点击回调</param>
public UGList(RecyclerView recyclerView, Action<TData> onItemClick = null)
: base(recyclerView, new Adapter<TData>(recyclerView), onItemClick)
{
}
}
/// <summary>
/// 分组列表类
/// 用于显示带有分组头的列表数据
/// </summary>
/// <typeparam name="TData">数据类型,必须实现 IGroupViewData</typeparam>
public class UGGroupList<TData> : UGListBase<TData, GroupAdapter<TData>> where TData : class, IGroupViewData, new()
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="recyclerView">RecyclerView 实例</param>
/// <param name="groupViewName">分组头视图名称</param>
/// <param name="onItemClick">列表项点击回调</param>
public UGGroupList(RecyclerView recyclerView, string groupViewName, Action<TData> onItemClick = null)
: base(recyclerView, new GroupAdapter<TData>(recyclerView, groupViewName), onItemClick)
{
}
}
/// <summary>
/// 循环列表类
/// 用于实现无限循环滚动的列表
/// </summary>
/// <typeparam name="TData">数据类型,必须实现 ISimpleViewData</typeparam>
public class UGLoopList<TData> : UGListBase<TData, LoopAdapter<TData>> where TData : ISimpleViewData, new()
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="recyclerView">RecyclerView 实例</param>
/// <param name="onItemClick">列表项点击回调</param>
public UGLoopList(RecyclerView recyclerView, Action<TData> onItemClick = null)
: base(recyclerView, new LoopAdapter<TData>(recyclerView), onItemClick)
{
}
}
/// <summary>
/// 混合列表类
/// 用于显示多种不同类型的列表项
/// </summary>
/// <typeparam name="TData">数据类型,必须实现 IMixedViewData</typeparam>
public class UGMixedList<TData> : UGListBase<TData, MixedAdapter<TData>> where TData : IMixedViewData
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="recyclerView">RecyclerView 实例</param>
/// <param name="onItemClick">列表项点击回调</param>
public UGMixedList(RecyclerView recyclerView, Action<TData> onItemClick = null)
: base(recyclerView, new MixedAdapter<TData>(recyclerView), onItemClick)
{
}
}
/// <summary>
/// UGList 创建辅助类
/// 提供便捷的静态方法来创建各种类型的列表
/// </summary>
public static class UGListCreateHelper
{
/// <summary>
/// 创建通用列表
/// </summary>
public static UGList<TData> Create<TData>(RecyclerView recyclerView, Action<TData> onItemClick = null) where TData : ISimpleViewData
=> new UGList<TData>(recyclerView, onItemClick);
/// <summary>
/// 创建分组列表
/// </summary>
public static UGGroupList<TData> CreateGroup<TData>(RecyclerView recyclerView, string groupViewName, Action<TData> onItemClick = null) where TData : class, IGroupViewData, new()
=> new UGGroupList<TData>(recyclerView, groupViewName, onItemClick);
/// <summary>
/// 创建循环列表
/// </summary>
public static UGLoopList<TData> CreateLoop<TData>(RecyclerView recyclerView, Action<TData> onItemClick = null) where TData : ISimpleViewData, new()
=> new UGLoopList<TData>(recyclerView, onItemClick);
/// <summary>
/// 创建混合列表
/// </summary>
public static UGMixedList<TData> CreateMixed<TData>(RecyclerView recyclerView, Action<TData> onItemClick = null) where TData : IMixedViewData
=> new UGMixedList<TData>(recyclerView, onItemClick);
}

View File

@ -4,24 +4,25 @@ using UnityEngine;
namespace AlicizaX.UI
{
/// <summary>
/// Extension methods for UGList to provide enhanced scrolling functionality
/// UGList 扩展方法类
/// 提供增强的滚动功能
/// </summary>
public static class UGListExtensions
{
/// <summary>
/// Enable debug logging for ScrollTo operations
/// 启用 ScrollTo 操作的调试日志
/// </summary>
public static bool DebugScrollTo { get; set; } = false;
/// <summary>
/// Scrolls to a specific item with alignment and animation options
/// 滚动到指定的列表项,支持对齐方式和动画选项
/// </summary>
/// <param name="ugList">The UGList instance</param>
/// <param name="index">The index of the item to scroll to</param>
/// <param name="alignment">How to align the item in the viewport (Start, Center, or End)</param>
/// <param name="offset">Additional offset in pixels to apply after alignment</param>
/// <param name="smooth">Whether to animate the scroll</param>
/// <param name="duration">Animation duration in seconds (only used when smooth is true)</param>
/// <param name="ugList">UGList 实例</param>
/// <param name="index">要滚动到的列表项索引</param>
/// <param name="alignment">列表项在视口中的对齐方式(起始、居中或结束)</param>
/// <param name="offset">对齐后额外应用的偏移量(像素)</param>
/// <param name="smooth">是否使用动画滚动</param>
/// <param name="duration">动画持续时间(秒),仅在 smooth 为 true 时使用</param>
public static void ScrollTo<TData, TAdapter>(
this UGListBase<TData, TAdapter> ugList,
int index,
@ -47,7 +48,7 @@ namespace AlicizaX.UI
}
/// <summary>
/// Scrolls to a specific item and aligns it at the start (top/left) of the viewport
/// 滚动到指定的列表项并将其对齐到视口的起始位置(顶部/左侧)
/// </summary>
public static void ScrollToStart<TData, TAdapter>(
this UGListBase<TData, TAdapter> ugList,
@ -62,7 +63,7 @@ namespace AlicizaX.UI
}
/// <summary>
/// Scrolls to a specific item and aligns it at the center of the viewport
/// 滚动到指定的列表项并将其对齐到视口的中心位置
/// </summary>
public static void ScrollToCenter<TData, TAdapter>(
this UGListBase<TData, TAdapter> ugList,
@ -77,7 +78,7 @@ namespace AlicizaX.UI
}
/// <summary>
/// Scrolls to a specific item and aligns it at the end (bottom/right) of the viewport
/// 滚动到指定的列表项并将其对齐到视口的结束位置(底部/右侧)
/// </summary>
public static void ScrollToEnd<TData, TAdapter>(
this UGListBase<TData, TAdapter> ugList,

View File

@ -5,10 +5,16 @@ using UnityEngine.UI;
namespace AlicizaX.UI
{
/// <summary>
/// 视图持有者基类,用于缓存和复用列表项视图
/// </summary>
public abstract class ViewHolder : MonoBehaviour
{
private RectTransform rectTransform;
/// <summary>
/// 获取 RectTransform 组件
/// </summary>
public RectTransform RectTransform
{
get
@ -23,24 +29,55 @@ namespace AlicizaX.UI
private set { rectTransform = value; }
}
/// <summary>
/// 视图名称,用于区分不同类型的视图
/// </summary>
public string Name { get; internal set; }
/// <summary>
/// 当前绑定的数据索引
/// </summary>
public int Index { get; internal set; }
/// <summary>
/// 选中状态
/// </summary>
public bool ChoiseState { private set; get; }
/// <summary>
/// 获取视图的尺寸
/// </summary>
public Vector2 SizeDelta => RectTransform.sizeDelta;
private IButton _button;
/// <summary>
/// 视图首次创建时调用
/// </summary>
protected internal virtual void OnStart()
{
}
/// <summary>
/// 视图被回收到对象池时调用
/// </summary>
protected internal virtual void OnRecycled()
{
}
/// <summary>
/// 绑定视图数据(抽象方法,子类必须实现)
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="data">要绑定的数据</param>
public abstract void BindViewData<T>(T data);
/// <summary>
/// 绑定列表项点击事件
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
/// <param name="data">数据对象</param>
/// <param name="action">点击回调</param>
protected internal virtual void BindItemClick<T>(T data, Action<T> action)
{
if (_button is null && !TryGetComponent(out _button))
@ -53,6 +90,10 @@ namespace AlicizaX.UI
_button.onClick.AddListener(() => action?.Invoke(data));
}
/// <summary>
/// 绑定选中状态
/// </summary>
/// <param name="state">是否选中</param>
protected internal void BindChoiceState(bool state)
{
if (ChoiseState != state)
@ -62,6 +103,10 @@ namespace AlicizaX.UI
}
}
/// <summary>
/// 选中状态改变时的回调(可在子类中重写)
/// </summary>
/// <param name="state">是否选中</param>
protected internal virtual void OnBindChoiceState(bool state)
{
}

View File

@ -5,35 +5,78 @@ namespace AlicizaX.UI
{
/// <summary>
/// 提供和管理 ViewHolder
/// 负责 ViewHolder 的创建、回收和复用
/// </summary>
public abstract class ViewProvider
{
private readonly List<ViewHolder> viewHolders = new();
/// <summary>
/// 获取或设置数据适配器
/// </summary>
public IAdapter Adapter { get; set; }
/// <summary>
/// 获取或设置布局管理器
/// </summary>
public LayoutManager LayoutManager { get; set; }
/// <summary>
/// 获取当前所有活动的 ViewHolder 列表
/// </summary>
public List<ViewHolder> ViewHolders => viewHolders;
protected RecyclerView recyclerView;
protected ViewHolder[] templates;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="recyclerView">关联的 RecyclerView 实例</param>
/// <param name="templates">ViewHolder 模板数组</param>
public ViewProvider(RecyclerView recyclerView, ViewHolder[] templates)
{
this.recyclerView = recyclerView;
this.templates = templates;
}
/// <summary>
/// 根据视图名称获取对应的模板(抽象方法,由子类实现)
/// </summary>
/// <param name="viewName">视图名称</param>
/// <returns>对应的 ViewHolder 模板</returns>
public abstract ViewHolder GetTemplate(string viewName);
/// <summary>
/// 获取所有模板(抽象方法,由子类实现)
/// </summary>
/// <returns>所有 ViewHolder 模板数组</returns>
public abstract ViewHolder[] GetTemplates();
/// <summary>
/// 从对象池中分配一个 ViewHolder抽象方法由子类实现
/// </summary>
/// <param name="viewName">视图名称</param>
/// <returns>分配的 ViewHolder 实例</returns>
public abstract ViewHolder Allocate(string viewName);
/// <summary>
/// 将 ViewHolder 回收到对象池(抽象方法,由子类实现)
/// </summary>
/// <param name="viewName">视图名称</param>
/// <param name="viewHolder">要回收的 ViewHolder</param>
public abstract void Free(string viewName, ViewHolder viewHolder);
/// <summary>
/// 重置 ViewProvider 状态(抽象方法,由子类实现)
/// </summary>
public abstract void Reset();
/// <summary>
/// 创建指定索引的 ViewHolder
/// 从对象池中获取或创建新的 ViewHolder并进行布局和数据绑定
/// </summary>
/// <param name="index">数据索引</param>
public void CreateViewHolder(int index)
{
for (int i = index; i < index + LayoutManager.Unit; i++)
@ -51,6 +94,11 @@ namespace AlicizaX.UI
}
}
/// <summary>
/// 移除指定索引的 ViewHolder
/// 将 ViewHolder 从活动列表中移除并回收到对象池
/// </summary>
/// <param name="index">数据索引</param>
public void RemoveViewHolder(int index)
{
for (int i = index; i < index + LayoutManager.Unit; i++)
@ -104,6 +152,10 @@ namespace AlicizaX.UI
return -1;
}
/// <summary>
/// 清空所有 ViewHolder
/// 将所有活动的 ViewHolder 回收到对象池并清空列表
/// </summary>
public void Clear()
{
foreach (var viewHolder in viewHolders)
@ -125,6 +177,10 @@ namespace AlicizaX.UI
return size;
}
/// <summary>
/// 获取数据项总数
/// </summary>
/// <returns>数据项总数,如果没有适配器则返回 0</returns>
public int GetItemCount()
{
return Adapter == null ? 0 : Adapter.GetItemCount();