UGUI製作無限迴圈List列表過程詳解
UI開發其實技術的成分相對來說不算多,但是一個好的UI是絕對少不了底層元件的支援的。我個人認為UI元件中相對比較複雜的就是List了,所以,這兩天實現了一個UGUI的list,寫了好幾個版本,最終這個版本是相對比較好用的,在這我介紹一下大概思路,一是鞏固一下知識做個記錄,二是發揚一下分享精神。
在寫List有兩個重點是需要慎重考慮的:
第一:list中的item總數問題,剛開啟的時候如果同時生成多個item會有卡頓的現象,50個,100個可能沒問題但是1000個2000個就比較難搞了。
第二:當list滑動時如何載入後面的item,一般的邏輯應該是這樣的:每當滑動到紅線的位置就生成後面一列的item,但是做過UI開發的人知道,這種方法做做demo可以,但是現實專案中基本沒這麼用的。如下圖
怎麼解決這兩個問題呢?其實很簡單,想一想就知道,其實對於list來說我們操作的是資料,而且我們最多隻能看到scroll view裡面的 (行數*列數 + 1*列數 )這個多個item,所以理論上來說只需要生成(行數*列數 + 1*列數 )個item就可以。
但是,還是不行!why?因為還是會出現“生成以後馬上就要顯示”的問題,理論跟現實是有差距的,顯示中就算再快的機器再快的效能生成一個item的時間也不可能為0,所以如果一個item生成的時間和它被顯示的時間重疊,在體驗上肯定好不了。
所以我們還要再多生成3列,多出來的幾列就相當於一個緩衝。
好吧,我直接說我的邏輯吧。
1.生成行數*列數+n*列數個item,這裡n其實是一個可控變數,他的值從必須要大於等於3,這裡我們給list的起始端定義了2個格子緩衝,末端最少要有1個格子的緩衝,這樣才能保證拖動的時候看不到空白的部分。
2.根據資料的長度,來計算整個item顯示區的rect,就是說在設定list資料的時候根據長度計算出整個滑動距離的最大值,即真正填滿所有資料要顯示的長度,這個主要是為了匹配Unity UGUI中的ScrollBar,因為ScrollBar的滑動邊界是根據這個距離算出來的。
使用這個list的時候可以直接給ScrollRect指定一個ScrollBar,在滑動時就會自動適配位置。和UGUI原生的ScrollRect+ScrollBar使用方法一樣。
3.每當item移出超過2個item的距離,就將移出的一列移動到最後並重新設定裡面的資料。
下面貼程式碼:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
///
/// 無限迴圈List
///
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 m_InstantiateItems = new List();
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();
m_ScrollRect.horizontal = direction == Direction.Horizontal;
m_ScrollRect.vertical = direction == Direction.Vertical;
m_Rect = GetComponent();
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);
}
///
/// 由格子數量獲取多少行多少列
///
///格子個數
///
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);
}
///
/// 設定content的大小
///
///行數
///列數
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 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 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();
lit.UpdateItem(index, item);
lit.Data(m_Datas[index]);
}
}
}