368 lines
12 KiB
C#
368 lines
12 KiB
C#
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;
|
||
private set => viewportSize = value;
|
||
}
|
||
|
||
protected Vector2 contentSize;
|
||
/// <summary>
|
||
/// 获取内容总大小(所有列表项占据的总尺寸)
|
||
/// </summary>
|
||
public Vector2 ContentSize
|
||
{
|
||
get => contentSize;
|
||
private set => contentSize = value;
|
||
}
|
||
|
||
protected Vector2 contentOffset;
|
||
/// <summary>
|
||
/// 获取内容偏移量(用于对齐计算)
|
||
/// </summary>
|
||
public Vector2 ContentOffset
|
||
{
|
||
get => contentOffset;
|
||
private set => contentOffset = value;
|
||
}
|
||
|
||
protected Vector2 viewportOffset;
|
||
/// <summary>
|
||
/// 获取视口偏移量(用于对齐计算)
|
||
/// </summary>
|
||
public Vector2 ViewportOffset
|
||
{
|
||
get => viewportOffset;
|
||
private set => viewportOffset = value;
|
||
}
|
||
|
||
protected IAdapter adapter;
|
||
/// <summary>
|
||
/// 获取或设置数据适配器
|
||
/// </summary>
|
||
public IAdapter Adapter
|
||
{
|
||
get => adapter;
|
||
set => adapter = value;
|
||
}
|
||
|
||
protected ViewProvider viewProvider;
|
||
/// <summary>
|
||
/// 获取或设置视图提供器
|
||
/// </summary>
|
||
public ViewProvider ViewProvider
|
||
{
|
||
get => viewProvider;
|
||
set => viewProvider = value;
|
||
}
|
||
|
||
protected RecyclerView recyclerView;
|
||
/// <summary>
|
||
/// 获取或设置关联的 RecyclerView 实例
|
||
/// </summary>
|
||
public virtual RecyclerView RecyclerView
|
||
{
|
||
get => recyclerView;
|
||
set => recyclerView = value;
|
||
}
|
||
|
||
protected Direction direction;
|
||
/// <summary>
|
||
/// 获取或设置滚动方向
|
||
/// </summary>
|
||
public Direction Direction
|
||
{
|
||
get => direction;
|
||
set => direction = value;
|
||
}
|
||
|
||
protected Alignment alignment;
|
||
/// <summary>
|
||
/// 获取或设置对齐方式
|
||
/// </summary>
|
||
public Alignment Alignment
|
||
{
|
||
get => alignment;
|
||
set => alignment = value;
|
||
}
|
||
|
||
protected Vector2 spacing;
|
||
/// <summary>
|
||
/// 获取或设置列表项间距
|
||
/// </summary>
|
||
public Vector2 Spacing
|
||
{
|
||
get => spacing;
|
||
set => spacing = value;
|
||
}
|
||
|
||
protected Vector2 padding;
|
||
/// <summary>
|
||
/// 获取或设置内边距
|
||
/// </summary>
|
||
public Vector2 Padding
|
||
{
|
||
get => padding;
|
||
set => padding = value;
|
||
}
|
||
|
||
protected int unit = 1;
|
||
/// <summary>
|
||
/// 获取或设置布局单元(用于网格布局等,表示一次处理多少个项)
|
||
/// </summary>
|
||
public int Unit
|
||
{
|
||
get => unit;
|
||
set => unit = value;
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 获取当前滚动位置
|
||
/// </summary>
|
||
public float ScrollPosition => recyclerView.GetScrollPosition();
|
||
|
||
public LayoutManager() { }
|
||
|
||
/// <summary>
|
||
/// 设置内容大小
|
||
/// 计算视口大小、内容大小以及各种偏移量
|
||
/// </summary>
|
||
public void SetContentSize()
|
||
{
|
||
viewportSize = recyclerView.GetComponent<RectTransform>().rect.size;
|
||
contentSize = CalculateContentSize();
|
||
contentOffset = CalculateContentOffset();
|
||
viewportOffset = CalculateViewportOffset();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新所有可见 ViewHolder 的布局
|
||
/// 遍历所有当前显示的 ViewHolder 并重新计算其位置
|
||
/// </summary>
|
||
public void UpdateLayout()
|
||
{
|
||
foreach (var viewHolder in viewProvider.ViewHolders)
|
||
{
|
||
Layout(viewHolder, viewHolder.Index);
|
||
}
|
||
}
|
||
|
||
/// <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);
|
||
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;
|
||
}
|
||
|
||
/// <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);
|
||
float position = direction == Direction.Vertical ? vector2.y : vector2.x;
|
||
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);
|
||
float position = direction == Direction.Vertical ? vector2.y : vector2.x;
|
||
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);
|
||
float position = direction == Direction.Vertical ? vector2.y : vector2.x;
|
||
float viewLength = direction == Direction.Vertical ? viewportSize.y : viewportSize.x;
|
||
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);
|
||
float position = direction == Direction.Vertical ? vector2.y : vector2.x;
|
||
float viewLength = direction == Direction.Vertical ? viewportSize.y : viewportSize.x;
|
||
return position + GetOffset() > viewLength;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 判断指定索引的 ViewHolder 是否可见(部分或完全)
|
||
/// </summary>
|
||
/// <param name="index">数据索引</param>
|
||
/// <returns>如果可见返回 true,否则返回 false</returns>
|
||
public virtual bool IsVisible(int index)
|
||
{
|
||
float position, viewLength;
|
||
viewLength = direction == Direction.Vertical ? viewportSize.y : viewportSize.x;
|
||
|
||
Vector2 vector2 = CalculatePosition(index);
|
||
position = direction == Direction.Vertical ? vector2.y : vector2.x;
|
||
if (position + GetOffset() > 0 && position + GetOffset() <= viewLength)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
vector2 = CalculatePosition(index + unit);
|
||
position = direction == Direction.Vertical ? vector2.y : vector2.x;
|
||
if (position + GetOffset() > 0 && position + GetOffset() <= viewLength)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取适配内容大小
|
||
/// 根据对齐方式计算实际显示的内容长度
|
||
/// </summary>
|
||
/// <returns>适配后的内容大小</returns>
|
||
protected virtual float GetFitContentSize()
|
||
{
|
||
float len;
|
||
if (direction == Direction.Vertical)
|
||
{
|
||
len = alignment == Alignment.Center ? Mathf.Min(contentSize.y, viewportSize.y) : viewportSize.y;
|
||
}
|
||
else
|
||
{
|
||
len = alignment == Alignment.Center ? Mathf.Min(contentSize.x, viewportSize.x) : viewportSize.x;
|
||
}
|
||
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
|
||
}
|
||
}
|