using System; using System.Buffers; using UnityEngine; namespace AlicizaX.UI { [Serializable] public class MixedLayoutManager : LayoutManager { private float[] itemLengths = Array.Empty(); private float[] itemPositions = Array.Empty(); private int rentedArraySize = 0; private Vector2 firstItemSize = Vector2.zero; private int cachedItemCount = -1; private bool positionCacheDirty = true; public MixedLayoutManager() { } ~MixedLayoutManager() { ReturnRentedArrays(); } private void ReturnRentedArrays() { if (rentedArraySize > 0) { ArrayPool.Shared.Return(itemLengths); ArrayPool.Shared.Return(itemPositions); itemLengths = Array.Empty(); itemPositions = Array.Empty(); rentedArraySize = 0; } } public override Vector2 CalculateContentSize() { positionCacheDirty = true; EnsurePositionCache(); 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); } 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); } public override Vector2 CalculateContentOffset() { EnsurePositionCache(); if (cachedItemCount <= 0) { return Vector2.zero; } float len = GetFitContentSize(); if (direction == Direction.Vertical) { return new Vector2(0, (len - firstItemSize.y) / 2); } return new Vector2((len - firstItemSize.x) / 2, 0); } public override Vector2 CalculateViewportOffset() { EnsurePositionCache(); if (cachedItemCount <= 0) { return Vector2.zero; } if (direction == Direction.Vertical) { return new Vector2(0, (viewportSize.y - firstItemSize.y) / 2); } return new Vector2((viewportSize.x - firstItemSize.x) / 2, 0); } public override int GetStartIndex() { EnsurePositionCache(); if (cachedItemCount <= 0) { return 0; } int index = FindFirstItemEndingAfter(ScrollPosition); return index >= 0 ? index : 0; } public override int GetEndIndex() { EnsurePositionCache(); if (cachedItemCount <= 0) { return -1; } 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) { return 0f; } float position = GetItemPosition(index); if (direction == Direction.Vertical) { return Mathf.Clamp(position, 0f, Mathf.Max(contentSize.y - viewportSize.y, 0f)); } return Mathf.Clamp(position, 0f, Mathf.Max(contentSize.x - viewportSize.x, 0f)); } public override int PositionToIndex(float position) { EnsurePositionCache(); if (cachedItemCount <= 0) { return 0; } int index = FindFirstItemEndingAtOrAfter(position); return index >= 0 ? index : cachedItemCount - 1; } private void EnsurePositionCache() { int itemCount = adapter != null ? adapter.GetItemCount() : 0; if (itemCount < 0) { itemCount = 0; } if (!positionCacheDirty && cachedItemCount == itemCount) { return; } RebuildPositionCache(itemCount); } private void RebuildPositionCache(int itemCount) { // ArrayPool.Rent 返回的数组长度 >= 请求长度,rentedArraySize 记录实际容量。 // 仅当 itemCount 超出当前容量时才归还并重新租用,缩容时复用原数组。 if (itemCount > rentedArraySize) { ReturnRentedArrays(); if (itemCount > 0) { itemLengths = ArrayPool.Shared.Rent(itemCount); itemPositions = ArrayPool.Shared.Rent(itemCount); rentedArraySize = itemLengths.Length; } } firstItemSize = itemCount > 0 ? viewProvider.CalculateViewSize(0) : Vector2.zero; float position = 0f; 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; } } 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) { result = mid; high = mid - 1; } 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; } return direction == Direction.Vertical ? size.y : size.x; } } }