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;
|
|
|
|
|
|
|
2025-11-20 15:40:38 +08:00
|
|
|
|
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
|
|
|
|
|
|
{
|
2026-03-31 15:18:50 +08:00
|
|
|
|
private float[] itemLengths = Array.Empty<float>();
|
|
|
|
|
|
private float[] itemPositions = Array.Empty<float>();
|
2026-04-15 14:51:57 +08:00
|
|
|
|
private int rentedArraySize = 0;
|
2026-03-31 15:18:50 +08:00
|
|
|
|
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()
|
|
|
|
|
|
{
|
2026-03-31 15:18:50 +08:00
|
|
|
|
positionCacheDirty = true;
|
|
|
|
|
|
EnsurePositionCache();
|
2025-03-12 20:59:12 +08:00
|
|
|
|
|
2026-03-31 15:18:50 +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)
|
|
|
|
|
|
{
|
2026-03-31 15:18:50 +08:00
|
|
|
|
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()
|
|
|
|
|
|
{
|
2026-03-31 15:18:50 +08:00
|
|
|
|
EnsurePositionCache();
|
|
|
|
|
|
if (cachedItemCount <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
return Vector2.zero;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-12 20:59:12 +08:00
|
|
|
|
float len = GetFitContentSize();
|
|
|
|
|
|
if (direction == Direction.Vertical)
|
|
|
|
|
|
{
|
2026-03-31 15:18:50 +08:00
|
|
|
|
return new Vector2(0, (len - firstItemSize.y) / 2);
|
2025-03-12 20:59:12 +08:00
|
|
|
|
}
|
2026-03-31 15:18:50 +08:00
|
|
|
|
|
|
|
|
|
|
return new Vector2((len - firstItemSize.x) / 2, 0);
|
2025-03-12 20:59:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override Vector2 CalculateViewportOffset()
|
|
|
|
|
|
{
|
2026-03-31 15:18:50 +08:00
|
|
|
|
EnsurePositionCache();
|
|
|
|
|
|
if (cachedItemCount <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
return Vector2.zero;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-12 20:59:12 +08:00
|
|
|
|
if (direction == Direction.Vertical)
|
|
|
|
|
|
{
|
2026-03-31 15:18:50 +08:00
|
|
|
|
return new Vector2(0, (viewportSize.y - firstItemSize.y) / 2);
|
2025-03-12 20:59:12 +08:00
|
|
|
|
}
|
2026-03-31 15:18:50 +08:00
|
|
|
|
|
|
|
|
|
|
return new Vector2((viewportSize.x - firstItemSize.x) / 2, 0);
|
2025-03-12 20:59:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override int GetStartIndex()
|
|
|
|
|
|
{
|
2026-03-31 15:18:50 +08:00
|
|
|
|
EnsurePositionCache();
|
|
|
|
|
|
if (cachedItemCount <= 0)
|
2025-03-12 20:59:12 +08:00
|
|
|
|
{
|
2026-03-31 15:18:50 +08:00
|
|
|
|
return 0;
|
2025-03-12 20:59:12 +08:00
|
|
|
|
}
|
2026-03-31 15:18:50 +08:00
|
|
|
|
|
|
|
|
|
|
int index = FindFirstItemEndingAfter(ScrollPosition);
|
|
|
|
|
|
return index >= 0 ? index : 0;
|
2025-03-12 20:59:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override int GetEndIndex()
|
|
|
|
|
|
{
|
2026-03-31 15:18:50 +08:00
|
|
|
|
EnsurePositionCache();
|
|
|
|
|
|
if (cachedItemCount <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-12 20:59:12 +08:00
|
|
|
|
float viewLength = direction == Direction.Vertical ? viewportSize.y : viewportSize.x;
|
2026-03-31 15:18:50 +08:00
|
|
|
|
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
|
|
|
|
{
|
2026-03-31 15:18:50 +08:00
|
|
|
|
return 0f;
|
|
|
|
|
|
}
|
2025-03-12 20:59:12 +08:00
|
|
|
|
|
2026-03-31 15:18:50 +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
|
|
|
|
}
|
2026-03-31 15:18:50 +08:00
|
|
|
|
|
|
|
|
|
|
return Mathf.Clamp(position, 0f, Mathf.Max(contentSize.x - viewportSize.x, 0f));
|
2025-03-12 20:59:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-31 15:18:50 +08:00
|
|
|
|
public override int PositionToIndex(float position)
|
2025-03-12 20:59:12 +08:00
|
|
|
|
{
|
2026-03-31 15:18:50 +08:00
|
|
|
|
EnsurePositionCache();
|
|
|
|
|
|
if (cachedItemCount <= 0)
|
2025-03-12 20:59:12 +08:00
|
|
|
|
{
|
2026-03-31 15:18:50 +08:00
|
|
|
|
return 0;
|
2025-03-12 20:59:12 +08:00
|
|
|
|
}
|
2026-03-31 15:18:50 +08:00
|
|
|
|
|
|
|
|
|
|
int index = FindFirstItemEndingAtOrAfter(position);
|
|
|
|
|
|
return index >= 0 ? index : cachedItemCount - 1;
|
2025-03-12 20:59:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-31 15:18:50 +08:00
|
|
|
|
private void EnsurePositionCache()
|
2025-03-12 20:59:12 +08:00
|
|
|
|
{
|
2026-03-31 15:18:50 +08:00
|
|
|
|
int itemCount = adapter != null ? adapter.GetItemCount() : 0;
|
|
|
|
|
|
if (itemCount < 0)
|
2025-03-12 20:59:12 +08:00
|
|
|
|
{
|
2026-03-31 15:18:50 +08:00
|
|
|
|
itemCount = 0;
|
2025-03-12 20:59:12 +08:00
|
|
|
|
}
|
2026-03-31 15:18:50 +08:00
|
|
|
|
|
|
|
|
|
|
if (!positionCacheDirty && cachedItemCount == itemCount)
|
2025-03-12 20:59:12 +08:00
|
|
|
|
{
|
2026-03-31 15:18:50 +08:00
|
|
|
|
return;
|
2025-03-12 20:59:12 +08:00
|
|
|
|
}
|
2026-03-31 15:18:50 +08:00
|
|
|
|
|
|
|
|
|
|
RebuildPositionCache(itemCount);
|
2025-03-12 20:59:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-31 15:18:50 +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-03-31 15:18:50 +08:00
|
|
|
|
{
|
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;
|
|
|
|
|
|
}
|
2026-03-31 15:18:50 +08:00
|
|
|
|
}
|
2025-03-12 20:59:12 +08:00
|
|
|
|
|
2026-03-31 15:18:50 +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++)
|
|
|
|
|
|
{
|
2026-03-31 15:18:50 +08:00
|
|
|
|
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
|
|
|
|
|
2026-03-31 15:18:50 +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
|
|
|
|
{
|
2026-03-31 15:18:50 +08:00
|
|
|
|
result = mid;
|
|
|
|
|
|
high = mid - 1;
|
2025-03-12 20:59:12 +08:00
|
|
|
|
}
|
2026-03-31 15:18:50 +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
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-31 15:18:50 +08:00
|
|
|
|
return direction == Direction.Vertical ? size.y : size.x;
|
2025-03-12 20:59:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|