VirtualFramework/Assets/Scripts/Tools/WaterfallScrollView.cs

361 lines
11 KiB
C#
Raw Normal View History

2025-04-27 11:41:11 +08:00
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class WaterfallScrollView : MonoBehaviour
{
[Header("<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>")]
[SerializeField] private ScrollRect scrollRect;
[SerializeField] private RectTransform viewport;
[SerializeField] private RectTransform content;
[SerializeField] private RectTransform itemPrefab;
[SerializeField] private Image itemImageComponent; // Ԥ<><D4A4><EFBFBD><EFBFBD><EFBFBD>е<EFBFBD>Image<67><65><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
[Header("<22>ٲ<EFBFBD><D9B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>")]
[SerializeField] private int columnCount = 3;
[SerializeField] private float spacingX = 10f;
[SerializeField] private float spacingY = 10f;
[SerializeField] private float paddingLeft = 10f;
[SerializeField] private float paddingRight = 10f;
[SerializeField] private float paddingTop = 10f;
[SerializeField] private float paddingBottom = 10f;
private List<float> columnHeights;
private List<RectTransform> activeItems = new List<RectTransform>();
private Dictionary<int, RectTransform> pooledItems = new Dictionary<int, RectTransform>();
private float itemWidth;
private float viewportHeight;
private float contentHeight;
private int totalItemCount = 0;
private int visibleStartIndex = 0;
private int visibleEndIndex = 0;
public List<Sprite> sprites = new List<Sprite>(); // <20><EFBFBD><E6B4A2><EFBFBD>о<EFBFBD><D0BE><EFBFBD>
private List<float> itemHeights = new List<float>(); // <20>洢ÿ<E6B4A2><C3BF><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>߶<EFBFBD>
private void Start()
{
Initialize();
scrollRect.onValueChanged.AddListener(OnScroll);
// <20><>ʼ<EFBFBD><CABC><EFBFBD><EFBFBD>sprites<65>Ƿ<EFBFBD><C7B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
if (sprites.Count > 0)
{
SetContent(sprites);
}
else
{
ClearContent();
}
}
private void Initialize()
{
// <20><><EFBFBD><EFBFBD><EFBFBD>п<EFBFBD><D0BF>ͼ<EFBFBD><CDBC><EFBFBD>
float availableWidth = viewport.rect.width - paddingLeft - paddingRight - (columnCount - 1) * spacingX;
itemWidth = availableWidth / columnCount;
// <20><>ʼ<EFBFBD><CABC><EFBFBD>и߶<D0B8><DFB6><EFBFBD><EFBFBD><EFBFBD>
columnHeights = new List<float>(new float[columnCount]);
// <20><>¼<EFBFBD>ӿڸ߶<DAB8><DFB6><EFBFBD><EFBFBD>ڼ<EFBFBD><DABC><EFBFBD><EFBFBD>ɼ<EFBFBD><C9BC><EFBFBD>
viewportHeight = viewport.rect.height;
}
// ʹ<><CAB9><EFBFBD><EFBFBD>ľ<EFBFBD><C4BE><EFBFBD><EFBFBD>б<EFBFBD><D0B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
public void SetContent(List<Sprite> sprites)
{
this.sprites = sprites ?? new List<Sprite>();
totalItemCount = this.sprites.Count;
// <20><><EFBFBD><EFBFBD>û<EFBFBD>о<EFBFBD><D0BE><EFBFBD><E9A3AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
if (totalItemCount == 0)
{
ClearContent();
return;
}
// <20><><EFBFBD><EFBFBD>ÿ<EFBFBD><C3BF><EFBFBD>ĸ߶<C4B8>
CalculateItemHeights();
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݸ߶<DDB8>
contentHeight = CalculateContentHeight();
content.sizeDelta = new Vector2(content.sizeDelta.x, contentHeight);
// <20><><EFBFBD>¿ɼ<C2BF><C9BC><EFBFBD>
UpdateVisibleItems();
}
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
public void ClearContent()
{
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>л<D0BB><EEB6AF>
foreach (var item in activeItems)
{
item.gameObject.SetActive(false);
}
activeItems.Clear();
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݴ<EFBFBD>С
content.sizeDelta = new Vector2(content.sizeDelta.x, 0);
totalItemCount = 0;
itemHeights.Clear();
}
private void CalculateItemHeights()
{
itemHeights.Clear();
foreach (Sprite sprite in sprites)
{
if (sprite != null)
{
// <20><><EFBFBD><EFBFBD>ֿ<EFBFBD><D6BF>߱ȵĸ߶<C4B8>
float aspectRatio = sprite.rect.width / sprite.rect.height;
float height = itemWidth / aspectRatio;
itemHeights.Add(height);
}
else
{
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD>գ<EFBFBD>ʹ<EFBFBD><CAB9>Ĭ<EFBFBD>ϸ߶<CFB8>
itemHeights.Add(150f);
}
}
}
private float CalculateContentHeight()
{
// <20><><EFBFBD><EFBFBD><EFBFBD>и߶<D0B8>
for (int i = 0; i < columnCount; i++)
{
columnHeights[i] = paddingTop;
}
// ģ<><EFBFBD>ּ<EFBFBD><D6BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݸ߶<DDB8>
for (int i = 0; i < totalItemCount; i++)
{
float height = itemHeights[i];
int shortestColumnIndex = GetShortestColumn();
columnHeights[shortestColumnIndex] += height + spacingY;
}
// <20>ҳ<EFBFBD><D2B3><EFBFBD><EFBFBD>ߵ<EFBFBD><DFB5><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><CEAA><EFBFBD>ݸ߶<DDB8>
float maxHeight = 0;
foreach (float height in columnHeights)
{
if (height > maxHeight)
{
maxHeight = height;
}
}
return maxHeight + paddingBottom - spacingY;
}
private int GetShortestColumn()
{
int shortestIndex = 0;
float minHeight = columnHeights[0];
for (int i = 1; i < columnCount; i++)
{
if (columnHeights[i] < minHeight)
{
minHeight = columnHeights[i];
shortestIndex = i;
}
}
return shortestIndex;
}
private void UpdateVisibleItems()
{
// <20><><EFBFBD><EFBFBD>û<EFBFBD><C3BB><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF><EFBFBD><EFBFBD>ִ<EFBFBD><D6B4><EFBFBD>κβ<CEBA><CEB2><EFBFBD>
if (totalItemCount == 0)
return;
// <20><><EFBFBD>㵱ǰ<E3B5B1><C7B0><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>±߽<C2B1>
float viewportTop = -content.anchoredPosition.y;
float viewportBottom = viewportTop + viewportHeight;
// <20><><EFBFBD><EFBFBD><EFBFBD>и߶<D0B8><DFB6><EFBFBD><EFBFBD>ڲ<EFBFBD><DAB2>ּ<EFBFBD><D6BC><EFBFBD>
for (int i = 0; i < columnCount; i++)
{
columnHeights[i] = paddingTop;
}
// <20><><EFBFBD><EFBFBD><EFBFBD>ɼ<EFBFBD><C9BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼ<EFBFBD>ͽ<EFBFBD><CDBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
int newVisibleStartIndex = 0;
int newVisibleEndIndex = totalItemCount - 1;
float currentYPosition = 0;
// <20>ҵ<EFBFBD><D2B5><EFBFBD>һ<EFBFBD><D2BB><EFBFBD>ɼ<EFBFBD><C9BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
for (int i = 0; i < totalItemCount; i++)
{
float height = itemHeights[i];
int columnIndex = GetShortestColumn();
float xPosition = paddingLeft + columnIndex * (itemWidth + spacingX);
float yPosition = -columnHeights[columnIndex]; // ע<><D7A2>UI<55><49><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><CEAA>
columnHeights[columnIndex] += height + spacingY;
float itemTop = yPosition;
float itemBottom = yPosition - height;
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڿ<EFBFBD><DABF><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڻ<EFBFBD><DABB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>н<EFBFBD><D0BD><EFBFBD>
if (itemBottom <= viewportBottom && itemTop >= viewportTop - viewportHeight) // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD><EFBFBD>Ļ<EFBFBD>߶ȵ<DFB6><C8B5><EFBFBD><EFBFBD><EFBFBD>
{
newVisibleStartIndex = i;
break;
}
}
// <20><><EFBFBD><EFBFBD><EFBFBD>и߶<D0B8><DFB6>ٴμ<D9B4><CEBC><EFBFBD>
for (int i = 0; i < columnCount; i++)
{
columnHeights[i] = paddingTop;
}
// <20>ҵ<EFBFBD><D2B5><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD>ɼ<EFBFBD><C9BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
for (int i = 0; i < totalItemCount; i++)
{
float height = itemHeights[i];
int columnIndex = GetShortestColumn();
float xPosition = paddingLeft + columnIndex * (itemWidth + spacingX);
float yPosition = -columnHeights[columnIndex]; // ע<><D7A2>UI<55><49><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><CEAA>
columnHeights[columnIndex] += height + spacingY;
float itemTop = yPosition;
float itemBottom = yPosition - height;
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڿ<EFBFBD><DABF><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڻ<EFBFBD><DABB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>н<EFBFBD><D0BD><EFBFBD>
if (itemBottom <= viewportBottom + viewportHeight && itemTop >= viewportTop) // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD><EFBFBD>Ļ<EFBFBD>߶ȵ<DFB6><C8B5><EFBFBD><EFBFBD><EFBFBD>
{
newVisibleEndIndex = i;
}
else if (i > newVisibleStartIndex) // <20>Ѿ<EFBFBD><D1BE>ҵ<EFBFBD><D2B5><EFBFBD>ʼ<EFBFBD><CABC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ſ<EFBFBD>ʼ<EFBFBD>жϽ<D0B6><CFBD><EFBFBD>
{
break;
}
}
// <20><><EFBFBD><EFBFBD><EFBFBD>ɼ<EFBFBD><C9BC>Χû<CEA7>б<D0B1>򲻸<EFBFBD><F2B2BBB8><EFBFBD>
if (newVisibleStartIndex == visibleStartIndex && newVisibleEndIndex == visibleEndIndex)
{
return;
}
visibleStartIndex = newVisibleStartIndex;
visibleEndIndex = newVisibleEndIndex;
// <20><><EFBFBD>ղ<EFBFBD><D5B2>ٿɼ<D9BF><C9BC><EFBFBD><EFBFBD><EFBFBD>
RecycleInvisibleItems();
// <20><><EFBFBD><EFBFBD><EFBFBD>и߶<D0B8><DFB6>ٴμ<D9B4><CEBC><EFBFBD><E3A3AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʵ<EFBFBD>ʲ<EFBFBD><CAB2><EFBFBD>
for (int i = 0; i < columnCount; i++)
{
columnHeights[i] = paddingTop;
}
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>¿ɼ<C2BF><C9BC><EFBFBD>
for (int i = 0; i < totalItemCount; i++)
{
float height = itemHeights[i];
int columnIndex = GetShortestColumn();
float xPosition = paddingLeft + columnIndex * (itemWidth + spacingX);
float yPosition = -columnHeights[columnIndex]; // ע<><D7A2>UI<55><49><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><CEAA>
columnHeights[columnIndex] += height + spacingY;
// ֻ<><D6BB><EFBFBD><EFBFBD><EFBFBD>ɼ<EFBFBD><C9BC><EFBFBD>Χ<EFBFBD>ڵ<EFBFBD><DAB5><EFBFBD>
if (i >= visibleStartIndex && i <= visibleEndIndex)
{
CreateOrUpdateItem(i, xPosition, yPosition, itemWidth, height);
}
}
}
private void CreateOrUpdateItem(int index, float x, float y, float width, float height)
{
RectTransform item;
if (!pooledItems.TryGetValue(index, out item))
{
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
item = Instantiate(itemPrefab, content);
item.name = "Item_" + index;
pooledItems[index] = item;
activeItems.Add(item);
}
else if (!activeItems.Contains(item))
{
// <20>ӳ<EFBFBD><D3B3><EFBFBD>ȡ<EFBFBD><C8A1>
item.gameObject.SetActive(true);
activeItems.Add(item);
}
// <20><><EFBFBD><EFBFBD>λ<EFBFBD>úʹ<C3BA>С
item.anchorMin = new Vector2(0, 1);
item.anchorMax = new Vector2(0, 1);
item.pivot = new Vector2(0, 1);
item.anchoredPosition = new Vector2(x, y);
item.sizeDelta = new Vector2(width, height);
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
UpdateItemContent(item, index);
}
private void UpdateItemContent(RectTransform item, int index)
{
// <20><>ȡImage<67><65><EFBFBD><EFBFBD>
Image image = item.GetComponentInChildren<Image>();
if (image == null && itemImageComponent != null)
{
// <20><><EFBFBD><EFBFBD>û<EFBFBD><C3BB><EFBFBD>ҵ<EFBFBD>Image<67><65><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>Ԥ<EFBFBD><D4A4><EFBFBD><EFBFBD><EFBFBD>е<EFBFBD><D0B5><EFBFBD><EFBFBD><EFBFBD>
image = Instantiate(itemImageComponent, item);
image.rectTransform.anchorMin = Vector2.zero;
image.rectTransform.anchorMax = Vector2.one;
image.rectTransform.sizeDelta = Vector2.zero;
}
if (image && index < sprites.Count && sprites[index] != null)
{
// <20><><EFBFBD>þ<EFBFBD><C3BE><EFBFBD>
image.sprite = sprites[index];
image.SetNativeSize();
// <20><><EFBFBD><EFBFBD>Image<67><65><EFBFBD><EFBFBD>Ϊ<EFBFBD><CEAA><EFBFBD>ֿ<EFBFBD><D6BF>߱<EFBFBD>
image.preserveAspect = true;
}
}
private void RecycleInvisibleItems()
{
// <20>ҳ<EFBFBD><D2B3><EFBFBD>Ҫ<EFBFBD><D2AA><EFBFBD>յ<EFBFBD><D5B5><EFBFBD>
List<RectTransform> itemsToRecycle = new List<RectTransform>();
foreach (RectTransform item in activeItems)
{
string[] nameParts = item.name.Split('_');
if (nameParts.Length >= 2 && int.TryParse(nameParts[1], out int itemIndex))
{
if (itemIndex < visibleStartIndex || itemIndex > visibleEndIndex)
{
itemsToRecycle.Add(item);
}
}
}
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
foreach (RectTransform item in itemsToRecycle)
{
item.gameObject.SetActive(false);
activeItems.Remove(item);
}
}
private void OnScroll(Vector2 scrollPosition)
{
UpdateVisibleItems();
}
}