還記得大學畢業剛工做的時候是作flash的開發,那時候看到別人寫的各類各樣的UI組件就很是佩服,後來本身也慢慢嘗試着寫,發現其實也就那麼回事。UI的開發其實技術的成分相對來講不算多,可是一個好的UI是絕對少不了底層組件的支持的。我我的認爲UI組件中相對比較複雜的就是List了,因此,這兩天實現了一個UGUI的list,寫了好幾個版本,最終這個版本是相對比較好用的,在這我介紹一下大概思路,一是鞏固一下知識作個記錄,二是發揚一下分享精神。嘿嘿,你們多多賜教。html
寫List有兩個重點是須要考慮的:ide
1.list中的item總數問題,剛打開的時候若是同時生成多個item會有卡頓的現象,50個,100個可能沒問題可是1000個2000個就比較難搞了。oop
2.當list滑動時如何加載後面的item,通常的邏輯應該是這樣的:每當滑動到紅線的位置就生成後面一列的item,可是作過UI開發的人知道,這種方法作作demo能夠,可是現實項目中基本沒這麼用的。以下圖性能
怎麼解決這兩個問題呢?其實很簡單,想想就知道,其實對於list來講咱們操做的是數據,並且咱們最多隻能看到scroll view裏面的 (行數*列數 + 1*列數 )這個多個item,因此理論上來講只須要生成(行數*列數 + 1*列數 )個item就能夠。測試
可是,仍是不行!why?由於仍是會出現「生成之後立刻就要顯示」的問題,理論跟現實是有差距的,顯示中就算再快的機器再快的性能生成一個item的時間也不可能爲0,因此若是一個item生成的時間和它被顯示的時間重疊,在體驗上確定好不了。spa
因此咱們還要再多生成3列,多出來的幾列就至關於一個緩衝。code
好吧,我直接說個人邏輯吧。orm
1.生成行數*列數+n*列數個item,這裏n實際上是一個可控變量,他的值從必需要大於等於3,這裏咱們給list的起始端定義了2個格子緩衝,末端最少要有1個格子的緩衝,這樣才能保證拖動的時候看不到空白的部分。htm
2.根據數據的長度,來計算整個item顯示區的rect,就是說在設置list數據的時候根據長度計算出整個滑動距離的最大值,即真正填滿全部數據要顯示的長度,這個主要是爲了匹配Unity UGUI中的ScrollBar,由於ScrollBar的滑動邊界是根據這個距離算出來的。blog
使用這個list的時候能夠直接給ScrollRect指定一個ScrollBar,在滑動時就會自動適配位置。和UGUI原生的ScrollRect+ScrollBar使用方法同樣。
3.每當item移出超過2個item的距離,就將移出的一列移動到最後並從新設置裏面的數據。
OK話很少說,貼代碼
using UnityEngine; using System.Collections; using System.Collections.Generic; using UnityEngine.UI; /// <summary> /// 無限循環List /// 做者:EdisonLee /// public class UILoop1 : UIBase { enum Direction { Horizontal, Vertical } [SerializeField] private RectTransform m_Cell; [SerializeField] private Vector2 m_Page; [SerializeField] Direction direction = Direction.Horizontal; [SerializeField,Range(4,10)] private int m_BufferNo; private List<RectTransform> m_InstantiateItems = new List<RectTransform>(); private IList m_Datas; public Vector2 CellRect { get { return m_Cell != null ? m_Cell.sizeDelta : new Vector2(100, 100); } } public float CellScale { get { return direction == Direction.Horizontal ? CellRect.x : CellRect.y; } } private float m_PrevPos = 0; public float DirectionPos { get { return direction == Direction.Horizontal ? m_Rect.anchoredPosition.x : m_Rect.anchoredPosition.y; } } private int m_CurrentIndex;//頁面的第一行(列)在整個conten中的位置 private Vector2 m_InstantiateSize = Vector2.zero; public Vector2 InstantiateSize { get { if (m_InstantiateSize == Vector2.zero) { float rows, cols; if (direction == Direction.Horizontal) { rows = m_Page.x; cols = m_Page.y + (float)m_BufferNo; } else { rows = m_Page.x + (float)m_BufferNo; cols = m_Page.y; } m_InstantiateSize = new Vector2(rows, cols); } return m_InstantiateSize; } } public int PageCount { get { return (int)m_Page.x * (int)m_Page.y; } } public int PageScale { get { return direction == Direction.Horizontal ? (int)m_Page.x : (int)m_Page.y; } } private ScrollRect m_ScrollRect; private RectTransform m_Rect; public int InstantiateCount { get { return (int)InstantiateSize.x * (int)InstantiateSize.y; } } protected override void Awake() { m_ScrollRect = GetComponentInParent<ScrollRect>(); m_ScrollRect.horizontal = direction == Direction.Horizontal; m_ScrollRect.vertical = direction == Direction.Vertical; m_Rect = GetComponent<RectTransform>(); m_Cell.gameObject.SetActive(false); } public override void Data(object data) { m_Datas = data as IList; if (m_Datas.Count > PageCount) { setBound(getRectByNum(m_Datas.Count)); } else { setBound(m_Page); } if (m_Datas.Count > InstantiateCount) { while (m_InstantiateItems.Count < InstantiateCount) { createItem(m_InstantiateItems.Count); } } else { while (m_InstantiateItems.Count > m_Datas.Count) { removeItem(m_InstantiateItems.Count - 1); } while (m_InstantiateItems.Count < m_Datas.Count) { createItem(m_InstantiateItems.Count); } } } private void createItem(int index) { RectTransform item = GameObject.Instantiate(m_Cell); item.SetParent(transform, false); item.anchorMax = Vector2.up; item.anchorMin = Vector2.up; item.pivot = Vector2.up; item.name = "item" + index; item.anchoredPosition = direction == Direction.Horizontal ? new Vector2(Mathf.Floor(index / InstantiateSize.x) * CellRect.x, -(index % InstantiateSize.x) * CellRect.y) : new Vector2((index % InstantiateSize.y) * CellRect.x, -Mathf.Floor(index / InstantiateSize.y) * CellRect.y); m_InstantiateItems.Add(item); item.gameObject.SetActive(true); updateItem(index, item.gameObject); } private void removeItem(int index) { RectTransform item = m_InstantiateItems[index]; m_InstantiateItems.Remove(item); RectTransform.Destroy(item.gameObject); } /// <summary> /// 由格子數量獲取多少行多少列 /// </summary> /// <param name="num"></param>格子個數 /// <returns></returns> private Vector2 getRectByNum(int num) { return direction == Direction.Horizontal ? new Vector2(m_Page.x, Mathf.CeilToInt(num / m_Page.x)) : new Vector2(Mathf.CeilToInt(num / m_Page.y), m_Page.y); } /// <summary> /// 設置content的大小 /// </summary> /// <param name="rows"></param>行數 /// <param name="cols"></param>列數 private void setBound(Vector2 bound) { m_Rect.sizeDelta = new Vector2(bound.y * CellRect.x, bound.x * CellRect.y); } public float MaxPrevPos { get { float result; Vector2 max = getRectByNum(m_Datas.Count); if(direction == Direction.Horizontal) { result = max.y - m_Page.y; } else { result = max.x - m_Page.x; } return result * CellScale; } } public float scale { get { return direction == Direction.Horizontal ? 1f : -1f; } } void Update() { while (scale * DirectionPos - m_PrevPos < -CellScale * 2) { if (m_PrevPos <= -MaxPrevPos) return; m_PrevPos -= CellScale; List<RectTransform> range = m_InstantiateItems.GetRange(0, PageScale); m_InstantiateItems.RemoveRange(0, PageScale); m_InstantiateItems.AddRange(range); for (int i = 0; i < range.Count; i++) { moveItemToIndex(m_CurrentIndex * PageScale + m_InstantiateItems.Count + i, range[i]); } m_CurrentIndex++; } while (scale * DirectionPos - m_PrevPos > -CellScale) { if (Mathf.RoundToInt(m_PrevPos) >= 0) return; m_PrevPos += CellScale; m_CurrentIndex--; if (m_CurrentIndex < 0) return; List<RectTransform> range = m_InstantiateItems.GetRange(m_InstantiateItems.Count - PageScale, PageScale); m_InstantiateItems.RemoveRange(m_InstantiateItems.Count - PageScale, PageScale); m_InstantiateItems.InsertRange(0, range); for (int i = 0; i < range.Count; i++) { moveItemToIndex(m_CurrentIndex * PageScale + i, range[i]); } } } private void moveItemToIndex(int index, RectTransform item) { item.anchoredPosition = getPosByIndex(index); updateItem(index, item.gameObject); } private Vector2 getPosByIndex(int index) { float x, y; if(direction == Direction.Horizontal) { x = index % m_Page.x; y = Mathf.FloorToInt(index / m_Page.x); } else { x = Mathf.FloorToInt(index / m_Page.y); y = index % m_Page.y; } return new Vector2(y * CellRect.x, -x * CellRect.y); } private void updateItem(int index, GameObject item) { item.SetActive(index < m_Datas.Count); if(item.activeSelf) { UILoopItem lit = item.GetComponent<UILoopItem>(); lit.UpdateItem(index, item); lit.Data(m_Datas[index]); } } }
經測試3-4千個數據仍是跑的剛剛的流暢,再往上就沒試過了,其實數據個數是根據設備性能而定的,由於顯示的Item只有固定的那幾個,因此性能的損耗就是在使用list以前設置數據生成數據的時候,也就是說跟list無關了,跟設備的內存,cpu有關。
本文固定連接:http://www.cnblogs.com/fly-100/p/4549354.html
轉載請註明出處,請尊重原創,多謝!