Unity ScrollView實現(xiàn)無限循環(huán)效果
本文實例為大家分享了Unity ScrollView實現(xiàn)無限循環(huán)效果的具體代碼,供大家參考,具體內(nèi)容如下
在Unity引擎中ScrollView組件是一個使用率比較高的組件,該組件能上下或者左右拖動的UI列表,背包、展示多個按鈕等情況的時候會用到,在做排行榜類似界面時,item非常多,可能有幾百個,一次創(chuàng)建這么多GameObject是非常卡的。為此,使用只創(chuàng)建可視區(qū)一共顯示的個數(shù),加上后置準備個數(shù)。
由于ScrollView有兩種滾動方式,水平滾動或者垂直滾動,所以我創(chuàng)建了ScrollBase基類,和HorizontalScroll子類及VerticalScroll子類,在父類中設(shè)定了公共的抽象方法OnDrawView—繪制顯示區(qū)的方法;CreateItem----創(chuàng)建Item的方法;Update----滾動狀態(tài)下實時更新Item的位置。
ScrollBase基類
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public enum MoveType
{
UP,
DOWN,
LEFT,
RIGHT,
STOP
}
public abstract class ScrollBase
{
public ItemArray<ItemData> items;
public ScrollViewLoop scrollLoop;
public float viewHeight;
public float viewWidth;
public float contentHeight;
public float contentWidth;
public RectTransform rectTransform;
public ScrollRect scrollRect;
public MoveType type;
public float item_width;//每個Item的寬度
public float item_height;//每個Item的高度
public int viewNum;//顯示區(qū)顯示的個數(shù)
public int addNum;//后置準備個數(shù)
public int itemNum;//總共需要多少Item的滾動
public float spaceY;//垂直Item間隔
public float spaceX;//水平Item間隔
public ScrollBase(ScrollViewLoop _scrollLoop, bool isCreateItem = true)
{
scrollLoop = _scrollLoop;
rectTransform = scrollLoop.GetComponent<RectTransform>();
scrollRect = scrollLoop.GetComponent<ScrollRect>();
if (!scrollRect)
{
scrollRect = scrollLoop.gameObject.AddComponent<ScrollRect>();
}
item_width = scrollLoop.item_width;
item_height = scrollLoop.item_height;
viewNum = scrollLoop.viewNum;
addNum = scrollLoop.addNum;
itemNum = scrollLoop.itemNum;
spaceY = scrollLoop.spaceY;
spaceX = scrollLoop.spaceX;
OnDrawView();
if (isCreateItem)
{
CreateItem();
}
}
public abstract void OnDrawView();
public abstract void CreateItem();
public abstract void Update();
}
ScrollViewLoop控制類
這個屬性類需要掛載在ScrollRect物體身上,然后在面板上進行屬性配置
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ItemData
{
public RectTransform itemRect;
public int index;
}
public class ScrollViewLoop : MonoBehaviour
{
public GameObject itemPrefab;
[Header("每個Item的寬度")]
public float item_width;
[Header("每個Item的高度")]
public float item_height;
[Header("顯示區(qū)顯示幾個Item")]
public int viewNum = 6;
[Header("顯示區(qū)外多復(fù)制幾個Item")]
public int addNum = 2;
[Header("總共需要多少Item")]
public int itemNum = 100;
[Header("Item之間的垂直距離")]
public float spaceY = 2;
public float spaceX = 2;
public delegate void ItemChange(ItemData itemData);
public event ItemChange OnChange;//Item位置改變時調(diào)用的事件
[Header("是否是橫向滑動")]
public bool isHorizontal = false;
ScrollBase scrollBase;
void Start()
{
if (isHorizontal)
{
scrollBase = new HorizontalScroll(this);
}
else
{
scrollBase = new VerticalScroll(this);
}
}
public void UseOnChange(ItemData itemData)
{
OnChange?.Invoke(itemData);
}
//ScrollRect滾動
void Update()
{
if (scrollBase != null)
{
scrollBase.Update();
}
}
}
ItemArray

