Unity 如何实现卡片循环滚动效果

news2024/11/24 2:47:02

文章目录

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


简介

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

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

卡片循环滚动

实现思路:

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

定义卡片的摆放规则

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

卡片摆放的顺序如下图所示,在遍历生成时会判断当前索引是否小等于卡片数量/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<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

卡片层级关系

代码如下:

//层级关系
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<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变量用于标识是否为最右侧的那张卡片,如果是,使其立即变为最左侧的卡片,不表现动画过程,目的是为了防止如下图所示,卡片从最右侧移动到最左侧的穿帮现象

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

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

卡片循环滚动

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/73903.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

案例:用户信息列表展示

1. 需求&#xff1a;用户信息的增删改查操作2. 设计&#xff1a;1. 技术选型&#xff1a;ServletJSPMySQLJDBCTempleatDuirdBeanUtilStomcat2. 数据库设计&#xff1a;create database day17; -- 创建数据库use day17; -- 使用数据库create table user( -- 创建表id in…

Java5分钟制作海报

一、需求背景我们经常在多终端应用开发中会遇到这样的需求&#xff1a;用户在浏览商品时觉得不错&#xff0c;希望分享给朋友。此时终端&#xff08;安卓、苹果、H5等&#xff09;生成一张精美的商品海报&#xff0c;通过微信或者其他途径分享给他人。也可能会遇到需求&#xf…

排课算法小记

输出&#xff1a; 在配置文件(config.txt)中配置:老师&#xff0c;课程&#xff0c;专业班级&#xff0c;课时的信息&#xff0c;运行test.py自动生成对应班级课程表 eg: 专业1&#xff0c;四门课&#xff0c;每门课每周2课时,共8门课 专业2&#xff0c;四门课&#xff0c;每…

springcloud(服务消费及熔断)

目录 1. 服务消费方式 1.1 RestTemplate1.2 feign2. 服务熔断&#xff08;降级&#xff09; 2.1 在微服务架构中服务熔断的必要性2.2 hystrix2.3 hystrix的使用 1. 服务消费方式 1.1 RestTemplate 传统情况下在java代码里访问restful服务&#xff0c;一般使用Apache的HttpClie…

[附源码]JAVA毕业设计田径运动会管理系统(系统+LW)

[附源码]JAVA毕业设计田径运动会管理系统&#xff08;系统LW&#xff09; 项目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技…

Vue项目实战 可视化 创建(vue2+Element ui)

Vue项目实战创建项目通过脚手架 创建项目 配置 vue 路由配置 element—ui 组件库配置 axios 库创建远程仓库初始化 git 远程仓库 将本地项目托管到 码云前端项目初始化步骤① 安装vue脚手架② 通过脚手架 创建项目③ 配置 vue 路由④ 配置 element—ui 组件库⑤ 配置 axios 库⑥…

基于小波变换的去噪,带GUI界面,可以设置小波变换层数

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 近年来&#xff0c;小波理论得到了非常迅速的发展&#xff0c;而且由于其具备良好的时频特性&#xff0c;因而实际应用也非常广泛。在去噪领域中&#xff0c;小波理论也同样受到了许多学者的重视…

关于我的Oracle Primavera P6/Unifier/Gateway离线帮助中心

目录 ​编辑 一、前序 二、路子 三、用途 四、最后 一、前序 经常到处飞&#xff0c;很多时候会又比较依赖于网页查看产品的帮助&#xff0c;而网页又比较依赖于网络 怎么办呢 有人提到通过官方提供的PDF&#xff0c;毕竟上面的内容和网页展现几乎完全一样&#xff0c;…

【ChatGPT】与ChatGPT聊天,了解世界杯的前世今生

文章目录&#x1f3c6; 前言&#x1f4ac; 什么是ChatGPT⚽ 与ChatGPT的快问快答&#x1f9e9; 总结&#x1f3c6; 前言 最近火爆全网的chatGPT&#xff0c;吸引一大批技术爱好者的疯狂围观。大家使用过后&#xff0c;纷纷发出惊叹&#xff0c;深陷其中&#xff0c;无法自拔。 …

数字信号处理用脉冲响应不变法和双线性变换法设计巴特沃斯滤波器MATLAB实现——实例

