com.alicizax.unity.ui.exten.../Runtime/RecyclerView/Layout/LayoutManager.cs
2026-03-11 14:18:07 +08:00

368 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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