using UnityEngine; using System.Collections; using System.Collections.Generic; using UnityEngine.UI; //瀑布流Scroll,没有居中功能(Todo),使用方法:添加回调onCreate,onUpdate public class UILoop : MonoBehaviour { //返回Index和相应的Gameobject public delegate void UILoopCallBack(int globalIndex, GameObject go); enum Direction { Horizontal, Vertical } [SerializeField] private RectTransform m_Cell; [SerializeField] private Vector2 m_CellGap; [SerializeField, Range(1, 50)] private int m_row = 5; [SerializeField, Range(1, 50)] private int m_column = 5; [SerializeField] private Direction direction = Direction.Horizontal; [SerializeField] private bool autoCollider = true; private Vector2 cellSize { get { return m_Cell.sizeDelta + m_CellGap; } } private float lineGap { get { return direction == Direction.Horizontal ? cellSize.x : cellSize.y; } } private float startPos { get { return direction == Direction.Horizontal ? -cacheRect.anchoredPosition.x : cacheRect.anchoredPosition.y; } } private int pageCapacity { get { return m_row * m_column; } } private int pageLine { get { return direction == Direction.Horizontal ? m_column : m_row; } } private int lineCapacity { get { return direction == Direction.Horizontal ? m_row : m_column; } } private int maxLineIndex { get { return Mathf.Max(0, (ItemsCount - 1) / lineCapacity); } } private int currentLineStartIndex { get { return Mathf.Max(0, Mathf.FloorToInt(startPos / lineGap)); } } private int currentLineEndIndex { get { return Mathf.Min(currentLineStartIndex + pageLine, maxLineIndex); } } private int currentItemStartIndex { get { return currentLineStartIndex * lineCapacity; } } private int currentItemEndIndex { get { return Mathf.Min((currentLineEndIndex + 1) * lineCapacity - 1, ItemsCount - 1); } } private int m_ItemsCount = 0; public int ItemsCount { get { return m_ItemsCount; } set { m_ItemsCount = value; UpdateAll(); } } private int m_lastLineStartIndex = -1; //创建每个Item后的回调 public UILoopCallBack onCreate; //更新每个Item后的回调 public UILoopCallBack onUpdate; private List m_poolItems = new List(); private List m_showItems = new List(); private int m_showIndexStart = 0; private int m_createIndex = 0; private static readonly Vector2 InvalidPos = new Vector2(99999f, 99999f); private RectTransform mRect; private RectTransform cacheRect { get { if (mRect == null) { mRect = GetComponent(); mRect.anchoredPosition = Vector2.zero; mRect.anchorMax = Vector2.up; mRect.anchorMin = Vector2.up; mRect.pivot = Vector2.up; } return mRect; } } private List m_delayShowItems = new List(); private ScrollRect m_scrollRect; private ScrollRect scrollRect { get { if (m_scrollRect == null) m_scrollRect = this.GetComponentInParent(); return m_scrollRect; } } void Awake() { if (m_Cell.gameObject.activeSelf) m_Cell.gameObject.SetActive(false); if (autoCollider) { Image img = this.GetComponent(); if (img == null) { img = this.gameObject.AddComponent(); img.color = new Color(1f, 1f, 1f, 0f); } } ResetBound(); ScrollToGlobalIndex(0); } private RectTransform CreateItem() { RectTransform item = GameObject.Instantiate(m_Cell); item.SetParent(cacheRect, false); item.anchorMax = Vector2.up; item.anchorMin = Vector2.up; item.pivot = new Vector2(0.5f, 0.5f); item.anchoredPosition3D = Vector3.zero; //item.gameObject.SetActive(true); m_delayShowItems.Add(item); //回调 if (onCreate != null) onCreate(m_createIndex, item.gameObject); m_createIndex++; return item; } private RectTransform GetItemFromPool() { if (m_poolItems.Count > 0) { RectTransform rt = m_poolItems[m_poolItems.Count - 1]; m_poolItems.RemoveAt(m_poolItems.Count - 1); return rt; } return CreateItem(); } void Update() { //Debug.LogWarning(maxLineIndex + " " + currentLineStartIndex + " " + currentLineEndIndex + " " + currentItemStartIndex + " " + currentItemEndIndex); if (m_delayShowItems.Count > 0) { m_delayShowItems[0].gameObject.SetActive(true); m_delayShowItems.RemoveAt(0); } if (m_lastLineStartIndex != currentLineStartIndex) { m_lastLineStartIndex = currentLineStartIndex; int itemStartIndex = currentItemStartIndex; int itemEndIndex = currentItemEndIndex; int count = itemEndIndex - itemStartIndex + 1; if (count > 0) { int oldShowStartIndex = m_showIndexStart; int oldShowEndIndex = m_showIndexStart + m_showItems.Count - 1; for (int i = oldShowEndIndex; i >= m_showIndexStart; i--) { if (i >= itemStartIndex && i <= itemEndIndex) continue; int localIndex = i - m_showIndexStart; RectTransform rt = m_showItems[localIndex]; m_showItems.RemoveAt(localIndex); m_poolItems.Add(rt); rt.anchoredPosition = InvalidPos; if (i > itemEndIndex) oldShowEndIndex--; else oldShowStartIndex++; } m_showIndexStart = itemStartIndex; for (int i = itemStartIndex; i <= itemEndIndex; i++) { int localIndex = i - itemStartIndex; if (i >= oldShowStartIndex && i <= oldShowEndIndex) { RectTransform rt = m_showItems[localIndex]; rt.anchoredPosition = GetPosByGlobalIndex(i); } else { RectTransform rt = GetItemFromPool(); rt.anchoredPosition = GetPosByGlobalIndex(i); if (i < oldShowStartIndex) { m_showItems.Insert(localIndex, rt); } else { m_showItems.Add(rt); } if (onUpdate != null) onUpdate(i, rt.gameObject); } // 测试编号显示 //Text text = m_showItems[localIndex].GetComponentInChildren(); //if (text) // text.text = i.ToString(); } } } } private Vector2 GetPosByGlobalIndex(int globalIndex) { if (globalIndex < 0) globalIndex = 0; Vector2 nativeSize = m_Cell.sizeDelta; float x; float y; if (direction == Direction.Horizontal) { x = globalIndex / m_row; y = globalIndex % m_row; } else { x = globalIndex % m_column; y = globalIndex / m_column; } x = x * cellSize.x + nativeSize.x * 0.5f; y = -(y * cellSize.y + nativeSize.y * 0.5f); return new Vector2(x, y); } private void ResetBound() { int row = m_row; int column = m_column; if (direction == Direction.Horizontal) { column = m_ItemsCount / m_row; if (m_ItemsCount % m_row != 0) column++; // perfect line count for drag bound column = Mathf.Max(column, m_column - 1); } else { row = m_ItemsCount / m_column; if (m_ItemsCount % m_column != 0) row++; // perfect line count for drag bound row = Mathf.Max(row, m_row - 1); } cacheRect.sizeDelta = new Vector2(column * cellSize.x - m_CellGap.x, row * cellSize.y - m_CellGap.y); } //-------------------------------------对外接口 //滚动到某个Item位置 public void ScrollToGlobalIndex(int globalIndex) { Vector2 pos = GetPosByGlobalIndex(globalIndex); Vector2 curPos = cacheRect.anchoredPosition; Vector2 nativeSize = m_Cell.sizeDelta; // pivot offset pos.x -= 0.5f * nativeSize.x; pos.y += 0.5f * nativeSize.y; if (direction == Direction.Horizontal) { curPos.x = -pos.x; if (scrollRect) { float maxScroll = cacheRect.sizeDelta.x - (scrollRect.transform as RectTransform).sizeDelta.x; if (maxScroll > 0 && curPos.x < -maxScroll) curPos.x = -maxScroll; else if (maxScroll < 0) curPos.x = 0; } } else { curPos.y = -pos.y; if (scrollRect) { float maxScroll = cacheRect.sizeDelta.y - (scrollRect.transform as RectTransform).sizeDelta.y; if (maxScroll > 0 && curPos.y > maxScroll) curPos.y = maxScroll; else if (maxScroll < 0) curPos.y = 0; } } // 为了修正ScrollRect的Bound计算问题 if (curPos.x == 0) curPos.x = 0.01f; if (curPos.y == 0) curPos.y = -0.01f; cacheRect.anchoredPosition = curPos; } public int GetItemGlobalIndex(GameObject go) { for (int i = m_showItems.Count - 1; i >= 0; i--) { if (m_showItems[i].gameObject == go) return currentItemStartIndex + i; } return -1; } public GameObject GetItemByLocalIndex(int localIndex) { if (localIndex >= 0 && localIndex < m_showItems.Count) { return m_showItems[localIndex].gameObject; } return null; } public GameObject GetItemByGlobalIndex(int globalIndex) { int localIndex = globalIndex - currentItemStartIndex; if (localIndex >= 0 && localIndex < m_showItems.Count) return m_showItems[localIndex].gameObject; return null; } public void UpdateAll() { m_lastLineStartIndex = -1; if (m_showItems.Count > 0) { for (int i = m_showItems.Count - 1; i >= 0; i--) { m_showItems[i].anchoredPosition = InvalidPos; m_poolItems.Add(m_showItems[i]); } m_showItems.Clear(); } ResetBound(); // 确保物件创建出来 Update(); } } #if UNITY_EDITOR [UnityEditor.CustomEditor(typeof(UILoop), true)] public class UILoopEditor : UnityEditor.Editor { int itemCount; public override void OnInspectorGUI() { base.OnInspectorGUI(); var comp = target as UILoop; UnityEditor.EditorGUILayout.HelpBox("以下只有编辑器有效", UnityEditor.MessageType.Info); UnityEditor.EditorGUILayout.LabelField("Item Count = " + comp.ItemsCount); GUILayout.BeginHorizontal(); itemCount = UnityEditor.EditorGUILayout.IntField("New Count = ", itemCount); if (GUILayout.Button("Reset Count")) { comp.ItemsCount = itemCount; } GUILayout.EndHorizontal(); } } #endif