UILoop.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using UnityEngine.UI;
  5. //瀑布流Scroll,没有居中功能(Todo),使用方法:添加回调onCreate,onUpdate
  6. public class UILoop : MonoBehaviour
  7. {
  8. //返回Index和相应的Gameobject
  9. public delegate void UILoopCallBack(int globalIndex, GameObject go);
  10. enum Direction
  11. {
  12. Horizontal,
  13. Vertical
  14. }
  15. [SerializeField]
  16. private RectTransform m_Cell;
  17. [SerializeField]
  18. private Vector2 m_CellGap;
  19. [SerializeField, Range(1, 50)]
  20. private int m_row = 5;
  21. [SerializeField, Range(1, 50)]
  22. private int m_column = 5;
  23. [SerializeField]
  24. private Direction direction = Direction.Horizontal;
  25. [SerializeField]
  26. private bool autoCollider = true;
  27. private Vector2 cellSize { get { return m_Cell.sizeDelta + m_CellGap; } }
  28. private float lineGap { get { return direction == Direction.Horizontal ? cellSize.x : cellSize.y; } }
  29. private float startPos { get { return direction == Direction.Horizontal ? -cacheRect.anchoredPosition.x : cacheRect.anchoredPosition.y; } }
  30. private int pageCapacity { get { return m_row * m_column; } }
  31. private int pageLine { get { return direction == Direction.Horizontal ? m_column : m_row; } }
  32. private int lineCapacity { get { return direction == Direction.Horizontal ? m_row : m_column; } }
  33. private int maxLineIndex
  34. {
  35. get
  36. {
  37. return Mathf.Max(0, (ItemsCount - 1) / lineCapacity);
  38. }
  39. }
  40. private int currentLineStartIndex
  41. {
  42. get
  43. {
  44. return Mathf.Max(0, Mathf.FloorToInt(startPos / lineGap));
  45. }
  46. }
  47. private int currentLineEndIndex
  48. {
  49. get
  50. {
  51. return Mathf.Min(currentLineStartIndex + pageLine, maxLineIndex);
  52. }
  53. }
  54. private int currentItemStartIndex
  55. {
  56. get
  57. {
  58. return currentLineStartIndex * lineCapacity;
  59. }
  60. }
  61. private int currentItemEndIndex
  62. {
  63. get
  64. {
  65. return Mathf.Min((currentLineEndIndex + 1) * lineCapacity - 1, ItemsCount - 1);
  66. }
  67. }
  68. private int m_ItemsCount = 0;
  69. public int ItemsCount
  70. {
  71. get
  72. {
  73. return m_ItemsCount;
  74. }
  75. set
  76. {
  77. m_ItemsCount = value;
  78. UpdateAll();
  79. }
  80. }
  81. private int m_lastLineStartIndex = -1;
  82. //创建每个Item后的回调
  83. public UILoopCallBack onCreate;
  84. //更新每个Item后的回调
  85. public UILoopCallBack onUpdate;
  86. private List<RectTransform> m_poolItems = new List<RectTransform>();
  87. private List<RectTransform> m_showItems = new List<RectTransform>();
  88. private int m_showIndexStart = 0;
  89. private int m_createIndex = 0;
  90. private static readonly Vector2 InvalidPos = new Vector2(99999f, 99999f);
  91. private RectTransform mRect;
  92. private RectTransform cacheRect
  93. {
  94. get
  95. {
  96. if (mRect == null)
  97. {
  98. mRect = GetComponent<RectTransform>();
  99. mRect.anchoredPosition = Vector2.zero;
  100. mRect.anchorMax = Vector2.up;
  101. mRect.anchorMin = Vector2.up;
  102. mRect.pivot = Vector2.up;
  103. }
  104. return mRect;
  105. }
  106. }
  107. private List<RectTransform> m_delayShowItems = new List<RectTransform>();
  108. private ScrollRect m_scrollRect;
  109. private ScrollRect scrollRect
  110. {
  111. get
  112. {
  113. if (m_scrollRect == null)
  114. m_scrollRect = this.GetComponentInParent<ScrollRect>();
  115. return m_scrollRect;
  116. }
  117. }
  118. void Awake()
  119. {
  120. if (m_Cell.gameObject.activeSelf)
  121. m_Cell.gameObject.SetActive(false);
  122. if (autoCollider)
  123. {
  124. Image img = this.GetComponent<Image>();
  125. if (img == null)
  126. {
  127. img = this.gameObject.AddComponent<Image>();
  128. img.color = new Color(1f, 1f, 1f, 0f);
  129. }
  130. }
  131. ResetBound();
  132. ScrollToGlobalIndex(0);
  133. }
  134. private RectTransform CreateItem()
  135. {
  136. RectTransform item = GameObject.Instantiate(m_Cell);
  137. item.SetParent(cacheRect, false);
  138. item.anchorMax = Vector2.up;
  139. item.anchorMin = Vector2.up;
  140. item.pivot = new Vector2(0.5f, 0.5f);
  141. item.anchoredPosition3D = Vector3.zero;
  142. //item.gameObject.SetActive(true);
  143. m_delayShowItems.Add(item);
  144. //回调
  145. if (onCreate != null)
  146. onCreate(m_createIndex, item.gameObject);
  147. m_createIndex++;
  148. return item;
  149. }
  150. private RectTransform GetItemFromPool()
  151. {
  152. if (m_poolItems.Count > 0)
  153. {
  154. RectTransform rt = m_poolItems[m_poolItems.Count - 1];
  155. m_poolItems.RemoveAt(m_poolItems.Count - 1);
  156. return rt;
  157. }
  158. return CreateItem();
  159. }
  160. void Update()
  161. {
  162. //Debug.LogWarning(maxLineIndex + " " + currentLineStartIndex + " " + currentLineEndIndex + " " + currentItemStartIndex + " " + currentItemEndIndex);
  163. if (m_delayShowItems.Count > 0)
  164. {
  165. m_delayShowItems[0].gameObject.SetActive(true);
  166. m_delayShowItems.RemoveAt(0);
  167. }
  168. if (m_lastLineStartIndex != currentLineStartIndex)
  169. {
  170. m_lastLineStartIndex = currentLineStartIndex;
  171. int itemStartIndex = currentItemStartIndex;
  172. int itemEndIndex = currentItemEndIndex;
  173. int count = itemEndIndex - itemStartIndex + 1;
  174. if (count > 0)
  175. {
  176. int oldShowStartIndex = m_showIndexStart;
  177. int oldShowEndIndex = m_showIndexStart + m_showItems.Count - 1;
  178. for (int i = oldShowEndIndex; i >= m_showIndexStart; i--)
  179. {
  180. if (i >= itemStartIndex && i <= itemEndIndex)
  181. continue;
  182. int localIndex = i - m_showIndexStart;
  183. RectTransform rt = m_showItems[localIndex];
  184. m_showItems.RemoveAt(localIndex);
  185. m_poolItems.Add(rt);
  186. rt.anchoredPosition = InvalidPos;
  187. if (i > itemEndIndex)
  188. oldShowEndIndex--;
  189. else
  190. oldShowStartIndex++;
  191. }
  192. m_showIndexStart = itemStartIndex;
  193. for (int i = itemStartIndex; i <= itemEndIndex; i++)
  194. {
  195. int localIndex = i - itemStartIndex;
  196. if (i >= oldShowStartIndex && i <= oldShowEndIndex)
  197. {
  198. RectTransform rt = m_showItems[localIndex];
  199. rt.anchoredPosition = GetPosByGlobalIndex(i);
  200. }
  201. else
  202. {
  203. RectTransform rt = GetItemFromPool();
  204. rt.anchoredPosition = GetPosByGlobalIndex(i);
  205. if (i < oldShowStartIndex)
  206. {
  207. m_showItems.Insert(localIndex, rt);
  208. }
  209. else
  210. {
  211. m_showItems.Add(rt);
  212. }
  213. if (onUpdate != null)
  214. onUpdate(i, rt.gameObject);
  215. }
  216. // 测试编号显示
  217. //Text text = m_showItems[localIndex].GetComponentInChildren<Text>();
  218. //if (text)
  219. // text.text = i.ToString();
  220. }
  221. }
  222. }
  223. }
  224. private Vector2 GetPosByGlobalIndex(int globalIndex)
  225. {
  226. if (globalIndex < 0)
  227. globalIndex = 0;
  228. Vector2 nativeSize = m_Cell.sizeDelta;
  229. float x;
  230. float y;
  231. if (direction == Direction.Horizontal)
  232. {
  233. x = globalIndex / m_row;
  234. y = globalIndex % m_row;
  235. }
  236. else
  237. {
  238. x = globalIndex % m_column;
  239. y = globalIndex / m_column;
  240. }
  241. x = x * cellSize.x + nativeSize.x * 0.5f;
  242. y = -(y * cellSize.y + nativeSize.y * 0.5f);
  243. return new Vector2(x, y);
  244. }
  245. private void ResetBound()
  246. {
  247. int row = m_row;
  248. int column = m_column;
  249. if (direction == Direction.Horizontal)
  250. {
  251. column = m_ItemsCount / m_row;
  252. if (m_ItemsCount % m_row != 0)
  253. column++;
  254. // perfect line count for drag bound
  255. column = Mathf.Max(column, m_column - 1);
  256. }
  257. else
  258. {
  259. row = m_ItemsCount / m_column;
  260. if (m_ItemsCount % m_column != 0)
  261. row++;
  262. // perfect line count for drag bound
  263. row = Mathf.Max(row, m_row - 1);
  264. }
  265. cacheRect.sizeDelta = new Vector2(column * cellSize.x - m_CellGap.x, row * cellSize.y - m_CellGap.y);
  266. }
  267. //-------------------------------------对外接口
  268. //滚动到某个Item位置
  269. public void ScrollToGlobalIndex(int globalIndex)
  270. {
  271. Vector2 pos = GetPosByGlobalIndex(globalIndex);
  272. Vector2 curPos = cacheRect.anchoredPosition;
  273. Vector2 nativeSize = m_Cell.sizeDelta;
  274. // pivot offset
  275. pos.x -= 0.5f * nativeSize.x;
  276. pos.y += 0.5f * nativeSize.y;
  277. if (direction == Direction.Horizontal)
  278. {
  279. curPos.x = -pos.x;
  280. if (scrollRect)
  281. {
  282. float maxScroll = cacheRect.sizeDelta.x - (scrollRect.transform as RectTransform).sizeDelta.x;
  283. if (maxScroll > 0 && curPos.x < -maxScroll)
  284. curPos.x = -maxScroll;
  285. else if (maxScroll < 0)
  286. curPos.x = 0;
  287. }
  288. }
  289. else
  290. {
  291. curPos.y = -pos.y;
  292. if (scrollRect)
  293. {
  294. float maxScroll = cacheRect.sizeDelta.y - (scrollRect.transform as RectTransform).sizeDelta.y;
  295. if (maxScroll > 0 && curPos.y > maxScroll)
  296. curPos.y = maxScroll;
  297. else if (maxScroll < 0)
  298. curPos.y = 0;
  299. }
  300. }
  301. // 为了修正ScrollRect的Bound计算问题
  302. if (curPos.x == 0)
  303. curPos.x = 0.01f;
  304. if (curPos.y == 0)
  305. curPos.y = -0.01f;
  306. cacheRect.anchoredPosition = curPos;
  307. }
  308. public int GetItemGlobalIndex(GameObject go)
  309. {
  310. for (int i = m_showItems.Count - 1; i >= 0; i--)
  311. {
  312. if (m_showItems[i].gameObject == go)
  313. return currentItemStartIndex + i;
  314. }
  315. return -1;
  316. }
  317. public GameObject GetItemByLocalIndex(int localIndex)
  318. {
  319. if (localIndex >= 0 && localIndex < m_showItems.Count)
  320. {
  321. return m_showItems[localIndex].gameObject;
  322. }
  323. return null;
  324. }
  325. public GameObject GetItemByGlobalIndex(int globalIndex)
  326. {
  327. int localIndex = globalIndex - currentItemStartIndex;
  328. if (localIndex >= 0 && localIndex < m_showItems.Count)
  329. return m_showItems[localIndex].gameObject;
  330. return null;
  331. }
  332. public void UpdateAll()
  333. {
  334. m_lastLineStartIndex = -1;
  335. if (m_showItems.Count > 0)
  336. {
  337. for (int i = m_showItems.Count - 1; i >= 0; i--)
  338. {
  339. m_showItems[i].anchoredPosition = InvalidPos;
  340. m_poolItems.Add(m_showItems[i]);
  341. }
  342. m_showItems.Clear();
  343. }
  344. ResetBound();
  345. // 确保物件创建出来
  346. Update();
  347. }
  348. }
  349. #if UNITY_EDITOR
  350. [UnityEditor.CustomEditor(typeof(UILoop), true)]
  351. public class UILoopEditor : UnityEditor.Editor
  352. {
  353. int itemCount;
  354. public override void OnInspectorGUI()
  355. {
  356. base.OnInspectorGUI();
  357. var comp = target as UILoop;
  358. UnityEditor.EditorGUILayout.HelpBox("以下只有编辑器有效", UnityEditor.MessageType.Info);
  359. UnityEditor.EditorGUILayout.LabelField("Item Count = " + comp.ItemsCount);
  360. GUILayout.BeginHorizontal();
  361. itemCount = UnityEditor.EditorGUILayout.IntField("New Count = ", itemCount);
  362. if (GUILayout.Button("Reset Count"))
  363. {
  364. comp.ItemsCount = itemCount;
  365. }
  366. GUILayout.EndHorizontal();
  367. }
  368. }
  369. #endif