Unity實現(xiàn)卡片循環(huán)滾動效果的示例詳解
簡介
功能需求如圖所示,點擊下一個按鈕,所有卡片向右滾動,其中最后一張需要變更為最前面的一張,點擊上一個按鈕,所有卡片向左滾動,最前面的一張需要變更為最后一張,實現(xiàn)循環(huán)滾動效果。
最中間的一張表示當前選中項,變更為選中項的滾動過程中,需要逐漸放大到指定值,相反則需要恢復到默認大小。

實現(xiàn)思路:
- 定義卡片的擺放規(guī)則;
- 調(diào)整卡片的層級關(guān)系;
- 調(diào)整卡片的尺寸大?。?/li>
- 卡片向指定方向移動,動態(tài)調(diào)整位置、大小、層級關(guān)系。
定義卡片的擺放規(guī)則
第一張卡片放在正中間,其余卡片分成兩部分分別放在左右兩側(cè),因此如果卡片數(shù)量為奇數(shù),則左右兩側(cè)卡片數(shù)量一致,如果卡片數(shù)量為偶數(shù),多出的一張需要放到左側(cè)或者右側(cè),這里我們定義為放到右側(cè)。
卡片擺放的順序如下圖所示,在遍歷生成時會判斷當前索引是否小等于卡片數(shù)量/2,是則將卡片生成在索引值*指定卡片間距的位置上,否則將其生成在(索引值-卡片數(shù)量)*指定卡片間距的位置上。

代碼實現(xiàn):
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
public class LoopScrollView : MonoBehaviour
{
[SerializeField] private Texture[] roomTextures; //所有卡片
[SerializeField] private GameObject itemPrefab; //列表項預制體
[SerializeField] private Transform itemParent; //列表項的父級,將卡片生成到該物體下
[SerializeField] private float interval = 450f; //卡片之間的間距
private void Start()
{
for (int i = 0; i < roomTextures.Length; i++)
{
var tex = roomTextures[i];
var instance = Instantiate(itemPrefab);
instance.SetActive(true);
instance.transform.SetParent(itemParent, false);
instance.GetComponent<RawImage>().texture = tex;
instance.name = i.ToString();
//坐標位置
(instance.transform as RectTransform).anchoredPosition3D = Vector3.right * interval
* (i <= roomTextures.Length / 2 ? i : (i - roomTextures.Length));
}
}
}
調(diào)整卡片的層級關(guān)系
卡片的層級關(guān)系如圖所示,第一張也就是中間的照片(編號0)需要在最上層,向左、向右逐漸遮擋下層,在Hierarchy層級窗口的表現(xiàn)則是編號0的卡片在最下方,編號1卡片在編號2卡片下方以遮擋編號2卡片,編號4卡片在編號3卡片下方以遮擋編號3卡片。
在遍歷生成卡片時判斷當前索引值是否小等于卡片數(shù)量/2,是則在層級中將其插入到最上方,也就是SiblingIndex=0,否則將其插入在第一張卡片之上,第一張卡片始終在最下方,也就是說插入為倒數(shù)第二個,即SiblingIndex=父節(jié)點的子物體數(shù)量-2。

代碼如下:
//層級關(guān)系 instance.transform.SetSiblingIndex(i <= roomTextures.Length / 2 ? 0 : itemParent.childCount - 2);
調(diào)整卡片的尺寸大小
大小的調(diào)整比較簡單,只需要將第一張卡片放大一定倍數(shù)即可。
//大小 instance.transform.localScale = (i == 0 ? 1.2f : 1f) * Vector3.one;

