RecycleView.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. // ## 生成滑动列表必须以下步骤:
  2. // 1. 持有RecycleView对象rv,rv.Init(callBackFunc)
  3. // 2. 刷新整个列表(首次调用和数量变化时调用): ShowList(int count)
  4. // 3. 回调: Func(GameObject cell, int index)
  5. // ----------
  6. // 功能接口看代码,案例详见RecycleViewTest.cs
  7. // 刷新单个项: UpdateCell(int index)
  8. // 刷新列表数据(无数量变化时调用): UpdateList()
  9. // 定位到索引所在当前列表的位置 GoToCellPos(int index)
  10. using UnityEngine;
  11. using UnityEngine.UI;
  12. using System.Collections.Generic;
  13. using UnityEngine.EventSystems;
  14. using System;
  15. public enum E_Direction
  16. {
  17. Horizontal,
  18. Vertical
  19. }
  20. public class RecycleView : MonoBehaviour, IBeginDragHandler, IEndDragHandler, IDragHandler
  21. {
  22. public GameObject firstArrow;
  23. public GameObject endArrow;
  24. public E_Direction dir = E_Direction.Vertical;
  25. public bool isShowArrow = false;
  26. public int lines = 1; // 默认显示1行
  27. public float squareSpacing = 4f; // 方阵间距
  28. public GameObject cell; //指定的cell
  29. public Vector2 Spacing = Vector2.zero;
  30. public float row = 0f; // 行间距
  31. public float col = 0f; // 列间距
  32. protected Action<GameObject, int> FuncCallBackFunc;
  33. protected Action<GameObject, int> FuncOnClickCallBack;
  34. protected Action<int, bool, GameObject> FuncOnButtonClickCallBack;
  35. protected float planeW;
  36. protected float planeH;
  37. protected float contentW;
  38. protected float contentH;
  39. protected float cellW;
  40. protected float cellH;
  41. private bool isInit = false;
  42. protected GameObject content;
  43. protected ScrollRect scrollRect;
  44. protected RectTransform rectTrans;
  45. protected RectTransform contentRectTrans;
  46. protected int maxCount = -1; //列表数量
  47. protected int minIndex = -1;
  48. protected int maxIndex = -1;
  49. //记录 物体的坐标 和 物体
  50. protected struct CellInfo
  51. {
  52. public Vector3 pos;
  53. public GameObject obj;
  54. };
  55. protected CellInfo[] cellInfos;
  56. protected bool isClearList = false; //是否清空列表
  57. // 对象池
  58. protected Stack<GameObject> Pool = new Stack<GameObject>();
  59. protected bool isInited = false;
  60. public virtual void Init(Action<GameObject, int> callBack)
  61. {
  62. Init(callBack, null);
  63. }
  64. public virtual void Init(Action<GameObject, int> callBack, Action<GameObject, int> onClickCallBack,
  65. Action<int, bool, GameObject> onButtonClickCallBack)
  66. {
  67. if (onButtonClickCallBack != null)
  68. {
  69. FuncOnButtonClickCallBack = onButtonClickCallBack;
  70. }
  71. Init(callBack, onClickCallBack);
  72. }
  73. public virtual void Init(Action<GameObject, int> callBack, Action<GameObject, int> onClickCallBack)
  74. {
  75. DisposeAll();
  76. FuncCallBackFunc = callBack;
  77. if (onClickCallBack != null)
  78. {
  79. FuncOnClickCallBack = onClickCallBack;
  80. }
  81. if (isInit) return;
  82. content = this.GetComponent<ScrollRect>().content.gameObject;
  83. if (cell == null)
  84. {
  85. cell = content.transform.GetChild(0).gameObject;
  86. }
  87. // ////////////////////** Cell 处理 **////////////////////
  88. // m_CellGameObject.transform.SetParent(m_Content.transform.parent, false);
  89. SetPoolsObj(cell);
  90. RectTransform cellRectTrans = cell.GetComponent<RectTransform>();
  91. cellRectTrans.pivot = new Vector2(0f, 1f);
  92. CheckAnchor(cellRectTrans);
  93. cellRectTrans.anchoredPosition = Vector2.zero;
  94. // 记录 Cell 信息
  95. cellH = cellRectTrans.rect.height;
  96. cellW = cellRectTrans.rect.width;
  97. // 记录 Plane 信息
  98. rectTrans = GetComponent<RectTransform>();
  99. Rect planeRect = rectTrans.rect;
  100. planeH = planeRect.height;
  101. planeW = planeRect.width;
  102. // 记录 Content 信息
  103. contentRectTrans = content.GetComponent<RectTransform>();
  104. Rect contentRect = contentRectTrans.rect;
  105. contentH = contentRect.height;
  106. contentW = contentRect.width;
  107. // 记录间距信息 如果存在行列设置就引用,没有使用方阵间距
  108. row = Spacing.x;
  109. col = Spacing.y;
  110. if (row == 0 && col == 0) row = col = squareSpacing;
  111. else squareSpacing = 0;
  112. contentRectTrans.pivot = new Vector2(0f, 1f);
  113. //m_ContentRectTrans.sizeDelta = new Vector2 (planeRect.width, planeRect.height);
  114. //m_ContentRectTrans.anchoredPosition = Vector2.zero;
  115. CheckAnchor(contentRectTrans);
  116. scrollRect = this.GetComponent<ScrollRect>();
  117. scrollRect.onValueChanged.RemoveAllListeners();
  118. //添加滑动事件
  119. scrollRect.onValueChanged.AddListener(delegate (Vector2 value) { ScrollRectListener(value); });
  120. if (firstArrow != null || endArrow != null)
  121. {
  122. scrollRect.onValueChanged.AddListener(delegate (Vector2 value) { OnDragListener(value); });
  123. OnDragListener(Vector2.zero);
  124. }
  125. //InitScrollBarGameObject(); // 废弃
  126. isInit = true;
  127. }
  128. // 检查 Anchor 是否正确
  129. private void CheckAnchor(RectTransform rectTrans)
  130. {
  131. if (dir == E_Direction.Vertical)
  132. {
  133. if (!((rectTrans.anchorMin == new Vector2(0, 1) && rectTrans.anchorMax == new Vector2(0, 1)) ||
  134. (rectTrans.anchorMin == new Vector2(0, 1) && rectTrans.anchorMax == new Vector2(1, 1))))
  135. {
  136. rectTrans.anchorMin = new Vector2(0, 1);
  137. rectTrans.anchorMax = new Vector2(1, 1);
  138. }
  139. }
  140. else
  141. {
  142. if (!((rectTrans.anchorMin == new Vector2(0, 1) && rectTrans.anchorMax == new Vector2(0, 1)) ||
  143. (rectTrans.anchorMin == new Vector2(0, 0) && rectTrans.anchorMax == new Vector2(0, 1))))
  144. {
  145. rectTrans.anchorMin = new Vector2(0, 0);
  146. rectTrans.anchorMax = new Vector2(0, 1);
  147. }
  148. }
  149. }
  150. // 实时刷新列表时用
  151. public virtual void UpdateList()
  152. {
  153. for (int i = 0, length = cellInfos.Length; i < length; i++)
  154. {
  155. CellInfo cellInfo = cellInfos[i];
  156. if (cellInfo.obj != null)
  157. {
  158. float rangePos = dir == E_Direction.Vertical ? cellInfo.pos.y : cellInfo.pos.x;
  159. if (!IsOutRange(rangePos))
  160. {
  161. Func(FuncCallBackFunc, cellInfo.obj, true);
  162. }
  163. }
  164. }
  165. }
  166. // 刷新某一项
  167. public void UpdateCell(int index)
  168. {
  169. CellInfo cellInfo = cellInfos[index - 1];
  170. if (cellInfo.obj != null)
  171. {
  172. float rangePos = dir == E_Direction.Vertical ? cellInfo.pos.y : cellInfo.pos.x;
  173. if (!IsOutRange(rangePos))
  174. {
  175. Func(FuncCallBackFunc, cellInfo.obj);
  176. }
  177. }
  178. }
  179. public virtual void ShowList(string numStr)
  180. {
  181. }
  182. public virtual void ShowList(int num)
  183. {
  184. minIndex = -1;
  185. maxIndex = -1;
  186. //-> 计算 Content 尺寸
  187. if (dir == E_Direction.Vertical)
  188. {
  189. float contentSize = (col + cellH) * Mathf.CeilToInt((float)num / lines);
  190. contentH = contentSize;
  191. contentW = contentRectTrans.sizeDelta.x;
  192. contentSize = contentSize < rectTrans.rect.height ? rectTrans.rect.height : contentSize;
  193. contentRectTrans.sizeDelta = new Vector2(contentW, contentSize);
  194. if (num != maxCount)
  195. {
  196. contentRectTrans.anchoredPosition = new Vector2(contentRectTrans.anchoredPosition.x, 0);
  197. }
  198. }
  199. else
  200. {
  201. float contentSize = (row + cellW) * Mathf.CeilToInt((float)num / lines);
  202. contentW = contentSize;
  203. contentH = contentRectTrans.sizeDelta.x;
  204. contentSize = contentSize < rectTrans.rect.width ? rectTrans.rect.width : contentSize;
  205. contentRectTrans.sizeDelta = new Vector2(contentSize, contentH);
  206. if (num != maxCount)
  207. {
  208. contentRectTrans.anchoredPosition = new Vector2(0, contentRectTrans.anchoredPosition.y);
  209. }
  210. }
  211. //-> 计算 开始索引
  212. int lastEndIndex = 0;
  213. //-> 过多的物体 扔到对象池 ( 首次调 ShowList函数时 则无效 )
  214. if (isInited)
  215. {
  216. lastEndIndex = num - maxCount > 0 ? maxCount : num;
  217. lastEndIndex = isClearList ? 0 : lastEndIndex;
  218. int count = isClearList ? cellInfos.Length : maxCount;
  219. for (int i = lastEndIndex; i < count; i++)
  220. {
  221. if (cellInfos[i].obj != null)
  222. {
  223. SetPoolsObj(cellInfos[i].obj);
  224. cellInfos[i].obj = null;
  225. }
  226. }
  227. }
  228. //-> 以下四行代码 在for循环所用
  229. CellInfo[] tempCellInfos = cellInfos;
  230. cellInfos = new CellInfo[num];
  231. //-> 1: 计算 每个Cell坐标并存储 2: 显示范围内的 Cell
  232. for (int i = 0; i < num; i++)
  233. {
  234. // * -> 存储 已有的数据 ( 首次调 ShowList函数时 则无效 )
  235. if (maxCount != -1 && i < lastEndIndex)
  236. {
  237. CellInfo tempCellInfo = tempCellInfos[i];
  238. //-> 计算是否超出范围
  239. float rPos = dir == E_Direction.Vertical ? tempCellInfo.pos.y : tempCellInfo.pos.x;
  240. if (!IsOutRange(rPos))
  241. {
  242. //-> 记录显示范围中的 首位index 和 末尾index
  243. minIndex = minIndex == -1 ? i : minIndex; //首位index
  244. maxIndex = i; // 末尾index
  245. if (tempCellInfo.obj == null)
  246. {
  247. tempCellInfo.obj = GetPoolsObj();
  248. }
  249. tempCellInfo.obj.transform.GetComponent<RectTransform>().anchoredPosition = tempCellInfo.pos;
  250. tempCellInfo.obj.name = i.ToString();
  251. tempCellInfo.obj.SetActive(true);
  252. Func(FuncCallBackFunc, tempCellInfo.obj);
  253. }
  254. else
  255. {
  256. SetPoolsObj(tempCellInfo.obj);
  257. tempCellInfo.obj = null;
  258. }
  259. cellInfos[i] = tempCellInfo;
  260. continue;
  261. }
  262. CellInfo cellInfo = new CellInfo();
  263. float pos = 0; //坐标( isVertical ? 记录Y : 记录X )
  264. float rowPos = 0; //计算每排里面的cell 坐标
  265. // * -> 计算每个Cell坐标
  266. if (dir == E_Direction.Vertical)
  267. {
  268. pos = cellH * Mathf.FloorToInt(i / lines) +
  269. col * Mathf.FloorToInt(i / lines);
  270. rowPos = cellW * (i % lines) + row * (i % lines);
  271. cellInfo.pos = new Vector3(rowPos, -pos, 0);
  272. }
  273. else
  274. {
  275. pos = cellW * Mathf.FloorToInt(i / lines) + row * Mathf.FloorToInt(i / lines);
  276. rowPos = cellH * (i % lines) + col * (i % lines);
  277. cellInfo.pos = new Vector3(pos, -rowPos, 0);
  278. }
  279. //-> 计算是否超出范围
  280. float cellPos = dir == E_Direction.Vertical ? cellInfo.pos.y : cellInfo.pos.x;
  281. if (IsOutRange(cellPos))
  282. {
  283. cellInfo.obj = null;
  284. cellInfos[i] = cellInfo;
  285. continue;
  286. }
  287. //-> 记录显示范围中的 首位index 和 末尾index
  288. minIndex = minIndex == -1 ? i : minIndex; //首位index
  289. maxIndex = i; // 末尾index
  290. //-> 取或创建 Cell
  291. GameObject cell = GetPoolsObj();
  292. cell.transform.GetComponent<RectTransform>().anchoredPosition = cellInfo.pos;
  293. cell.gameObject.name = i.ToString();
  294. //-> 存数据
  295. cellInfo.obj = cell;
  296. cellInfos[i] = cellInfo;
  297. //-> 回调 函数
  298. Func(FuncCallBackFunc, cell);
  299. }
  300. maxCount = num;
  301. isInited = true;
  302. OnDragListener(Vector2.zero);
  303. }
  304. /// <summary>
  305. /// 通过index定位到GameObject
  306. /// </summary>
  307. /// <param name="index"></param>
  308. public void GoToCellPos(int index)
  309. {
  310. // 当前索引所在行的第一个索引
  311. int theFirstIndex = index - index % lines;
  312. // 假设在第一行最大索引
  313. var tmpIndex = theFirstIndex + maxIndex;
  314. int theLastIndex = tmpIndex > maxCount - 1 ? maxCount - 1 : tmpIndex;
  315. // 如果最大索引就是边界的话,边界的
  316. if (theLastIndex == maxCount - 1)
  317. {
  318. // 余数不为0的情况下,第一个索引位置需要考虑最大数到最后显示位置的边距
  319. var shortOfNum = maxCount % lines == 0 ? 0 : lines - maxCount % lines;
  320. theFirstIndex = theLastIndex - maxIndex + shortOfNum;
  321. }
  322. Vector2 newPos = cellInfos[theFirstIndex].pos;
  323. if (dir == E_Direction.Vertical)
  324. {
  325. contentRectTrans.anchoredPosition = new Vector2(contentRectTrans.anchoredPosition.x, -newPos.y);
  326. }
  327. else
  328. {
  329. contentRectTrans.anchoredPosition = new Vector2(-newPos.x, contentRectTrans.anchoredPosition.y);
  330. }
  331. // print(string.Format("index: {0}, theFirstIndex: {1},maxIndex:{2} theLastIndex:{3}", index, theFirstIndex,
  332. // maxIndex, theLastIndex));
  333. }
  334. #if UNITY_EDITOR
  335. //public void LogRecycleView()
  336. //{
  337. // // 拿到容器基础信息
  338. // print("----------------------------------------------------------------------------");
  339. // print("Direction: " + dir);
  340. // print("Lines: " + lines);
  341. // print(string.Format("minIndex: {0} , maxIndex: {1}", minIndex, maxIndex));
  342. // print("Capacity: " + (maxIndex - minIndex + 1));
  343. // print("----------------------------------------------------------------------------");
  344. //}
  345. #endif
  346. // 更新滚动区域的大小
  347. public void UpdateSize()
  348. {
  349. Rect rect = GetComponent<RectTransform>().rect;
  350. planeH = rect.height;
  351. planeW = rect.width;
  352. }
  353. // 滑动事件
  354. protected virtual void ScrollRectListener(Vector2 value)
  355. {
  356. UpdateCheck();
  357. }
  358. private void UpdateCheck()
  359. {
  360. if (cellInfos == null) return;
  361. // 检查超出范围
  362. for (int i = 0, length = cellInfos.Length; i < length; i++)
  363. {
  364. CellInfo cellInfo = cellInfos[i];
  365. GameObject obj = cellInfo.obj;
  366. Vector3 pos = cellInfo.pos;
  367. float rangePos = dir == E_Direction.Vertical ? pos.y : pos.x;
  368. // 判断是否超出显示范围
  369. if (IsOutRange(rangePos))
  370. {
  371. // 把超出范围的cell 扔进 poolsObj里
  372. if (obj != null)
  373. {
  374. SetPoolsObj(obj);
  375. cellInfos[i].obj = null;
  376. }
  377. }
  378. else
  379. {
  380. if (obj == null)
  381. {
  382. // 优先从 poolsObj中 取出 (poolsObj为空则返回 实例化的cell)
  383. GameObject cell = GetPoolsObj();
  384. cell.transform.localPosition = pos;
  385. cell.gameObject.name = i.ToString();
  386. cellInfos[i].obj = cell;
  387. Func(FuncCallBackFunc, cell);
  388. }
  389. }
  390. }
  391. }
  392. // 判断是否超出显示范围
  393. protected bool IsOutRange(float pos)
  394. {
  395. Vector3 listP = contentRectTrans.anchoredPosition;
  396. if (dir == E_Direction.Vertical)
  397. {
  398. if (pos + listP.y > cellH || pos + listP.y < -rectTrans.rect.height)
  399. {
  400. return true;
  401. }
  402. }
  403. else
  404. {
  405. if (pos + listP.x < -cellW || pos + listP.x > rectTrans.rect.width)
  406. {
  407. return true;
  408. }
  409. }
  410. return false;
  411. }
  412. //取出 cell
  413. protected virtual GameObject GetPoolsObj()
  414. {
  415. GameObject cell = null;
  416. if (Pool.Count > 0) cell = Pool.Pop();
  417. if (cell == null) cell = Instantiate(this.cell) as GameObject;
  418. cell.transform.SetParent(content.transform);
  419. cell.transform.localScale = Vector3.one;
  420. SetActive(cell, true);
  421. return cell;
  422. }
  423. //存入 cell
  424. protected virtual void SetPoolsObj(GameObject cell)
  425. {
  426. if (cell != null)
  427. {
  428. Pool.Push(cell);
  429. SetActive(cell, false);
  430. }
  431. }
  432. //回调
  433. protected void Func(Action<GameObject, int> func, GameObject selectObject, bool isUpdate = false)
  434. {
  435. int index = int.Parse(selectObject.name);
  436. if (func != null)
  437. {
  438. func(selectObject, index);
  439. }
  440. }
  441. public void DisposeAll()
  442. {
  443. if (FuncCallBackFunc != null) FuncCallBackFunc = null;
  444. if (FuncOnClickCallBack != null) FuncOnClickCallBack = null;
  445. if (FuncOnButtonClickCallBack != null) FuncOnButtonClickCallBack = null;
  446. }
  447. protected void OnDestroy()
  448. {
  449. DisposeAll();
  450. }
  451. public virtual void OnClickCell(GameObject cell)
  452. {
  453. }
  454. //-> ExpandCircularScrollView 函数
  455. public virtual void OnClickExpand(int index)
  456. {
  457. }
  458. //-> FlipCircularScrollView 函数
  459. public virtual void SetToPageIndex(int index)
  460. {
  461. }
  462. public virtual void OnBeginDrag(PointerEventData eventData)
  463. {
  464. }
  465. public void OnDrag(PointerEventData eventData)
  466. {
  467. }
  468. public virtual void OnEndDrag(PointerEventData eventData)
  469. {
  470. }
  471. protected void OnDragListener(Vector2 value)
  472. {
  473. float normalizedPos = dir == E_Direction.Vertical
  474. ? scrollRect.verticalNormalizedPosition
  475. : scrollRect.horizontalNormalizedPosition;
  476. if (dir == E_Direction.Vertical)
  477. {
  478. if (contentH - rectTrans.rect.height < 10)
  479. {
  480. SetActive(firstArrow, false);
  481. SetActive(endArrow, false);
  482. return;
  483. }
  484. }
  485. else
  486. {
  487. if (contentW - rectTrans.rect.width < 10)
  488. {
  489. SetActive(firstArrow, false);
  490. SetActive(endArrow, false);
  491. return;
  492. }
  493. }
  494. if (normalizedPos >= 0.9)
  495. {
  496. SetActive(firstArrow, false);
  497. SetActive(endArrow, true);
  498. }
  499. else if (normalizedPos <= 0.1)
  500. {
  501. SetActive(firstArrow, true);
  502. SetActive(endArrow, false);
  503. }
  504. else
  505. {
  506. SetActive(firstArrow, true);
  507. SetActive(endArrow, true);
  508. }
  509. }
  510. public GameObject GetCellGameObject(int index)
  511. {
  512. // 为了保证拿到正确数据,根据index应该-1拿到正确数据
  513. return cellInfos[--index].obj;
  514. }
  515. public int GetCellIndex(GameObject obj)
  516. {
  517. // 第0号是模板,所以列表中索引应该-1
  518. return Convert.ToInt32(obj.name) - 1;
  519. }
  520. private static void SetActive(GameObject obj, bool isActive)
  521. {
  522. if (obj != null)
  523. {
  524. obj.SetActive(isActive);
  525. }
  526. }
  527. }