ScrollPanel.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.UI;
  5. using UnityEngine.Events;
  6. using UnityEngine.EventSystems;
  7. namespace JC
  8. {
  9. public class ScrollPanel : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, IPointerDownHandler, IPointerUpHandler
  10. {
  11. public enum InitType {
  12. Statics, Dynamic
  13. }
  14. public InitType initType = InitType.Statics;
  15. ScrollRect _scrollRect;
  16. RectTransform _rectTransform;
  17. ScrollRect scrollRect {
  18. get {
  19. if (!_scrollRect)
  20. {
  21. _scrollRect = this.GetComponent<ScrollRect>();
  22. }
  23. return _scrollRect;
  24. }
  25. }
  26. RectTransform rectTransform {
  27. get {
  28. if (!_rectTransform)
  29. {
  30. _rectTransform = this.GetComponent<RectTransform>();
  31. }
  32. return _rectTransform;
  33. }
  34. }
  35. //解决【Content节点的AnchorPresets在Inspector中设置成了Center,但运行时的效果却是LeftTop】的问题。
  36. Vector3 contentPosition {
  37. get {
  38. Vector3 v3 = scrollRect.content.localPosition;
  39. v3.x -= rectTransform.sizeDelta.x / 2;
  40. v3.y += rectTransform.sizeDelta.y / 2;
  41. return v3;
  42. }
  43. set {
  44. value.x += rectTransform.sizeDelta.x / 2;
  45. value.y -= rectTransform.sizeDelta.y / 2;
  46. scrollRect.content.localPosition = value;
  47. }
  48. }
  49. public List<RectTransform> itemList = new List<RectTransform>();
  50. RectTransform minItem {
  51. get {
  52. RectTransform bestItem = null;
  53. foreach (RectTransform item in itemList)
  54. {
  55. if (!bestItem || item.localPosition.x < bestItem.localPosition.x)
  56. {
  57. bestItem = item;
  58. }
  59. }
  60. return bestItem;
  61. }
  62. }
  63. RectTransform maxItem {
  64. get {
  65. RectTransform bestItem = null;
  66. foreach (RectTransform item in itemList)
  67. {
  68. if (!bestItem || item.localPosition.x > bestItem.localPosition.x)
  69. {
  70. bestItem = item;
  71. }
  72. }
  73. return bestItem;
  74. }
  75. }
  76. public int startIndex = 0;
  77. public int spacing = 0;
  78. public bool openReceiveItemViewInfo = false;
  79. public UnityEvent<RectTransform, Vector3, Vector2> onReceiveItemViewInfo;
  80. [Tooltip("以下节点将作为预制体,不加入ItemList中作为滚动内容,组件初始化时会隐藏预制体。")]
  81. int currentItemIndex = -1;
  82. void SetCurrentItemIndex(int index)
  83. {
  84. if (index != currentItemIndex)
  85. {
  86. currentItemIndex = index;
  87. onItemChange.Invoke(itemList[index], index);
  88. }
  89. }
  90. public UnityEvent<RectTransform, int> onItemChange;
  91. public List<GameObject> prefabs;
  92. void Start()
  93. {
  94. for (int i = 0; i < scrollRect.content.childCount; i++)
  95. {
  96. RectTransform item = scrollRect.content.GetChild(i).GetComponent<RectTransform>();
  97. if (prefabs.Contains(item.gameObject)) {
  98. item.gameObject.SetActive(false);
  99. } else {
  100. if (initType == InitType.Statics)
  101. {
  102. AddItem(item);
  103. }
  104. }
  105. }
  106. UpdateItems();
  107. SelectItemByStartIndex();
  108. }
  109. public void SelectItemByStartIndex()
  110. {
  111. if (startIndex >= 0 && startIndex < itemList.Count) {
  112. SetCurrentItemIndex(startIndex);
  113. MoveItemToCenter(itemList[startIndex]);
  114. }
  115. }
  116. bool snapping = false;
  117. int snapIndex = 0;
  118. void Update()
  119. {
  120. if (snapping) {
  121. if (snapIndex >= 0 && snapIndex < itemList.Count) {
  122. RectTransform item = itemList[snapIndex];
  123. MoveItemToCenter(item);
  124. } else if (snapIndex >= itemList.Count) {
  125. snapIndex = itemList.Count - 1;
  126. }
  127. }
  128. }
  129. public void UpdateItems()
  130. {
  131. if (itemList.Count == 0) return;
  132. if (!scrollRect.horizontal) return;
  133. RectTransform minItemRecord = minItem;
  134. RectTransform maxItemRecord = maxItem;
  135. float centerX = -contentPosition.x;
  136. float deltaForMin = Mathf.Abs(minItemRecord.localPosition.x - centerX);
  137. float deltaForMax = Mathf.Abs(maxItemRecord.localPosition.x - centerX);
  138. if (deltaForMin < deltaForMax) {
  139. RectTransform item = minItemRecord;
  140. while (item && IsInMinBound(item)) {
  141. item = RenderNextItem(item, -1);
  142. }
  143. } else if (deltaForMax < deltaForMin) {
  144. RectTransform item = maxItemRecord;
  145. while (item && IsInMaxBound(item)) {
  146. item = RenderNextItem(item, 1);
  147. }
  148. }
  149. if (openReceiveItemViewInfo)
  150. {
  151. Vector3 contentPoint = contentPosition;
  152. foreach (var item in itemList)
  153. {
  154. Vector3 positinInView = contentPoint + item.localPosition;
  155. Vector2 viewSize = rectTransform.sizeDelta;
  156. onReceiveItemViewInfo.Invoke(item, positinInView, viewSize);
  157. }
  158. }
  159. }
  160. bool IsInMinBound(RectTransform item)
  161. {
  162. return item.localPosition.x + contentPosition.x - item.sizeDelta.x / 2
  163. > -rectTransform.sizeDelta.x / 2;
  164. }
  165. bool IsInMaxBound(RectTransform item)
  166. {
  167. return item.localPosition.x + contentPosition.x + item.sizeDelta.x / 2
  168. < rectTransform.sizeDelta.x / 2;
  169. }
  170. bool IsOutBound(RectTransform item)
  171. {
  172. if (item.localPosition.x + contentPosition.x + item.sizeDelta.x / 2
  173. < -rectTransform.sizeDelta.x / 2) return true;
  174. if (item.localPosition.x + contentPosition.x - item.sizeDelta.x / 2
  175. > rectTransform.sizeDelta.x / 2) return true;
  176. return false;
  177. }
  178. RectTransform RenderNextItem(RectTransform item, int relativeIndex) {
  179. int index = itemList.IndexOf(item);
  180. index += relativeIndex;
  181. if (index < 0) {
  182. index = itemList.Count - 1;
  183. } else if (index >= itemList.Count) {
  184. index = 0;
  185. }
  186. RectTransform targetItem = itemList[index];
  187. if (IsOutBound(targetItem)) {
  188. Vector3 position = targetItem.localPosition;
  189. position.x = item.localPosition.x + relativeIndex * (targetItem.sizeDelta.x + spacing);
  190. targetItem.localPosition = position;
  191. return targetItem;
  192. }
  193. return null;
  194. }
  195. public void AddItem(RectTransform item)
  196. {
  197. if (scrollRect.horizontal)
  198. {
  199. item.SetParent(scrollRect.content);
  200. Vector3 position = item.localPosition;
  201. position.x = 0;
  202. if (itemList.Count > 0) {
  203. RectTransform lastItem = itemList[itemList.Count - 1];
  204. foreach (RectTransform child in itemList)
  205. {
  206. if (child.localPosition.x > lastItem.localPosition.x) {
  207. Vector3 childPosition = child.localPosition;
  208. childPosition.x += child.sizeDelta.x + spacing;
  209. child.localPosition = childPosition;
  210. }
  211. }
  212. position.x = lastItem.localPosition.x + item.sizeDelta.x + spacing;
  213. }
  214. position.y = 0;
  215. position.z = 0;
  216. item.localPosition = position;
  217. }
  218. itemList.Add(item);
  219. }
  220. public void RemoveItem(RectTransform item)
  221. {
  222. if (itemList.IndexOf(item) == currentItemIndex) {
  223. currentItemIndex = -1;
  224. }
  225. itemList.Remove(item);
  226. if (scrollRect.horizontal)
  227. {
  228. foreach (RectTransform child in itemList)
  229. {
  230. if (child.localPosition.x > item.localPosition.x) {
  231. Vector3 childPosition = child.localPosition;
  232. childPosition.x -= child.sizeDelta.x + spacing;
  233. child.localPosition = childPosition;
  234. }
  235. }
  236. }
  237. Destroy(item.gameObject);
  238. }
  239. public void ClearItems()
  240. {
  241. foreach (var item in itemList)
  242. {
  243. Destroy(item.gameObject);
  244. }
  245. itemList.Clear();
  246. currentItemIndex = -1;
  247. }
  248. RectTransform FindNearestItemToCenter()
  249. {
  250. RectTransform bestItem = null;
  251. Vector3 position = contentPosition;
  252. foreach(RectTransform item in itemList)
  253. {
  254. if (
  255. !bestItem ||
  256. Mathf.Abs(item.localPosition.x + position.x - 0)
  257. < Mathf.Abs(bestItem.localPosition.x + position.x - 0)
  258. ) {
  259. bestItem = item;
  260. }
  261. }
  262. return bestItem;
  263. }
  264. public void MoveNearestItemToCenter()
  265. {
  266. if (itemList.Count == 0) return;
  267. RectTransform item = FindNearestItemToCenter();
  268. snapping = true;
  269. snapIndex = itemList.IndexOf(item);
  270. SetCurrentItemIndex(snapIndex);
  271. }
  272. void MoveItemToCenter(RectTransform item)
  273. {
  274. Vector3 position = contentPosition;
  275. position.x -= (item.localPosition.x + contentPosition.x);
  276. if (Mathf.Abs(contentPosition.x - position.x) < 1) {
  277. snapping = false;
  278. }
  279. contentPosition = snapping ? Vector3.Lerp(contentPosition, position, Time.deltaTime * 5) : position;
  280. UpdateItems();
  281. }
  282. public void MoveNextToCenter(int direction)
  283. {
  284. if (itemList.Count == 0) return;
  285. if (direction == 0)
  286. {
  287. MoveNearestItemToCenter();
  288. return;
  289. }
  290. RectTransform item = FindNearestItemToCenter();
  291. int index = itemList.IndexOf(item);
  292. if (direction < 0)
  293. {
  294. index--;
  295. }
  296. if (direction > 0)
  297. {
  298. index++;
  299. }
  300. if (index < 0)
  301. {
  302. index = itemList.Count - 1;
  303. }
  304. if (index >= itemList.Count)
  305. {
  306. index = 0;
  307. }
  308. item = itemList[index];
  309. snapping = true;
  310. snapIndex = index;
  311. SetCurrentItemIndex(snapIndex);
  312. }
  313. public void OnBeginDrag(PointerEventData eventData) {
  314. snapping = false;
  315. }
  316. public void OnDrag(PointerEventData eventData)
  317. {
  318. UpdateItems();
  319. }
  320. public void OnEndDrag(PointerEventData eventData)
  321. {
  322. MoveNearestItemToCenter();
  323. }
  324. public void OnPointerDown(PointerEventData eventData) {
  325. snapping = false;
  326. }
  327. public void OnPointerUp(PointerEventData eventData) {
  328. snapping = true;
  329. }
  330. }
  331. }