至此已經(jīng)完成了卡片的生成,但是如何在點擊上一個、下一個按鈕時動態(tài)調(diào)整所有卡片的坐標、層級和大小才是關(guān)鍵。
動態(tài)調(diào)整位置、層級和大小
移動動畫
首先為每張卡片添加腳本,用于實現(xiàn)卡片的移動邏輯,使用插值的形式來實現(xiàn)動畫過程,假設(shè)動畫所需時長為0.5秒,使用變量float類型變量timer來計時,自增Time.deltaTime * 2以使其在0.5秒內(nèi)的取值從0增加為1,并使用Mathf.Clamp01來鉗制其取值范圍不要超過1。
代碼如下:
using UnityEngine;
public class LoopScrollViewItem : MonoBehaviour
{
private RectTransform rectTransform;
private int index; //用于記錄當前所在位置
private Vector3 cacheScale; //開始移動時的大小
private Vector3 cacheAnchorPosition3d; //開始移動時的坐標
private Vector3 targetAnchorPostion3D; //目標坐標
private int targetSiblingIndex; //目標層級
private bool isMoving; //是否正在移動標識
private float timer; //計時
private bool last; //是否為最右側(cè)的那張卡片
private void Awake()
{
rectTransform = GetComponent<RectTransform>();
}
public int Index
{
get
{
return index;
}
set
{
if (index != value)
{
index = value;
}
}
}
private void Update()
{
if (isMoving)
{
timer += Time.deltaTime * 2f;
timer = Mathf.Clamp01(timer);
if (timer >= .2f)
{
transform.SetSiblingIndex(targetSiblingIndex);
}
rectTransform.anchoredPosition3D = Vector3.Lerp(cacheAnchorPosition3d, targetAnchorPostion3D, last ? 1f : timer);
transform.localScale = Vector3.Lerp(cacheScale, (index == 0 ? 1.3f : 1f) * Vector3.one, last ? 1f : timer);
if (timer == 1f)
{
isMoving = false;
}
}
}
public void Move(LoopScrollViewData data, bool last)
{
timer = 0f;
targetAnchorPostion3D = data.AnchorPosition3D;
targetSiblingIndex = data.SiblingIndex;
cacheAnchorPosition3d = rectTransform.anchoredPosition3D;
cacheScale = transform.localScale;
isMoving = true;
this.last = last;
}
}
其中last變量用于標識是否為最右側(cè)的那張卡片,如果是,使其立即變?yōu)樽钭髠?cè)的卡片,不表現(xiàn)動畫過程,目的是為了防止如下圖所示,卡片從最右側(cè)移動到最左側(cè)的穿幫現(xiàn)象:

