UIScrollScript.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. using UnityEngine;
  2. using System.Collections;
  3. using UnityEngine.UI;
  4. using System.Collections.Generic;
  5. /// <summary>
  6. /// 设置Item内容的委托
  7. /// </summary>
  8. /// <param name="item">Item对象</param>
  9. /// <param name="wrapIndex">Item在Grid中的序号</param>
  10. /// <param name="realIndex">当前Item在List中的序号</param>
  11. public delegate void OnUpdateItem(GameObject item, int wrapIndex, int realIndex);
  12. /// <summary>
  13. /// 已显示了所有item的委托
  14. /// </summary>
  15. public delegate void OnEatUpItem();
  16. ///注意:当横排滚动时,不能使用横向拉伸布局,不然会有一些额外的多余尺寸问题
  17. /// 同理,当竖排滚动时,不能使用纵向拉伸布局
  18. /// <summary>
  19. /// 排列方式枚举
  20. /// </summary>
  21. public enum ArrangeType
  22. {
  23. Horizontal = 0,//水平排列
  24. Vertical = 1,//垂直排列
  25. }
  26. public class UIScrollScript : MonoBehaviour
  27. {
  28. /// <summary>
  29. /// Item的宽
  30. /// </summary>
  31. public int cell_x = 100;
  32. /// <summary>
  33. /// Item的高
  34. /// </summary>
  35. public int cell_y = 100;
  36. /// <summary>
  37. /// Item最小索引
  38. /// </summary>
  39. public int minIndex = 0;
  40. /// <summary>
  41. /// Item最大数量
  42. /// </summary>
  43. public int maxCount = 0;
  44. /// <summary>
  45. /// 排列方式
  46. /// </summary>
  47. public ArrangeType arrangeType = ArrangeType.Horizontal;
  48. /// <summary>
  49. /// 行列的个数
  50. /// </summary>
  51. public int ConstraintCount = 0;
  52. /// <summary>
  53. /// 当前RectTransform对象
  54. /// </summary>
  55. RectTransform mRTrans;
  56. /// <summary>
  57. /// ScrollRect组件
  58. /// </summary>
  59. public ScrollRect mScroll;
  60. /// <summary>
  61. /// Pivot为中心点的坐标
  62. /// </summary>
  63. Vector2 pivotChildFactor = new Vector2(0.5f, 0.5f);
  64. /// <summary>
  65. /// 滚动方向
  66. /// </summary>
  67. bool mHorizontal;
  68. /// <summary>
  69. /// 子物体集合
  70. /// </summary>
  71. List<Transform> ChildList = new List<Transform>();
  72. /// <summary>
  73. /// 初始化时区域的长度或者高度的一半
  74. /// </summary>
  75. private float extents = 0;
  76. /// <summary>
  77. /// SrollRect的尺寸
  78. /// </summary>
  79. Vector2 SR_size = Vector2.zero;
  80. /// <summary>
  81. /// SrollRect的尺寸
  82. /// </summary>
  83. Vector3[] conners = new Vector3[4];
  84. /// <summary>
  85. /// ScrollRect的初始位置
  86. /// </summary>
  87. Vector2 startPos;
  88. /// <summary>
  89. /// 设置Item的委托
  90. /// </summary>
  91. public OnUpdateItem onUpdateItem;
  92. public OnEatUpItem onEatUpItem;
  93. private bool isHandEatup = false;
  94. [SerializeField] private float leftOffset = 0;
  95. [SerializeField] private float rightOffset = 0;
  96. [SerializeField] private float topOffset = 0;
  97. [SerializeField] private float bottomOffset = 0;
  98. /// <summary>
  99. /// 一行中元素的间距
  100. /// </summary>
  101. [SerializeField] private float XSpacing = 0;
  102. /// <summary>
  103. /// 一列中元素的间距
  104. /// </summary>
  105. [SerializeField] private float YSpacing = 0;
  106. /// <summary>
  107. /// 初始化赋值(目前只会执行一次)
  108. /// </summary>
  109. public void Init()
  110. {
  111. mRTrans = transform.GetComponent<RectTransform>();
  112. mHorizontal = mScroll.horizontal;
  113. SR_size = mScroll.transform.GetComponent<RectTransform>().rect.size;
  114. //四角坐标 横着数
  115. conners[0] = new Vector3(-SR_size.x / 2f, SR_size.y / 2f, 0);
  116. conners[1] = new Vector3(SR_size.x / 2f, SR_size.y / 2f, 0);
  117. conners[2] = new Vector3(-SR_size.x / 2f, -SR_size.y / 2f, 0);
  118. conners[3] = new Vector3(SR_size.x / 2f, -SR_size.y / 2f, 0);
  119. //将四角坐标转化为绝对的坐标值
  120. for (int i = 0; i < 4; i++)
  121. {
  122. Vector3 temp = mScroll.transform.TransformPoint(conners[i]);
  123. conners[i].x = temp.x;
  124. conners[i].y = temp.y;
  125. }
  126. mRTrans.pivot = new Vector2(0, 1);//设置mRTrans的中心在左上角
  127. mScroll.onValueChanged.AddListener(delegate { WrapContent(); });//添加滚动事件回调
  128. startPos = this.transform.localPosition;
  129. }
  130. /// <summary>
  131. /// ScrollRect复位(比如:重新进入该页面时)
  132. /// </summary>
  133. public void ResetPosition()
  134. {
  135. this.transform.localPosition = startPos;
  136. }
  137. [ContextMenu("ResetToInit")]
  138. public void ResetToInit()
  139. {
  140. ResetPosition();
  141. Init();
  142. InitList(minIndex, maxCount);
  143. }
  144. /// <summary>
  145. /// 初始化mChild链表
  146. /// </summary>
  147. ///
  148. public void InitList(int minIndex, int maxIndex)
  149. {
  150. isHandEatup = false;
  151. this.minIndex = minIndex;
  152. this.maxCount = maxIndex;
  153. //如果横排数量为0,则设置默认为1,避免手误
  154. if (ConstraintCount <= 0)
  155. {
  156. ConstraintCount = 1;
  157. }
  158. //如果最小索引大于了最大索引,则让他俩相等,避免手误
  159. if (this.minIndex > this.maxCount)
  160. {
  161. this.minIndex = this.maxCount;
  162. }
  163. //Init();
  164. ChildList.Clear();
  165. for (int i = 0; i < transform.childCount; i++)
  166. {
  167. ChildList.Add(this.transform.GetChild(i));
  168. }
  169. for (int j = 0; j < ChildList.Count; j++)
  170. {
  171. Transform temp = ChildList[j];
  172. RectTransform trf = temp as RectTransform;
  173. trf.anchorMax = new Vector2(0, 1);
  174. trf.anchorMin = new Vector2(0, 1);
  175. trf.pivot = pivotChildFactor;
  176. trf.anchoredPosition3D = Vector3.zero;
  177. }
  178. ResetChildPosition();
  179. }
  180. /// <summary>
  181. /// 更新最大索引数
  182. /// </summary>
  183. /// <param name="minIndex"></param>
  184. /// <param name="maxIndex"></param>
  185. public void UpdateCount(int minIndex, int maxIndex)
  186. {
  187. this.minIndex = minIndex;
  188. this.maxCount = maxIndex;
  189. isHandEatup = false;
  190. }
  191. void ResetChildPosition()
  192. {
  193. ///行数 列数
  194. int rows = 1, cols = 1;
  195. //初始位置
  196. Vector2 startAxis = new Vector2((cell_x / 2f) + leftOffset, (-cell_y / 2f) - topOffset);
  197. if (arrangeType == ArrangeType.Horizontal)
  198. {
  199. rows = ConstraintCount;
  200. cols = (int)Mathf.Ceil((float)ChildList.Count / rows);
  201. //(所有子物体宽度的总和 + 所有间距的总和 + 左偏移量 + 右偏移量)/2
  202. extents = ((cols * cell_x) + (XSpacing * (cols - 1) + leftOffset + rightOffset)) * 0.5f;
  203. }
  204. else if (arrangeType == ArrangeType.Vertical)
  205. {
  206. cols = ConstraintCount;
  207. rows = (int)Mathf.Ceil((float)ChildList.Count / (float)cols);
  208. //(所有子物体高度的总和 + 所有间距的总和 + 上偏移量 + 下偏移量)/2
  209. extents = ((rows * cell_y) + (YSpacing * (rows - 1) + topOffset + bottomOffset)) * 0.5f;
  210. }
  211. ///初始为所有子物体排列布局
  212. for (int i = 0; i < ChildList.Count; i++)
  213. {
  214. Transform temp = ChildList[i];
  215. RectTransform trf = temp as RectTransform;
  216. trf.anchorMax = new Vector2(0, 1);
  217. trf.anchorMin = new Vector2(0, 1);
  218. trf.pivot = pivotChildFactor;
  219. trf.anchoredPosition3D = Vector3.zero;
  220. int x = 0, y = 0;//行列号
  221. Vector2 pos = Vector2.zero;
  222. if (arrangeType == ArrangeType.Vertical)
  223. {
  224. x = i / cols; //(行号:0 -1 - 2 - 3 - 4...)
  225. y = i % cols;//(列号:0 - 1 - 0 - 1...)
  226. pos = new Vector2((startAxis.x + y * cell_x) + y * XSpacing, (startAxis.y - x * cell_y) - (x * YSpacing));
  227. }
  228. else if (arrangeType == ArrangeType.Horizontal)
  229. {
  230. x = i % rows;//行号:0 - 1 - 0 - 1...
  231. y = i / rows;//列号:0 -1 - 2 - 3 - 4...
  232. pos = new Vector2((startAxis.x + y * cell_x) + y * XSpacing, (startAxis.y - x * cell_y) - (x * YSpacing));
  233. }
  234. temp.localPosition = pos;
  235. if (minIndex == maxCount || (i >= minIndex && i < maxCount))
  236. {
  237. temp.gameObject.SetActive(true);
  238. UpdateRectsize(pos);//更新panel的尺寸
  239. UpdateItem(temp, i, i);
  240. temp.name = i.ToString();
  241. }
  242. else
  243. {
  244. temp.gameObject.SetActive(false);
  245. }
  246. }
  247. }
  248. /// <summary>
  249. /// 更新Content的尺寸
  250. /// </summary>
  251. /// <param name="pos"></param>
  252. void UpdateRectsize(Vector2 pos)
  253. {
  254. if (arrangeType == ArrangeType.Horizontal)
  255. {
  256. mRTrans.sizeDelta = new Vector2(pos.x + cell_x * 0.5f + rightOffset, ConstraintCount * cell_y + (ConstraintCount - 1) * YSpacing + topOffset + bottomOffset);
  257. }
  258. else
  259. {
  260. mRTrans.sizeDelta = new Vector2(ConstraintCount * cell_x + (ConstraintCount - 1) * XSpacing + leftOffset + rightOffset
  261. , -pos.y + cell_y * 0.5f + bottomOffset);
  262. }
  263. }
  264. /// <summary>
  265. /// ScrollRect的回调
  266. /// </summary>
  267. public void WrapContent(bool isRefresh = false)
  268. {
  269. for (int i = 0; i < ChildList.Count; i++)
  270. {
  271. Transform temp = ChildList[i];
  272. bool isRefreshRect = false;
  273. Vector2 pos = GetChildPos(temp, out isRefreshRect);
  274. int realIndex = GetRealIndex(pos);
  275. if (minIndex == maxCount || (realIndex >= minIndex && realIndex < maxCount))
  276. {
  277. temp.gameObject.SetActive(true);
  278. if (isRefreshRect)
  279. {
  280. //Debug.Log("是否需要刷新");
  281. UpdateRectsize(pos);
  282. }
  283. temp.localPosition = pos;
  284. if (temp.name != realIndex.ToString())
  285. {
  286. //设置Item内容
  287. UpdateItem(temp, i, realIndex);
  288. temp.name = realIndex.ToString();
  289. }
  290. else if (temp.name == realIndex.ToString() && isRefresh)
  291. {
  292. //设置Item内容
  293. UpdateItem(temp, i, realIndex);
  294. temp.name = realIndex.ToString();
  295. }
  296. }
  297. else if (realIndex >= maxCount)
  298. {
  299. if (!isHandEatup)
  300. {
  301. Debug.Log("WrapContent===>数据已用完");
  302. //通知所有要显示的数据已用完
  303. if (onEatUpItem != null)
  304. {
  305. onEatUpItem();
  306. }
  307. isHandEatup = true;
  308. }
  309. return;
  310. }
  311. }
  312. }
  313. /// <summary>
  314. /// 获取子物体的位置
  315. /// </summary>
  316. /// <param name="temp"></param>
  317. /// <param name="isRefreshRect"></param>
  318. /// <returns></returns>
  319. Vector2 GetChildPos(Transform temp, out bool isRefreshRect)
  320. {
  321. isRefreshRect = false;
  322. Vector3[] conner_local = new Vector3[4];
  323. for (int i = 0; i < 4; i++)
  324. {
  325. conner_local[i] = this.transform.InverseTransformPoint(conners[i]);
  326. }
  327. //计算ScrollRect的中心坐标 相对于this的坐标
  328. Vector2 center = (conner_local[3] + conner_local[0]) / 2f;
  329. Vector2 pos = Vector2.zero;
  330. if (mHorizontal)
  331. {
  332. float distance = temp.localPosition.x - center.x;
  333. pos = temp.localPosition;
  334. if (distance < -extents)
  335. {
  336. isRefreshRect = true;
  337. pos.x += extents * 2f - leftOffset - rightOffset + XSpacing;
  338. }
  339. else if (distance > extents)
  340. {
  341. pos.x -= extents * 2f - leftOffset - rightOffset + XSpacing;
  342. }
  343. }
  344. else
  345. {
  346. if (temp != null)
  347. {
  348. float distance = temp.localPosition.y - center.y;
  349. pos = temp.localPosition;
  350. if (distance < -extents)
  351. {
  352. pos.y += extents * 2f - bottomOffset - topOffset + YSpacing;
  353. }
  354. else if (distance > extents)
  355. {
  356. isRefreshRect = true;
  357. pos.y -= extents * 2f - bottomOffset - topOffset + YSpacing;
  358. }
  359. }
  360. }
  361. return pos;
  362. }
  363. /// <summary>
  364. /// 计算realIndex
  365. /// </summary>
  366. /// <param name="pos"></param>
  367. /// <returns></returns>
  368. private int GetRealIndex(Vector2 pos)
  369. {
  370. int x = (int)Mathf.Ceil((-pos.y - topOffset + YSpacing) / (cell_y + YSpacing)) - 1;
  371. int y = (int)Mathf.Ceil((pos.x - leftOffset + XSpacing) / (cell_x + XSpacing)) - 1;
  372. //int x = (int)Mathf.Ceil(-pos.y / cell_y) - 1;//行号
  373. //int y = (int)Mathf.Ceil(pos.x / cell_x) - 1;//列号
  374. int realIndex;
  375. if (arrangeType == ArrangeType.Vertical)
  376. {
  377. realIndex = x * ConstraintCount + y;
  378. }
  379. else
  380. {
  381. realIndex = x + ConstraintCount * y;
  382. }
  383. return realIndex;
  384. }
  385. /// <summary>
  386. /// 通知更新Item的数据
  387. /// </summary>
  388. /// <param name="item"></param>
  389. /// <param name="index"></param>
  390. /// <param name="realIndex"></param>
  391. void UpdateItem(Transform item, int index, int realIndex)
  392. {
  393. if (onUpdateItem != null)
  394. {
  395. onUpdateItem(item.gameObject, index, realIndex);
  396. }
  397. }
  398. }