com.alicizax.unity.ui.exten.../Runtime/RecyclerView/Layout/MixedLayoutManager.cs

271 lines
7.9 KiB
C#
Raw Normal View History

2025-12-26 14:22:46 +08:00
using System;
2026-04-15 14:51:57 +08:00
using System.Buffers;
2025-03-12 20:59:12 +08:00
using UnityEngine;
namespace AlicizaX.UI
2025-03-12 20:59:12 +08:00
{
2025-12-26 14:22:46 +08:00
[Serializable]
2025-03-12 20:59:12 +08:00
public class MixedLayoutManager : LayoutManager
{
private float[] itemLengths = Array.Empty<float>();
private float[] itemPositions = Array.Empty<float>();
2026-04-15 14:51:57 +08:00
private int rentedArraySize = 0;
private Vector2 firstItemSize = Vector2.zero;
private int cachedItemCount = -1;
private bool positionCacheDirty = true;
2025-03-12 20:59:12 +08:00
public MixedLayoutManager() { }
2026-04-15 14:51:57 +08:00
~MixedLayoutManager()
{
ReturnRentedArrays();
}
private void ReturnRentedArrays()
{
if (rentedArraySize > 0)
{
ArrayPool<float>.Shared.Return(itemLengths);
ArrayPool<float>.Shared.Return(itemPositions);
itemLengths = Array.Empty<float>();
itemPositions = Array.Empty<float>();
rentedArraySize = 0;
}
}
2025-03-12 20:59:12 +08:00
public override Vector2 CalculateContentSize()
{
positionCacheDirty = true;
EnsurePositionCache();
2025-03-12 20:59:12 +08:00
float totalLength = cachedItemCount > 0
? itemPositions[cachedItemCount - 1] + itemLengths[cachedItemCount - 1]
: 0f;
float paddingLength = direction == Direction.Vertical ? padding.y * 2 : padding.x * 2;
return direction == Direction.Vertical
? new Vector2(contentSize.x, cachedItemCount > 0 ? totalLength + paddingLength : paddingLength)
: new Vector2(cachedItemCount > 0 ? totalLength + paddingLength : paddingLength, contentSize.y);
2025-03-12 20:59:12 +08:00
}
public override Vector2 CalculatePosition(int index)
{
EnsurePositionCache();
float position = GetItemPosition(index) - ScrollPosition;
return direction == Direction.Vertical
? new Vector2(0, position + padding.y)
: new Vector2(position + padding.x, 0);
2025-03-12 20:59:12 +08:00
}
public override Vector2 CalculateContentOffset()
{
EnsurePositionCache();
if (cachedItemCount <= 0)
{
return Vector2.zero;
}
2025-03-12 20:59:12 +08:00
float len = GetFitContentSize();
if (direction == Direction.Vertical)
{
return new Vector2(0, (len - firstItemSize.y) / 2);
2025-03-12 20:59:12 +08:00
}
return new Vector2((len - firstItemSize.x) / 2, 0);
2025-03-12 20:59:12 +08:00
}
public override Vector2 CalculateViewportOffset()
{
EnsurePositionCache();
if (cachedItemCount <= 0)
{
return Vector2.zero;
}
2025-03-12 20:59:12 +08:00
if (direction == Direction.Vertical)
{
return new Vector2(0, (viewportSize.y - firstItemSize.y) / 2);
2025-03-12 20:59:12 +08:00
}
return new Vector2((viewportSize.x - firstItemSize.x) / 2, 0);
2025-03-12 20:59:12 +08:00
}
public override int GetStartIndex()
{
EnsurePositionCache();
if (cachedItemCount <= 0)
2025-03-12 20:59:12 +08:00
{
return 0;
2025-03-12 20:59:12 +08:00
}
int index = FindFirstItemEndingAfter(ScrollPosition);
return index >= 0 ? index : 0;
2025-03-12 20:59:12 +08:00
}
public override int GetEndIndex()
{
EnsurePositionCache();
if (cachedItemCount <= 0)
{
return -1;
}
2025-03-12 20:59:12 +08:00
float viewLength = direction == Direction.Vertical ? viewportSize.y : viewportSize.x;
int index = FindFirstItemEndingAfter(ScrollPosition + viewLength);
return index >= 0 ? Mathf.Min(index, cachedItemCount - 1) : cachedItemCount - 1;
}
public override float IndexToPosition(int index)
{
EnsurePositionCache();
if (cachedItemCount <= 0)
2025-03-12 20:59:12 +08:00
{
return 0f;
}
2025-03-12 20:59:12 +08:00
float position = GetItemPosition(index);
if (direction == Direction.Vertical)
{
return Mathf.Clamp(position, 0f, Mathf.Max(contentSize.y - viewportSize.y, 0f));
2025-03-12 20:59:12 +08:00
}
return Mathf.Clamp(position, 0f, Mathf.Max(contentSize.x - viewportSize.x, 0f));
2025-03-12 20:59:12 +08:00
}
public override int PositionToIndex(float position)
2025-03-12 20:59:12 +08:00
{
EnsurePositionCache();
if (cachedItemCount <= 0)
2025-03-12 20:59:12 +08:00
{
return 0;
2025-03-12 20:59:12 +08:00
}
int index = FindFirstItemEndingAtOrAfter(position);
return index >= 0 ? index : cachedItemCount - 1;
2025-03-12 20:59:12 +08:00
}
private void EnsurePositionCache()
2025-03-12 20:59:12 +08:00
{
int itemCount = adapter != null ? adapter.GetItemCount() : 0;
if (itemCount < 0)
2025-03-12 20:59:12 +08:00
{
itemCount = 0;
2025-03-12 20:59:12 +08:00
}
if (!positionCacheDirty && cachedItemCount == itemCount)
2025-03-12 20:59:12 +08:00
{
return;
2025-03-12 20:59:12 +08:00
}
RebuildPositionCache(itemCount);
2025-03-12 20:59:12 +08:00
}
private void RebuildPositionCache(int itemCount)
2025-03-12 20:59:12 +08:00
{
2026-04-15 14:51:57 +08:00
// ArrayPool.Rent 返回的数组长度 >= 请求长度rentedArraySize 记录实际容量。
// 仅当 itemCount 超出当前容量时才归还并重新租用,缩容时复用原数组。
if (itemCount > rentedArraySize)
{
2026-04-15 14:51:57 +08:00
ReturnRentedArrays();
if (itemCount > 0)
{
itemLengths = ArrayPool<float>.Shared.Rent(itemCount);
itemPositions = ArrayPool<float>.Shared.Rent(itemCount);
rentedArraySize = itemLengths.Length;
}
}
2025-03-12 20:59:12 +08:00
firstItemSize = itemCount > 0 ? viewProvider.CalculateViewSize(0) : Vector2.zero;
float position = 0f;
2025-03-12 20:59:12 +08:00
for (int i = 0; i < itemCount; i++)
{
itemPositions[i] = position;
itemLengths[i] = GetLength(i, itemCount);
position += itemLengths[i];
}
cachedItemCount = itemCount;
positionCacheDirty = false;
}
private float GetItemPosition(int index)
{
if (index <= 0 || cachedItemCount <= 0)
{
return 0f;
}
if (index >= cachedItemCount)
{
return itemPositions[cachedItemCount - 1] + itemLengths[cachedItemCount - 1];
}
return itemPositions[index];
}
private int FindFirstItemEndingAfter(float position)
{
int low = 0;
int high = cachedItemCount - 1;
int result = -1;
while (low <= high)
{
int mid = low + ((high - low) / 2);
if (GetItemEndPosition(mid) > position)
{
result = mid;
high = mid - 1;
}
else
{
low = mid + 1;
}
}
2025-03-12 20:59:12 +08:00
return result;
}
private int FindFirstItemEndingAtOrAfter(float position)
{
int low = 0;
int high = cachedItemCount - 1;
int result = -1;
while (low <= high)
{
int mid = low + ((high - low) / 2);
if (GetItemEndPosition(mid) >= position)
2025-03-12 20:59:12 +08:00
{
result = mid;
high = mid - 1;
2025-03-12 20:59:12 +08:00
}
else
{
low = mid + 1;
}
}
return result;
}
private float GetItemEndPosition(int index)
{
return itemPositions[index] + itemLengths[index];
}
private float GetLength(int index, int itemCount)
{
Vector2 size = viewProvider.CalculateViewSize(index);
if (index < itemCount - 1)
{
size += spacing;
2025-03-12 20:59:12 +08:00
}
return direction == Direction.Vertical ? size.y : size.x;
2025-03-12 20:59:12 +08:00
}
}
}