文章目录符号含义例题脉冲响应不变法双线性变换法画图完整代码符号含义 例题 脉冲响应不变法 clear close all clcfs1000;%采样频率 fc200;%通带截止频率 fr300;%阻带截止频率 T0.001; %采样周期%%%%%%%脉冲响应不变法 wp12*pi*fc;%通带截止频率 wr12*pi*fr;%阻带截止频率[N1,…

数据分享|用加性多元线性回归、随机森林、弹性网络模型预测鲍鱼年龄和可视化...

原文链接&#xff1a;http://tecdat.cn/?p24127鲍鱼是一种贝类&#xff0c;在世界许多地方都被视为美味佳肴&#xff08;点击文末“阅读原文”获取完整代码数据&#xff09;。介绍相关视频养殖者通常会切开贝壳并通过显微镜计算环数来估计鲍鱼的年龄。因此&#xff0c;判断鲍鱼…

【QT开发笔记-基础篇】| 第五章 绘图QPainter | 5.3 初始化数据

本节对应的视频讲解&#xff1a;B_站_视_频 https://www.bilibili.com/video/BV1qd4y1s7xk 布局完成后&#xff0c;就可以修改控件的名称&#xff0c;以及添加初始化数据 1. 变量命名 先修改各控件显示的的名称&#xff0c;做到 “见名知义”&#xff0c;方便写代码 修改完…

C语言开发《推箱子游戏》,亲自手把手教会大家

【C语言经典算法100道实战题】适合具备C语言基础语法的同学学习&#xff0c;提高编写程序的逻辑思维能力和算法设计能力专门精心设计。100个经典的算法供大家练习及配套对应的录播视频。为我们今后学习其它的编程语言和软件开发打下坚实的基础&#xff0c;让你在编码道路上如鱼…

【手把手】教你玩转消息中间件之RabbitMQ

1、微服务下现存的各种问题 服务调用问题 当两个服务调用时&#xff0c;可以通过传统的HTTP方式&#xff0c;让服务A直接去调用服务B的接口&#xff0c;但是这种方式是同步的方式&#xff0c;虽然可以采用SpringBoot提供的Async注解实现异步调用&#xff0c;但是这种方式无法…

gsva gsea ssgsea gaochao 使用GSVA方法计算某基因集在各个样本的表现

傻傻分不清!GSEA & GSVA有啥差别?史上最全教程来了! - 知乎 (zhihu.com) 文章发表于2013年,GSVA: gene set variation analysis for microarray and RNA-Seq data 同样是broad 研究生出品,其在2005年PNAS发表的gsea已经高达1.4万的引用了,不过这个GSVA才不到300。 G…

【边缘检测】基于模糊算法的图像边缘检测附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步进步&#xff0c;matlab项目目标合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信息&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算…

实时数仓Flink生产环境部署+提交作业【步骤】

文章目录1、基础环境2、开发环境2.1、pom.xml2.2、log4j.properties2.3、测试用的代码2.3.1、Kafka工具2.3.3、Flink执行环境工具2.3.3、测试Flink读写Kafka2.3.4、测试FlinkSQL读写Kafka2.4、打包后上传到服务器3、生产环境3.1、Flink安装3.2、Flink on YARN下3种模式3.2.1、S…

Linux下POSIX信号量以及基于环形队列的生产消费模型

目录 一、POSIX信号量介绍 1. 信号量原理 2&#xff0c;初始化信号量 3,信号量销毁 4&#xff0c;信号量等待 5,发布信号量 二&#xff0c;基于环形队列的生产消费模型 1.基于单线程 2&#xff0c;测试&#xff1a; 3&#xff0c;基于多线程 4,测试 三&#xff0c;代…

故障转移,服务发现,负载均衡所运用的连接池

没错&#xff0c;说的就是连接池&#xff0c;玩互联网架构&#xff0c;连接池是必须要掌握的。 什么是连接池&#xff1f; 创建与管理连接缓冲池的技术&#xff0c;本质是资源复用&#xff0c;不用频繁创建与销毁连接&#xff0c;能提高性能。 画外音&#xff1a;数据库连接池…

Sentinel-2 L2A数据导入ENVI

Sentinel-2 L2A数据导入ENVI前言0 首先对SNAP进行设置1 用SNAP对Sentinel-2数据重采样2 在ENVI中打开重采样后的Sentinel-2数据3 其实不用重采样也行&#xff0c;ENVI可以直接打开解压后的Ssentinel-2文件&#xff0c;只需要将解压后的MTD_MSIL2A.xml拖进ENVI即可前言 Sentine…