在生成卡片時,為卡片物體添加該腳本,并添加到列表中進行緩存,同時,定義一個用于存儲各編號對應(yīng)的層級和坐標的數(shù)據(jù)結(jié)構(gòu),代碼如下:
using UnityEngine;
public class LoopScrollViewData
{
public int SiblingIndex { get; private set; }
public Vector3 AnchorPosition3D { get; private set; }
public LoopScrollViewData(int siblingIndex, Vector3 anchorPosition3D)
{
SiblingIndex = siblingIndex;
AnchorPosition3D = anchorPosition3D;
}
}
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
public class LoopScrollView : MonoBehaviour
{
[SerializeField] private Texture[] roomTextures; //所有卡片
[SerializeField] private GameObject itemPrefab; //列表項預制體
[SerializeField] private Transform itemParent; //列表項的父級,將卡片生成到該物體下
[SerializeField] private float interval = 400f; //卡片之間的間距
//生成的卡片列表
private readonly List<LoopScrollViewItem> itemList = new List<LoopScrollViewItem>();
//字典用于存儲各位置對應(yīng)的卡片層級和坐標
private readonly Dictionary<int, LoopScrollViewData> map = new Dictionary<int, LoopScrollViewData>();
private void Start()
{
for (int i = 0; i < roomTextures.Length; i++)
{
var tex = roomTextures[i];
var instance = Instantiate(itemPrefab);
instance.SetActive(true);
instance.transform.SetParent(itemParent, false);
instance.GetComponent<RawImage>().texture = tex;
instance.name = i.ToString();
//坐標位置
(instance.transform as RectTransform).anchoredPosition3D = Vector3.right * interval
* (i <= roomTextures.Length / 2 ? i : (i - roomTextures.Length));
//層級關(guān)系
instance.transform.SetSiblingIndex(i <= roomTextures.Length / 2 ? 0 : itemParent.childCount - 2);
//大小
instance.transform.localScale = (i == 0 ? 1.2f : 1f) * Vector3.one;
var item = instance.AddComponent<LoopScrollViewItem>();
item.Index = i;
itemList.Add(item);
}
for (int i = 0; i < itemList.Count; i++)
{
var item = itemList[i];
map.Add(i, new LoopScrollViewData(item.transform.GetSiblingIndex(), (item.transform as RectTransform).anchoredPosition3D));
}
}
}
按鈕事件
在生成卡片時,記錄了卡片當前的編號,以及各編號對應(yīng)的層級和位置,在點擊下一個、上一個按鈕時,只需要根據(jù)卡片當前的編號+1或-1來獲取目標層級和位置即可。
編號自增后,如果等于卡片的數(shù)量,表示當前卡片已經(jīng)是列表中最后一個,需要將其編號設(shè)為0,相反,當編號自減后,如果小于0,表示當前卡片已經(jīng)是列表中第一個,需要將其編號設(shè)為列表長度-1,以實現(xiàn)循環(huán)。
完整代碼:
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
public class LoopScrollView : MonoBehaviour
{
[SerializeField] private Texture[] roomTextures; //所有卡片
[SerializeField] private GameObject itemPrefab; //列表項預制體
[SerializeField] private Transform itemParent; //列表項的父級,將卡片生成到該物體下
[SerializeField] private Button prevButton; //上一個按鈕
[SerializeField] private Button nextButton; //下一個按鈕
[SerializeField] private float interval = 400f; //卡片之間的間距
//生成的卡片列表
private readonly List<LoopScrollViewItem> itemList = new List<LoopScrollViewItem>();
//字典用于存儲各位置對應(yīng)的卡片層級和坐標
private readonly Dictionary<int, LoopScrollViewData> map = new Dictionary<int, LoopScrollViewData>();
private void Start()
{
for (int i = 0; i < roomTextures.Length; i++)
{
var tex = roomTextures[i];
var instance = Instantiate(itemPrefab);
instance.SetActive(true);
instance.transform.SetParent(itemParent, false);
instance.GetComponent<RawImage>().texture = tex;
instance.name = i.ToString();
//坐標位置
(instance.transform as RectTransform).anchoredPosition3D = Vector3.right * interval
* (i <= roomTextures.Length / 2 ? i : (i - roomTextures.Length));
//層級關(guān)系
instance.transform.SetSiblingIndex(i <= roomTextures.Length / 2 ? 0 : itemParent.childCount - 2);
//大小
instance.transform.localScale = (i == 0 ? 1.2f : 1f) * Vector3.one;
var item = instance.AddComponent<LoopScrollViewItem>();
item.Index = i;
itemList.Add(item);
}
for (int i = 0; i < itemList.Count; i++)
{
var item = itemList[i];
map.Add(i, new LoopScrollViewData(item.transform.GetSiblingIndex(), (item.transform as RectTransform).anchoredPosition3D));
}
//添加按鈕點擊事件
nextButton.onClick.AddListener(OnNextButtonClick);
prevButton.onClick.AddListener(OnPrevButtonClick);
}
//下一個按鈕點擊事件
private void OnNextButtonClick()
{
for (int i = 0; i < itemList.Count; i++)
{
var item = itemList[i];
bool last = item.Index == itemList.Count / 2;
int index = item.Index + 1;
index = index >= itemList.Count ? 0 : index;
item.Index = index;
item.Move(map[index], last);
}
}
//上一個按鈕點擊事件
private void OnPrevButtonClick()
{
for (int i = 0; i < itemList.Count; i++)
{
var item = itemList[i];
int index = item.Index - 1;
index = index < 0 ? itemList.Count - 1 : index;
item.Index = index;
item.Move(map[index], false);
}
}
}

到此這篇關(guān)于Unity實現(xiàn)卡片循環(huán)滾動效果的示例詳解的文章就介紹到這了,更多相關(guān)Unity卡片循環(huán)滾動效果內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C# 如何使用OpcUaHelper讀寫OPC服務(wù)器
這篇文章給大家介紹C# 如何使用OpcUaHelper讀寫OPC服務(wù)器,本文通過圖文實例代碼相結(jié)合給大家介紹的非常詳細,需要的朋友參考下吧2023-12-12

