基本结构就是: Scoll(Scroll Rect) > View(Mask) > Content > Items
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; // 简单的无限循环列表,竖向滑动 // 第一个可以运行的版本,效率不高,拓展性不好,代码丑陋,还没有加上真实的数据 // 后续还可以加上的优化:使用Grid Layout组织Item,不用手动计算座标 // 无限循环列表的关键思路是,在Scroll Rect中把滑动的限制取消,可以无限滑动 // 实际的内容位置由自己手动维护 public class ISV : MonoBehaviour { // Content对象,所有的Item都是Content的子对象 private RectTransform m_Content = null; public GameObject Item; // 列表中每一项的模板 public Vector2 CellSize; // 每一项的大小 // 为了支持多列 private int HorizontalItemCount; // 计算每一行有几个Item private int VerticalItemCount; // 计算有几列能覆盖满整个View private int AllItemCount; // 上面两个相乘:一共需要生成的Item的数量 private bool m_Inited = false; // 这个应该是使用双端队列来做的,两个方向都有添加、删除的需求 private List<GameObject> m_Items = new List<GameObject>(); // Item UI的列表 // Start is called before the first frame update void Start() { m_Content = transform.Find("View/Content").GetComponent<RectTransform>(); Vector2 ContentSize = m_Content.sizeDelta; // 计算Item数量 HorizontalItemCount = (int)(ContentSize.x / CellSize.x); VerticalItemCount = (int)(ContentSize.y / CellSize.y) + 3; AllItemCount = HorizontalItemCount * VerticalItemCount; // 按照行列生成Item int nowIndex = 0; float nowY = CellSize.y; for (int i = 0; i < VerticalItemCount; i++) { float nowX = 0; for (int j = 0; j < HorizontalItemCount; j++) { // 生成Item var itemObj = GameObject.Instantiate(Item) as GameObject; itemObj.name = "item_" + (++nowIndex); itemObj.transform.SetParent(m_Content, false); itemObj.transform.localScale = Vector3.one; itemObj.GetComponent<RandomColor>().SetData(nowIndex); // 这个是Item需要实现的一个接口,很简单,为了做区分 RectTransform Rect = itemObj.GetComponent<RectTransform>(); if (Rect != null) { // 注意使用的这个anchoredPosition Rect.anchoredPosition = new Vector2(nowX, nowY); // 这里的座标都是自己维护的 } m_Items.Add(itemObj); nowX += CellSize.x; } nowY -= CellSize.y; } Debug.Log("Init Done"); } // Update is called once per frame void Update() { if(!m_Inited) { m_Inited = true; } for(int i=0;i<3;i+=1) { // 列表上面满了吗 bool TopFull = Out(m_Items[0].GetComponent<RectTransform>()); // 列表下面满了吗 bool BottomFull = Out(m_Items[AllItemCount - 1].GetComponent<RectTransform>()); if (!(TopFull && BottomFull)) { // 有一方满了,另一方没有满的话,就把整行移过去 if (TopFull) { Move(true); } else if (BottomFull) { Move(false); } } else break; } } private bool Out(RectTransform itemTrans) { float viewY = itemTrans.anchoredPosition.y + m_Content.anchoredPosition.y; // 判断满没满按照座标来 return viewY > CellSize.y * 1.8 || viewY < (-m_Content.sizeDelta.y - CellSize.y * 0.8); } // 移动一整行,在双端队列中需要移动,Item UI的座标也需要重新计算 private void Move(bool TopToBottom) { if (TopToBottom) { List<GameObject> Cache = new List<GameObject>(); for (int i = 0; i < HorizontalItemCount; i += 1) { Cache.Add(m_Items[0]); m_Items.RemoveAt(0); } float nowX = 0, nowY = m_Items[m_Items.Count - 1].GetComponent<RectTransform>().anchoredPosition.y - CellSize.y; for (int i = 0; i < HorizontalItemCount; i += 1) { Cache[i].GetComponent<RectTransform>().anchoredPosition = new Vector2(nowX, nowY); m_Items.Add(Cache[i]); nowX += CellSize.x; } } else { List<GameObject> Cache = new List<GameObject>(); for (int i = 0; i < HorizontalItemCount; i += 1) { Cache.Add(m_Items[AllItemCount - HorizontalItemCount]); m_Items.RemoveAt(AllItemCount - HorizontalItemCount); } float nowX = 0, nowY = m_Items[0].GetComponent<RectTransform>().anchoredPosition.y + CellSize.y; for (int i = 0; i < HorizontalItemCount; i += 1) { Cache[i].GetComponent<RectTransform>().anchoredPosition = new Vector2(nowX, nowY); m_Items.Insert(i, Cache[i]); nowX += CellSize.x; } } } }