如果一個scrollView中顯示的按鈕總數多達四五百,可以一次性全部生成嗎?不能!!會非常非常卡。是以需要隻生成一定小數量的按鈕,然後動态調整這些按鈕的位置和顯示内容以及綁定事件來達到顯示海量資料的辦法。廢話不多說,直接上。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;
/// <summary>
/// 挂載在ScrollView遊戲物體上 這個類懶得寫注釋了。
/// </summary>
public class LoopList : MonoBehaviour
{
/// <summary>
/// 自己建立的一個臨時存儲的清單,模拟一個簡單的資料表或者資料庫
/// </summary>
List<string> data = new List<string>();
/// <summary>
/// 要動态生成的預制體按鈕
/// </summary>
public GameObject prefabButton;
/// <summary>
/// 滾動卷軸元件
/// </summary>
ScrollRect scrollRect;
/// <summary>
/// 滾動卷軸的transform元件
/// </summary>
RectTransform scrollRectTransform;
/// <summary>
/// content的transform元件
/// </summary>
RectTransform contentTransform;
private List<LoopListItem> _items=new List<LoopListItem>();
int itemMaxNum = 0;
float itemHeight = 0;
public float offsetY = 0;
private void Awake()
{
scrollRectTransform = transform as RectTransform;
scrollRect = GetComponent<ScrollRect>();
contentTransform = scrollRect.transform.Find("Viewport/Content") as RectTransform;
}
// Use this for initialization
void Start ()
{
//填充模拟的資料表,共317個資料
for (int i = 1; i <= 317; i++)
{
data.Add("按鈕" + i);
}
itemHeight = (prefabButton.transform as RectTransform).rect.height;
itemMaxNum = GetShowItemNum();
SetContentSize();
SpawnItem();
scrollRect.onValueChanged.AddListener(Refresh);
}
private void Refresh(Vector2 delta)
{
foreach (LoopListItem item in _items)
{
item.OnValueChangedEvent();
}
}
private int GetShowItemNum()
{
return Mathf.CeilToInt(scrollRectTransform.rect.height / (itemHeight + offsetY))+1;
}
void SpawnItem()
{
GameObject temp = null;
LoopListItem itemTemp = null;
for (int i = 0; i < itemMaxNum; i++)
{
temp = Instantiate<GameObject>(prefabButton, contentTransform, false);
itemTemp = temp.AddComponent<LoopListItem>();
itemTemp._getData += GetData;
itemTemp.Init(i,offsetY,itemMaxNum,contentTransform);
_items.Add(itemTemp);
}
}
string GetData(int index)
{
if(index<0||index>=data.Count)
{
return "";
}
return data[index];
}
void SetContentSize()
{
float y = data.Count * (itemHeight + offsetY)-offsetY;
contentTransform.sizeDelta = new Vector2(contentTransform.sizeDelta.x, y);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using UnityEngine.UI;
/// <summary>
/// 按鈕自身的管理器,挂載在每一個生成按鈕上
/// </summary>
public class LoopListItem : MonoBehaviour {
/// <summary>
/// 事件綁定,用來擷取按鈕對應的資料
/// </summary>
public event Func<int, string> _getData;
/// <summary>
/// 目前按鈕的ID
/// </summary>
int id=-1;
/// <summary>
/// 目前按鈕的transform元件
/// </summary>
RectTransform rect;
public RectTransform rectTransform
{
get
{
if(rect==null)
{
rect = GetComponent<RectTransform>();
}
return rect;
}
}
/// <summary>
/// 目前按鈕的文字顯示元件
/// </summary>
Text des;
public Text Des
{
get
{
if(des==null)
{
des = transform.Find("Lable").GetComponent<Text>();
}
return des;
}
}
/// <summary>
/// 滾動卷軸清單的content遊戲物體的transform元件
/// </summary>
RectTransform content;
/// <summary>
/// 按鈕之間的間距,變量名或許應該叫spacing
/// </summary>
float offsetY;
/// <summary>
/// 目前的scrollView的視口中能放置按鈕的最大個數
/// </summary>
int itemMaxNum;
/// <summary>
/// 初始化按鈕函數,也即是在按鈕被生成時調用的函數,在LoopList腳本中被調用
/// </summary>
/// <param name="_id"></param>
/// <param name="_offsetY"></param>
/// <param name="_itemMaxNum"></param>
/// <param name="_content"></param>
public void Init(int _id, float _offsetY, int _itemMaxNum, RectTransform _content)
{
//LoopList腳本中已經擷取該元件,傳值進來接收即可。
content= _content;
//最大值以擷取,傳值過來接收即可
itemMaxNum = _itemMaxNum;
//按鈕之間的間距,同上
offsetY = _offsetY;
//設定該按鈕的初始ID
id = _id;
//根據id擷取對應的按鈕名稱文本并設定
Des.text = _getData(_id);
//根據id設定按鈕對應的位置
rectTransform.anchoredPosition = new Vector2(0, -id * (rectTransform.rect.height + offsetY));
//将按鈕名稱同樣改變,這個有沒有無所謂,具體需求根據個人來講
gameObject.name = _getData(_id);
}
/// <summary>
/// ScrollView上下滑動時,也即滑動值或者說Content遊戲物體位置産生變化時,自動調用的函數。此函數在LoopList腳本中被調用.
/// </summary>
public void OnValueChangedEvent()
{
//根據content的位置計算出content與viePort重疊區域内,對應的頂端ID是多少。比如每個按鈕100寬度,目前content位置為1000,那麼頂端的按鈕應該就是數量十,即ID為9.
if (!gameObject.activeSelf)
{
return;
}
float y = content.anchoredPosition.y;
if (y < 0)
{
y = 0;
}
int startId = Mathf.FloorToInt(y / (rectTransform.rect.height + offsetY));
//将重疊區域内的首個ID和目前按鈕的初始ID相加,就是臨時用的目标ID。比如目前初始ID為0,那麼它就應當是目前區域内的最頂端按鈕。 其它按鈕以此類推。很簡單的邏輯。
int tempID = id + startId;
//根據臨時目标id設定按鈕對應的位置,千萬注意,在此過程中初始ID不發生改變
rectTransform.anchoredPosition = new Vector2(0, -tempID * (rectTransform.rect.height + offsetY));
Des.text = _getData(tempID);
gameObject.name = _getData(tempID);
}
}