Unity实现卡片循环滚动效果的示例详解

作者:CoderZ1010 时间:2022-06-06 16:04:47 

简介

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

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

Unity实现卡片循环滚动效果的示例详解

实现思路:

  • 定义卡片的摆放规则;

  • 调整卡片的层级关系;

  • 调整卡片的尺寸大小;

  • 卡片向指定方向移动,动态调整位置、大小、层级关系。

定义卡片的摆放规则

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

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

Unity实现卡片循环滚动效果的示例详解

代码实现:

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));
       }
   }
}

调整卡片的层级关系

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

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

Unity实现卡片循环滚动效果的示例详解

代码如下:

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

调整卡片的尺寸大小

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

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

Unity实现卡片循环滚动效果的示例详解

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

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

移动动画

首先为每张卡片添加脚本,用于实现卡片的移动逻辑,使用插值的形式来实现动画过程,假设动画所需时长为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<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变量用于标识是否为最右侧的那张卡片,如果是,使其立即变为最左侧的卡片,不表现动画过程,目的是为了防止如下图所示,卡片从最右侧移动到最左侧的穿帮现象:

Unity实现卡片循环滚动效果的示例详解

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

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>();
   //字典用于存储各位置对应的卡片层级和坐标
   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));
           //层级关系
           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));
       }
   }
}

按钮事件

在生成卡片时,记录了卡片当前的编号,以及各编号对应的层级和位置,在点击下一个、上一个按钮时,只需要根据卡片当前的编号+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<LoopScrollViewItem> itemList = new List<LoopScrollViewItem>();
   //字典用于存储各位置对应的卡片层级和坐标
   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));
           //层级关系
           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);
       }
   }
}

Unity实现卡片循环滚动效果的示例详解

来源:https://blog.csdn.net/qq_42139931/article/details/128240698

标签:Unity,循环,滚动
0
投稿

猜你喜欢

  • C#正则实现Ubb解析类的代码

    2021-10-23 22:19:38
  • photoView实现图片多点触控效果

    2023-06-21 20:16:03
  • C#+MO实现一个道路编辑软件(刚开始)

    2023-03-28 08:52:05
  • 详解ThreadLocal为什么会内存溢出原理

    2023-11-09 18:45:26
  • C语言算法积累加tag的循环队列

    2022-09-21 16:05:30
  • springboot访问template下的html页面的实现配置

    2023-02-09 23:33:21
  • C#实现学生成绩管理系统

    2021-06-05 22:51:31
  • springMVC利用FastJson接口返回json数据相关配置详解

    2023-02-24 10:51:58
  • mybatis foreach 循环 list(map)实例

    2023-11-23 23:39:05
  • Java程序控制逻辑—流程控制

    2023-08-28 01:51:18
  • C#使用BinaryFormatter类、ISerializable接口、XmlSerializer类进行序列化和反序列化

    2023-01-16 02:11:54
  • Android的OkHttp包中的HTTP拦截器Interceptor用法示例

    2022-01-31 06:31:25
  • Android 中解决Viewpage调用notifyDataSetChanged()时界面无刷新的问题

    2022-11-11 22:21:50
  • Flutter 底部弹窗如何实现多项选择

    2023-06-24 17:08:17
  • C#进制之间的相互转换详解

    2022-07-24 11:47:53
  • java实现学籍管理系统

    2023-04-03 00:32:19
  • kafka的消息存储机制和原理分析

    2022-02-01 13:07:39
  • C#之Expression表达式树实例

    2023-02-24 20:54:31
  • java实现数字转换人民币中文大写工具

    2023-08-16 08:48:41
  • Android中协调滚动布局的实现代码

    2023-11-07 20:37:05
  • asp之家 软件编程 m.aspxhome.com