Unity 如何实现卡片循环滚动效果
创始人
2024-03-28 14:13:01
0

文章目录

  • 简介
  • 定义卡片的摆放规则
  • 调整卡片的层级关系
  • 调整卡片的尺寸大小
  • 动态调整位置、层级和大小
    • 移动动画
    • 按钮事件


简介

功能需求如图所示,点击下一个按钮,所有卡片向右滚动,其中最后一张需要变更为最前面的一张,点击上一个按钮,所有卡片向左滚动,最前面的一张需要变更为最后一张,实现循环滚动效果。

最中间的一张表示当前选中项,变更为选中项的滚动过程中,需要逐渐放大到指定值,相反则需要恢复到默认大小。

卡片循环滚动

实现思路:

  • 定义卡片的摆放规则;
  • 调整卡片的层级关系;
  • 调整卡片的尺寸大小;
  • 卡片向指定方向移动,动态调整位置、大小、层级关系。

定义卡片的摆放规则

第一张卡片放在正中间,其余卡片分成两部分分别放在左右两侧,因此如果卡片数量为奇数,则左右两侧卡片数量一致,如果卡片数量为偶数,多出的一张需要放到左侧或者右侧,这里我们定义为放到右侧。

卡片摆放的顺序如下图所示,在遍历生成时会判断当前索引是否小等于卡片数量/2,是则将卡片生成在索引值*指定卡片间距的位置上,否则将其生成在(索引值-卡片数量)*指定卡片间距的位置上。

卡片摆放顺序

代码实现:

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().texture = tex;instance.name = i.ToString();//坐标位置(instance.transform as RectTransform).anchoredPosition3D = Vector3.right * interval* (i <= roomTextures.Length / 2 ? i : (i - roomTextures.Length));}}
}

调整卡片的层级关系

卡片的层级关系如图所示,第一张也就是中间的照片(编号0)需要在最上层,向左、向右逐渐遮挡下层,在Hierarchy层级窗口的表现则是编号0的卡片在最下方,编号1卡片在编号2卡片下方以遮挡编号2卡片,编号4卡片在编号3卡片下方以遮挡编号3卡片。

在遍历生成卡片时判断当前索引值是否小等于卡片数量/2,是则在层级中将其插入到最上方,也就是SiblingIndex=0,否则将其插入在第一张卡片之上,第一张卡片始终在最下方,也就是说插入为倒数第二个,即SiblingIndex=父节点的子物体数量-2

卡片层级关系

代码如下:

//层级关系
instance.transform.SetSiblingIndex(i <= roomTextures.Length / 2 ? 0 : itemParent.childCount - 2);

调整卡片的尺寸大小

大小的调整比较简单,只需要将第一张卡片放大一定倍数即可。

//大小
instance.transform.localScale = (i == 0 ? 1.2f : 1f) * Vector3.one;

卡片尺寸大小
至此已经完成了卡片的生成,但是如何在点击上一个、下一个按钮时动态调整所有卡片的坐标、层级和大小才是关键。

动态调整位置、层级和大小

移动动画

首先为每张卡片添加脚本,用于实现卡片的移动逻辑,使用插值的形式来实现动画过程,假设动画所需时长为0.5秒,使用变量float类型变量timer来计时,自增Time.deltaTime * 2以使其在0.5秒内的取值从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; //是否为最右侧的那张卡片private void Awake(){rectTransform = GetComponent();}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变量用于标识是否为最右侧的那张卡片,如果是,使其立即变为最左侧的卡片,不表现动画过程,目的是为了防止如下图所示,卡片从最右侧移动到最左侧的穿帮现象

穿帮现象
在生成卡片时,为卡片物体添加该脚本,并添加到列表中进行缓存,同时,定义一个用于存储各编号对应的层级和坐标的数据结构,代码如下:

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 itemList = new List();//字典用于存储各位置对应的卡片层级和坐标private readonly Dictionary map = new Dictionary();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().texture = tex;instance.name = i.ToString();//坐标位置(instance.transform as RectTransform).anchoredPosition3D = Vector3.right * interval* (i <= roomTextures.Length / 2 ? i : (i - roomTextures.Length));//层级关系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();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));}}
}

按钮事件

在生成卡片时,记录了卡片当前的编号,以及各编号对应的层级和位置,在点击下一个、上一个按钮时,只需要根据卡片当前的编号+1-1来获取目标层级和位置即可。

编号自增后,如果等于卡片的数量,表示当前卡片已经是列表中最后一个,需要将其编号设为0,相反,当编号自减后,如果小于0,表示当前卡片已经是列表中第一个,需要将其编号设为列表长度-1,以实现循环。

完整代码:

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 itemList = new List();//字典用于存储各位置对应的卡片层级和坐标private readonly Dictionary map = new Dictionary();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().texture = tex;instance.name = i.ToString();//坐标位置(instance.transform as RectTransform).anchoredPosition3D = Vector3.right * interval* (i <= roomTextures.Length / 2 ? i : (i - roomTextures.Length));//层级关系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();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);}}
}

卡片循环滚动

相关内容

热门资讯

银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...