using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class WaterfallScrollView : MonoBehaviour { [Header("组件引用")] [SerializeField] private ScrollRect scrollRect; [SerializeField] private RectTransform viewport; [SerializeField] private RectTransform content; [SerializeField] private RectTransform itemPrefab; [SerializeField] private Image itemImageComponent; // 预制体中的Image组件引用 [Header("瀑布流设置")] [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 columnHeights; private List activeItems = new List(); private Dictionary pooledItems = new Dictionary(); private float itemWidth; private float viewportHeight; private float contentHeight; private int totalItemCount = 0; private int visibleStartIndex = 0; private int visibleEndIndex = 0; public List sprites = new List(); // 存储所有精灵 private List itemHeights = new List(); // 存储每项的计算高度 private void Start() { Initialize(); scrollRect.onValueChanged.AddListener(OnScroll); // 初始检查sprites是否有内容 if (sprites.Count > 0) { SetContent(sprites); } else { ClearContent(); } } private void Initialize() { // 计算列宽和间距 float availableWidth = viewport.rect.width - paddingLeft - paddingRight - (columnCount - 1) * spacingX; itemWidth = availableWidth / columnCount; // 初始化列高度数组 columnHeights = new List(new float[columnCount]); // 记录视口高度用于计算可见项 viewportHeight = viewport.rect.height; } // 使用提供的精灵列表设置内容 public void SetContent(List sprites) { this.sprites = sprites ?? new List(); totalItemCount = this.sprites.Count; // 如果没有精灵,清空内容 if (totalItemCount == 0) { ClearContent(); return; } // 计算每项的高度 CalculateItemHeights(); // 计算内容高度 contentHeight = CalculateContentHeight(); content.sizeDelta = new Vector2(content.sizeDelta.x, contentHeight); // 更新可见项 UpdateVisibleItems(); } // 清空所有内容 public void ClearContent() { // 回收所有活动项 foreach (var item in activeItems) { item.gameObject.SetActive(false); } activeItems.Clear(); // 重置内容大小 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) { // 计算保持宽高比的高度 float aspectRatio = sprite.rect.width / sprite.rect.height; float height = itemWidth / aspectRatio; itemHeights.Add(height); } else { // 如果精灵为空,使用默认高度 itemHeights.Add(150f); } } } private float CalculateContentHeight() { // 重置列高度 for (int i = 0; i < columnCount; i++) { columnHeights[i] = paddingTop; } // 模拟布局计算内容高度 for (int i = 0; i < totalItemCount; i++) { float height = itemHeights[i]; int shortestColumnIndex = GetShortestColumn(); columnHeights[shortestColumnIndex] += height + spacingY; } // 找出最高的列作为内容高度 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() { // 如果没有项目,不执行任何操作 if (totalItemCount == 0) return; // 计算当前可视区域的上下边界 float viewportTop = -content.anchoredPosition.y; float viewportBottom = viewportTop + viewportHeight; // 重置列高度用于布局计算 for (int i = 0; i < columnCount; i++) { columnHeights[i] = paddingTop; } // 计算可见项的起始和结束索引 int newVisibleStartIndex = 0; int newVisibleEndIndex = totalItemCount - 1; float currentYPosition = 0; // 找到第一个可见项的索引 for (int i = 0; i < totalItemCount; i++) { float height = itemHeights[i]; int columnIndex = GetShortestColumn(); float xPosition = paddingLeft + columnIndex * (itemWidth + spacingX); float yPosition = -columnHeights[columnIndex]; // 注意UI坐标是向下为正 columnHeights[columnIndex] += height + spacingY; float itemTop = yPosition; float itemBottom = yPosition - height; // 如果项在可视区域内或者与可视区域有交集 if (itemBottom <= viewportBottom && itemTop >= viewportTop - viewportHeight) // 多加载一个屏幕高度的内容 { newVisibleStartIndex = i; break; } } // 重置列高度再次计算 for (int i = 0; i < columnCount; i++) { columnHeights[i] = paddingTop; } // 找到最后一个可见项的索引 for (int i = 0; i < totalItemCount; i++) { float height = itemHeights[i]; int columnIndex = GetShortestColumn(); float xPosition = paddingLeft + columnIndex * (itemWidth + spacingX); float yPosition = -columnHeights[columnIndex]; // 注意UI坐标是向下为正 columnHeights[columnIndex] += height + spacingY; float itemTop = yPosition; float itemBottom = yPosition - height; // 如果项在可视区域内或者与可视区域有交集 if (itemBottom <= viewportBottom + viewportHeight && itemTop >= viewportTop) // 多加载一个屏幕高度的内容 { newVisibleEndIndex = i; } else if (i > newVisibleStartIndex) // 已经找到起始索引后才开始判断结束 { break; } } // 如果可见项范围没有变化则不更新 if (newVisibleStartIndex == visibleStartIndex && newVisibleEndIndex == visibleEndIndex) { return; } visibleStartIndex = newVisibleStartIndex; visibleEndIndex = newVisibleEndIndex; // 回收不再可见的项 RecycleInvisibleItems(); // 重置列高度再次计算,这次用于实际布局 for (int i = 0; i < columnCount; i++) { columnHeights[i] = paddingTop; } // 创建或更新可见项 for (int i = 0; i < totalItemCount; i++) { float height = itemHeights[i]; int columnIndex = GetShortestColumn(); float xPosition = paddingLeft + columnIndex * (itemWidth + spacingX); float yPosition = -columnHeights[columnIndex]; // 注意UI坐标是向下为正 columnHeights[columnIndex] += height + spacingY; // 只处理可见范围内的项 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)) { // 创建新项 item = Instantiate(itemPrefab, content); item.name = "Item_" + index; pooledItems[index] = item; activeItems.Add(item); } else if (!activeItems.Contains(item)) { // 从池中取出 item.gameObject.SetActive(true); activeItems.Add(item); } // 设置位置和大小 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); // 更新项内容 UpdateItemContent(item, index); } private void UpdateItemContent(RectTransform item, int index) { // 获取Image组件 Image image = item.GetComponentInChildren(); if (image == null && itemImageComponent != null) { // 如果没有找到Image组件,使用预制体中的引用 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) { // 设置精灵 image.sprite = sprites[index]; image.SetNativeSize(); // 设置Image组件为保持宽高比 image.preserveAspect = true; } } private void RecycleInvisibleItems() { // 找出需要回收的项 List itemsToRecycle = new List(); 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); } } } // 回收项 foreach (RectTransform item in itemsToRecycle) { item.gameObject.SetActive(false); activeItems.Remove(item); } } private void OnScroll(Vector2 scrollPosition) { UpdateVisibleItems(); } }