我們實現(xiàn)無限循環(huán)的核心思想是,當滾動框向左移動時,就判斷左邊的第一個Item距離顯示框左邊框的距離,是否大于Item水平間隔*0.5+Item的寬度,如果大于則把第一個Item放到最后的位置,當滾動框向右移動時就判斷右邊的Item距離右邊框的距離情況,這樣我們就可以在顯示區(qū)域一直看到有Item的規(guī)則顯示,所以我定制了一個泛型容器類,這個類存放所有的Item信息,當我們?nèi)〉谝粋€Item時自動把第一個Item放到最后,當我們?nèi)∽詈笠粋€Item時自動把最后的一個Item放到最前面。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// Item集合類
/// </summary>
/// <typeparam name="T"></typeparam>
public class ItemArray<T> where T : ItemData
{
List<T> list;
public int count
{
get
{
return list.Count;
}
}
public ItemArray()
{
list = new List<T>();
}
public ItemArray(T[] ts)
{
if (ts == null)
{
return;
}
for (int i = 0; i < ts.Length; i++)
{
list.Add(ts[i]);
}
}
/// <summary>
/// 添加一個元素
/// </summary>
/// <param name="t"></param>
public void AddItem(T t)
{
list.Add(t);
}
public T GetFirst()
{
if (list.Count == 0)
{
return default(T);
}
T t = list[0];
list.RemoveAt(0);
t.index = list[list.Count - 1].index + 1;
list.Add(t);
return t;
}
public T GetLast()
{
if (list.Count == 0)
{
return default(T);
}
T t = list[list.Count - 1];
list.RemoveAt(list.Count - 1);
t.index = list[0].index - 1;
list.Insert(0, t);
return t;
}
public T[] GetArray()
{
T[] ts = new T[list.Count];
for (int i = 0; i < list.Count; i++)
{
ts[i] = list[i];
}
return ts;
}
public T this[int index]
{
get
{
return list[index];
}
}
}
HorizontalScroll子類及VerticalScroll子類
ScrollView無限循環(huán)的核心功能代碼都在這兩個子類中
HorizontalScroll子類
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HorizontalScroll : ScrollBase
{
public HorizontalScroll(ScrollViewLoop _scrollLoop, bool isCreateItem = true) : base(_scrollLoop, isCreateItem)
{
scrollRect.horizontal = true;
scrollRect.vertical = false;
}
public override void CreateItem()
{
items = new ItemArray<ItemData>();
for (int i = 0; i < viewNum + addNum; i++)
{
GameObject item = GameObject.Instantiate(scrollLoop.itemPrefab);
RectTransform itemRect = item.GetComponent<RectTransform>();
ItemData data = new ItemData();
data.itemRect = itemRect;
data.index = i;
items.AddItem(data);
scrollLoop.UseOnChange(data);
itemRect.sizeDelta = new Vector2(item_width, item_height);
itemRect.anchorMin = new Vector2(0, 1);
itemRect.anchorMax = new Vector2(0, 1);
itemRect.pivot = new Vector2(0, 1);
itemRect.localScale = Vector2.one;
item.transform.parent = scrollRect.content;
item.transform.localPosition = new Vector3(i * (item_width + spaceX), 0, 0);
}
}
void RewindItemPos()
{
int index = Mathf.FloorToInt(Mathf.Abs(scrollRect.content.localPosition.x) / (item_width + spaceX));
for (int i = 0; i < items.count; i++)
{
RectTransform itemRect = items[i].itemRect;
items[i].index = index;
scrollLoop.UseOnChange(items[i]);
itemRect.transform.localPosition = new Vector3(items[i].index * (item_width + spaceX), 0, 0);
index++;
}
}
public override void OnDrawView()
{
SetRectTransform(rectTransform);
rectTransform.sizeDelta = new Vector2(item_width * viewNum + (viewNum - 1) * spaceX, item_height);
SetRectTransform(scrollRect.viewport);
viewHeight = scrollLoop.item_height;
viewWidth = viewNum * item_width + (viewNum - 1) * spaceX;
scrollRect.viewport.sizeDelta = new Vector2(viewWidth, viewHeight);
scrollRect.viewport.localPosition = Vector3.zero;
SetRectTransform(scrollRect.content);
contentHeight = item_height;
contentWidth = itemNum * item_width + (itemNum - 1) * spaceX;
scrollRect.content.sizeDelta = new Vector2(contentWidth, contentHeight);
scrollRect.content.localPosition = Vector3.zero;
}
public void SetRectTransform(RectTransform rect)
{
rect.anchorMin = new Vector2(0, 0.5f);
rect.anchorMax = new Vector2(0, 0.5f);
rect.pivot = new Vector2(0, 1);
rect.localScale = Vector2.one;
}
float lastX = 0;
MoveType lastMoveType = MoveType.STOP;
public override void Update()
{
float x = scrollRect.content.localPosition.x;
if (x > lastX)
{
type = MoveType.RIGHT;
}
else if (x < lastX)
{
type = MoveType.LEFT;
}
else
{
type = MoveType.STOP;
if (lastMoveType != type)
{
Debug.Log("重置位置");
RewindItemPos();
}
}
if (type == MoveType.LEFT)//向左滑動
{
if (items[items.count - 1].index == itemNum - 1)
{
return;
}
float firstItemX = items[0].itemRect.localPosition.x;
if (-x - firstItemX - item_width >= spaceX * 0.5f)
{
float lastItemX = items[items.count - 1].itemRect.localPosition.x;
ItemData firstItem = items.GetFirst();
firstItem.itemRect.localPosition = new Vector3(lastItemX + spaceX + item_width, 0, 0);
if (firstItem.index < itemNum)
{
scrollLoop.UseOnChange(firstItem);
}
}
}
else if (type == MoveType.RIGHT)//向右滑動
{
if (items[0].index == 0)
{
return;
}
float lastItemX = items[items.count - 1].itemRect.localPosition.x;
if (lastItemX + x - viewWidth >= spaceX * 0.5f)
{
float firstItemX = items[0].itemRect.localPosition.x;
ItemData lastItem = items.GetLast();
lastItem.itemRect.localPosition = new Vector3(firstItemX - spaceX - item_width, 0, 0);
if (lastItem.index >= 0)
{
scrollLoop.UseOnChange(lastItem);
}
}
}
lastMoveType = type;
lastX = x;
}
}
VerticalScroll子類
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class VerticalScroll : ScrollBase
{
public VerticalScroll(ScrollViewLoop _scrollLoop, bool isCreateItem = true) : base(_scrollLoop, isCreateItem)
{
scrollRect.horizontal = false;
scrollRect.vertical = true;
}
public override void CreateItem()
{
items = new ItemArray<ItemData>();
for (int i = 0; i < viewNum + addNum; i++)
{
GameObject item = GameObject.Instantiate(scrollLoop.itemPrefab);
RectTransform itemRect = item.GetComponent<RectTransform>();
ItemData data = new ItemData();
data.itemRect = itemRect;
data.index = i;
items.AddItem(data);
scrollLoop.UseOnChange(data);
itemRect.sizeDelta = new Vector2(item_width, item_height);
itemRect.anchorMin = new Vector2(0, 1);
itemRect.anchorMax = new Vector2(0, 1);
itemRect.pivot = new Vector2(0, 1);
itemRect.localScale = Vector2.one;
item.transform.parent = scrollRect.content;
item.transform.localPosition = new Vector3(0, (-i) * (item_height + spaceY), 0);
}
}
void RewindItemPos()
{
int index = Mathf.FloorToInt(Mathf.Abs(scrollRect.content.localPosition.y) / (item_height + spaceY));
for (int i = 0; i < items.count; i++)
{
RectTransform itemRect = items[i].itemRect;
items[i].index = index;
scrollLoop.UseOnChange(items[i]);
itemRect.transform.localPosition = new Vector3(0, (-items[i].index) * (item_height + spaceY), 0);
index++;
}
}
public override void OnDrawView()
{
SetRectTransform(rectTransform);
rectTransform.sizeDelta = new Vector2(item_width, viewNum * item_height + (viewNum - 1) * spaceY);
SetRectTransform(scrollRect.viewport);
viewHeight = viewNum * item_height + (viewNum - 1) * spaceY;
scrollRect.viewport.sizeDelta = new Vector2(item_width, viewHeight);
scrollRect.viewport.localPosition = Vector3.zero;
SetRectTransform(scrollRect.content);
contentHeight = itemNum * item_height + (itemNum - 1) * spaceY;
scrollRect.content.sizeDelta = new Vector2(item_width, contentHeight);
scrollRect.content.localPosition = Vector3.zero;
}
public void SetRectTransform(RectTransform rect)
{
rect.anchorMin = new Vector2(0.5f, 1);
rect.anchorMax = new Vector2(0.5f, 1);
rect.pivot = new Vector2(0, 1);
rect.localScale = Vector2.one;
}
float lastY = 0;
MoveType lastMoveType = MoveType.STOP;
public override void Update()
{
float y = scrollRect.content.localPosition.y;
if (y > lastY)
{
type = MoveType.UP;
}
else if (y < lastY)
{
type = MoveType.DOWN;
}
else
{
type = MoveType.STOP;
if (lastMoveType != type)
{
RewindItemPos();
}
}
if (type == MoveType.UP)//向上滑動
{
if (items[items.count - 1].index == itemNum - 1)
{
return;
}
float firstItemY = items[0].itemRect.localPosition.y;
if (firstItemY - item_height + y >= spaceY * 0.5f)
{
float lastItemY = items[items.count - 1].itemRect.localPosition.y;
ItemData firstItem = items.GetFirst();
firstItem.itemRect.localPosition = new Vector3(0, lastItemY - spaceY - item_height, 0);
if (firstItem.index < itemNum)
{
scrollLoop.UseOnChange(firstItem);
}
}
}
else if (type == MoveType.DOWN)//向下滑動
{
if (items[0].index == 0)
{
return;
}
float lastItemY = items[items.count - 1].itemRect.localPosition.y;
if (-lastItemY - y - viewHeight >= spaceY * 0.5f)
{
float firstItemY = items[0].itemRect.localPosition.y;
ItemData lastItem = items.GetLast();
lastItem.itemRect.localPosition = new Vector3(0, firstItemY + spaceY + item_height, 0);
if (lastItem.index >= 0)
{
scrollLoop.UseOnChange(lastItem);
}
}
}
lastMoveType = type;
lastY = y;
}
}
編輯器拓展
這一步我們進行編輯器的拓展,根據(jù)ScrollViewLoop控制類面板上的數(shù)據(jù)我們在未運行的狀態(tài)下改變ScrollView的顯示區(qū)域,這樣更利于我們的可視化排版。
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;
[CustomEditor(typeof(ScrollViewLoop))]
public class ScrollLopEditor : Editor
{
ScrollViewLoop scrollLoop;
private RectTransform scrollRectTransfrom;
private ScrollRect scrollRect;
private void OnEnable()
{
scrollLoop = target as ScrollViewLoop;
scrollRectTransfrom = scrollLoop.GetComponent<RectTransform>();
scrollRect = scrollLoop.GetComponent<ScrollRect>();
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
bool isBtn = GUILayout.Button("編輯ScrollView顯示區(qū)大小");
if (isBtn)
{
if (scrollLoop.isHorizontal)
{
ScrollBase scrollBase = new HorizontalScroll(scrollLoop, false);
}
else
{
ScrollBase scrollBase = new VerticalScroll(scrollLoop, false);
}
}
}
}
測試
測試一下功能效果
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Test : MonoBehaviour
{
public ScrollViewLoop sl;
private void Start()
{
sl.OnChange += Sl_OnChange;
}
//Item位置改變時自動改變Item展示內(nèi)容
private void Sl_OnChange(ItemData itemData)
{
itemData.itemRect.GetChild(0).GetComponent<Text>().text = "ITEM"+itemData.index;
}
}
面板顯示如下:

運行效果如下(本來想放一段視頻的,遺憾的是沒能成功操作,尷尬):

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
利用MySqlBulkLoader實現(xiàn)批量插入數(shù)據(jù)的示例詳解
MySQLBulkLoader是MySQL?Connector/Net類中的一個類,用于包裝MySQL語句。本文將利用MySqlBulkLoader實現(xiàn)批量插入數(shù)據(jù)功能,感興趣的可以了解一下2022-06-06

