Unity 圆形循环复用滚动列表

一.在上一篇垂直循环复用滚动列表的基础上,扩展延申了圆形循环复用滚动列表。实现此效果需要导入垂直循环复用滚动列表里面的类。

1.基础类

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using UnityEngine.UIElements;

/// <summary>
/// 环形的网格布局;
/// 让子对象摆成一个环形;
/// </summary>
public class CricleGrid : MonoBehaviour
{
    /// <summary>
    /// 是否是自动刷新模式,否则的话需要手动调用刷新;
    /// </summary>
    public bool IsAutoRefresh = true;

    /// <summary>
    /// 是否发生过改变;
    /// </summary>
    private bool IsChanged = false;
    /// <summary>
    /// 上一次检查的数量;
    /// </summary>
    int LastCheckCount = 0;

    /// <summary>
    /// Update每帧调用一次
    /// </summary>
    void Update()
    {
        检查是否需要自动刷新;
        if (!IsAutoRefresh)
            return;

        if (!IsChanged)
        {
            //检测子物体有没有被改变;
            GetChidList();
            int length = ListRect.Count;
            if (length != LastCheckCount)
            {
                LastCheckCount = length;
                IsChanged = true;
            }
            //此时刷新大小和位置;
            if (IsChanged)
                ResetSizeAndPos();
        }
        else
            RefreshAll();
    }

    private void OnValidate()
    {
        //编辑器下每一次更改需要实时刷新;
        RefreshAll();
    }

    /// <summary>
    /// 全部刷新;
    /// </summary>
    public void RefreshAll()
    {
        GetChidList();
        ResetSizeAndPos();
    }

    /// <summary>
    /// 当下激活的Rect;
    /// </summary>
    public List<RectTransform> ListRect = new List<RectTransform>(4);
    List<RectTransform> tempListRect = new List<RectTransform>(4);

    /// <summary>
    /// 获取父节点为本身的子对象
    /// </summary>
    void GetChidList()
    {
        ListRect.Clear();
        GetComponentsInChildren(false, tempListRect);
        int length = tempListRect.Count;
        for (int i = 0; i < length; i++)
        {
            var r = tempListRect[i];
            if (r.transform.parent != transform) continue;
            ListRect.Add(r);
        }
    }

    /// <summary>
    /// 网格大小;
    /// </summary>
    public Vector2 CellSize = new Vector2();

    /// <summary>
    /// 半径;
    /// </summary>
    public float Radius = 1;

    /// <summary>
    /// 起始角度;
    /// </summary>
    [Range(0f, 360f)]
    [SerializeField]
    float m_StartAngle = 30;

    /// <summary>
    /// 起始角度;
    /// </summary>
    public float StartAngle
    {
        get { return m_StartAngle; }
        set
        {
            m_StartAngle = value;
            IsChanged = true;
        }
    }

    /// <summary>
    /// 间隔角度;
    /// </summary>
    [Range(0f, 360f)]
    [SerializeField]
    float m_Angle = 30;

    /// <summary>
    /// 间隔角度;
    /// </summary>
    public float Angle
    {
        get { return m_Angle; }
        set
        {
            m_Angle = value;
            IsChanged = true;
        }
    }
    public Dictionary<int, CricleScrollItemPosData> itemPosDic = new();
    public List<CricleScrollItemPosData> itemPosList = new();
    /// <summary>
    /// 重新将字节点设置大小;
    /// </summary>
    public void ResetSizeAndPos()
    {
        int length = ListRect.Count;
        for (int i = 0; i < length; i++)
        {
            var tran = ListRect[i];
            tran.sizeDelta = CellSize;
            var v = GerCurPosByIndex(i);
            tran.anchoredPosition = new Vector2(v.x,v.y);
            tran.localEulerAngles = new Vector3(0,0, v.z);
        }
    }

    /// <summary>
    /// 返回第几个子对象应该所在的相对位置;
    /// </summary>
    public Vector3 GerCurPosByIndex(int index)
    {
        //1、先计算间隔角度:(弧度制)
        float totalAngle = Mathf.Deg2Rad * (index * Angle + m_StartAngle);
        //2、计算位置
        Vector3 Pos = new Vector2(Radius * Mathf.Cos(totalAngle), Mathf.Sin(totalAngle) * Radius);
        Pos.z = index * Angle + m_StartAngle + 180;
        return Pos;
    }

    public ScrollRect scrollRect;
    public List<object> list = new();
    public GameObject item;
    private List<CustomScrollItemMono> scrollTestItems = new();
    private int startIndex;
    private int endIndex;
    private int showItemCount = 10;



    private void InitItemsPos() 
    {
        int n = (int)(360f / Angle);
        for (int i = 0; i < list.Count; i++)
        {
            var v = GerCurPosByIndex(i);
            CricleScrollItemPosData data = new CricleScrollItemPosData();
            data.AnchoredPosition = new Vector3(v.x, v.y);
            data.LocalEulerAngles = new Vector3(0, 0, v.z);
            itemPosDic.Add(i, data);
            itemPosList.Add(data);
            //if (i < n)
            //{
            //    var v = GerCurPosByIndex(i);
            //    CricleScrollItemPosData data = new CricleScrollItemPosData();
            //    data.AnchoredPosition = new Vector3(v.x, v.y);
            //    data.LocalEulerAngles = new Vector3(0, 0, v.z);
            //    itemPosDic.Add(i, data);
            //    itemPosList.Add(data);
            //}
            //else 
            //{
            //    int temp = i % n;
            //    int m = (i + 1) / n;
            //    CricleScrollItemPosData d = new CricleScrollItemPosData();
            //    d.AnchoredPosition = itemPosDic[temp].AnchoredPosition;
            //    d.LocalEulerAngles.z += itemPosDic[temp].LocalEulerAngles.z + 360 * m;
            //    itemPosDic.Add(i, d);
            //    itemPosList.Add(d);
            //}
        }
        Debug.Log($"InitItemsPos ");
    }

    private void InitShowItems()
    {
        for (int i = 0; i < showItemCount; i++)
        {
            GameObject obj = Instantiate(item, transform);
            obj.transform.name = i.ToString();
            obj.SetActive(true);
            CustomScrollItemMono testItem = obj.GetComponent<CustomScrollItemMono>();
            testItem.Init(i, list[i]);
            scrollTestItems.Add(testItem);
        }
        item.SetActive(false);
    }



    private float eulerAnglersZ;
    private float preA = 1;
    public ScrollRect ScrollRect;
    private RectTransform Content;

    /// <summary>
    /// 用这个初始化
    /// </summary>
    void Start()
    {
        for (int i = 0; i < 100; i++)
        {
            list.Add(new ScrollTestData() { ID = i });
        }
        Content = ScrollRect.content;
        Content.sizeDelta = new Vector2(500, (100 * Angle / 360 + StartAngle % 360 / 360f) * 2 * Mathf.PI * Radius);
        InitItemsPos();
        InitShowItems();
        RefreshAll();
    }

    private void Awake()
    {
        scrollRect.horizontal = false;
        scrollRect.vertical = true;
        scrollRect.onValueChanged.AddListener((value) =>
        {
            float offset = Mathf.Abs(preA - value.y);
            float aa = (offset) * ((100 - 6) * Angle);
            if (scrollRect.velocity.y > 0) //手指上滑 
            {
                transform.localEulerAngles -= new Vector3(0,0, aa);
                eulerAnglersZ += aa;
            }
            else if (scrollRect.velocity.y < 0)//手指下滑 
            {
                transform.localEulerAngles += new Vector3(0, 0, aa);
                eulerAnglersZ -= aa;
            }
            preA = value.y;
 
            for (int i = startIndex; i < itemPosList.Count; i++)
            {
                if (i + 1 < itemPosList.Count)
                {
                    if (scrollRect.velocity.y > 0)//手指上滑 
                    {
                        var targetY = itemPosList[i + 1].LocalEulerAngles.z - 180;
                        //Debug.Log($"ccc y {y} targetY {targetY} startIndex {startIndex} Z {transform.localEulerAngles.z}");
                        if (eulerAnglersZ + StartAngle >= targetY)
                        {
                            startIndex = i + 1;
                            endIndex = startIndex + showItemCount - 1;
                            break;
                        }
                    }
                    else if (scrollRect.velocity.y < 0)//手指下滑
                    {
                        if (startIndex > 0 && startIndex < itemPosDic.Count)
                        {
                            var targetY = itemPosDic[startIndex].LocalEulerAngles.z - 180;
                            if (eulerAnglersZ + StartAngle <= targetY)
                            {
                                startIndex = i - 1;
                                endIndex = startIndex + showItemCount - 1;
                                break;
                            }
                        }
                    }
                }
            }
            //Debug.Log($"bbb startIndex {startIndex} endIndex {endIndex}");
            if (startIndex > 100 - showItemCount) 
            {
                startIndex = 100 - showItemCount;
            }
            if (endIndex >= itemPosDic.Count) { return; }
            int index = 0;
            for (int i = startIndex; i < endIndex + 1; i++)
            {
                if (index < scrollTestItems.Count && i < itemPosDic.Count)
                {
                    var item = scrollTestItems[index];
                    item.Init(i, list[i]);
                    var rect = item.gameObject.GetComponent<RectTransform>();
                    rect.anchoredPosition3D = itemPosDic[i].AnchoredPosition;
                    rect.localEulerAngles = itemPosDic[i].LocalEulerAngles;
                    index += 1;
                }
            }
        });
    }


    public class CricleScrollItemPosData
    {
        public Vector3 AnchoredPosition;
        public Vector3 LocalEulerAngles;
    }

}

2.UI目录

3.item克隆体,挂载脚本

4.按照图示,把CricleGrid脚本,挂在Items节点下,调整各个参数,运行即可。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

中国人工智能学会技术白皮书

中国人工智能学会的技术白皮书具有多方面的重要作用&#xff0c;是极具权威性和价值的参考资料。 看看编委会和编写组的阵容&#xff0c;还是很让人觉得靠谱的 如何下载这份资料呢&#xff1f;下面跟着步骤来吧 步骤一&#xff1a;进入中国智能学会官网。百度搜索“中国智能学…

maui开发成生安卓apk,运行提示该应用与此设备的CPU不兼容

在生成.NET MAUI安卓应用时遇到“该应用与此设备的CPU不兼容”的问题&#xff0c;确保你的.NET MAUI应用支持的Android目标框架与设备CPU架构相匹配。例如&#xff0c;如果你的应用是为ARM64架构编译的&#xff0c;而你的设备是x86架构&#xff0c;就会出现不兼容的问题。 一、…

二叉树 -- 堆(详解)

目录 1、堆的概念及结构 2、堆的实现(附代码) 2.1、向下调整算法建堆 3、堆的应用(附代码) 3.1、堆排序 3.2、TOP-K问题 1、堆的概念及结构 如果有一个关键码的集合K { k0&#xff0c;k1 &#xff0c;k2 &#xff0c;…&#xff0c;k(n-1) }&#xff0c;把它的所有元素…

windows环境下pytorch安装踩坑

目录 1 前言2 安装Anaconda3 安装CUDA4 创建Python3.9环境5 安装Pytorch环境5.1 conda方式5.2 pip方式 6 验证是否安装成功7 注意事项7.1 no module named torch问题7.12 torch.cuda.is_available()返回False问题 8 最佳实践9 总结 1 前言 这两天由于要使用Genesis&#xff0c;…

Linux系统命令基础

Linux命令⾏ [pypylinux ~]$ 普通⽤户py&#xff0c;登陆后 [rootpylinux ~]# 超级⽤户root&#xff0c;登录后root代表当前登录的⽤户 分隔符pylinux 主机名~ 当前的登录的位置&#xff0c;此时是家⽬录# 超级⽤户身份提示符 $ 普通⽤户身份提示符操作系统⽬录分隔符 Linux目录…

PHP木马编写

一、最简单的一句话木马 <?php eval($_REQUEST[cmd]); ?> 1. <?php 和 ?> <?php 和 ?> 是 PHP 代码的开始和结束标记&#xff0c;表示 PHP 代码块的范围。 2. eval() eval() 是 PHP 中的一个内建函数&#xff0c;用来执行字符串类型的 PHP 代码。…

[Unity]【图形渲染】【游戏开发】Shader数学基础4-更多矢量运算

在计算机图形学和着色器编程中,矢量运算是核心的数学工具之一。矢量用于描述空间中的位置、方向、速度等各种物理量,并在图形变换、光照计算、纹理映射等方面起着至关重要的作用。本篇文章将详细讲解矢量和标量之间的乘法与除法、矢量的加法与减法、矢量的模与单位矢量、点积…

基于JSP动漫论坛的设计与实现【源码+文档】

目录 摘 要 Abstract 1. 绪论 1.1 课题背景 1.2 国内外现状 1.3 动漫论坛系统特点 1.4 发展前景 1.5 所做的主要工作 2. 可行性分析及需求分析 2.1 可行性分析 2.1.1 经济可行性 2.1.2 技术可行性 2.1.3 运行可行性 2.2 需求分析 2.2.1 功能需求 …

VCU--新能源汽车VCU电控开发

课程目标 信号采集的原理 使用simulink处理信号 做一个MIL仿真测试 零、参考 构建Simulink模型——CAN通信 | chans Bloggerrrrr基于Simulink实现CAN报文解析(unpack)与打包(pack)任务_RichardsZ_-开放原子开发者工作坊 一、功能概述 1.硬线信号 定义&#xff1a;通过物…

nodejs搭配express网站开发后端接口设计需要注意事项

nodejs搭配express网站开发后端接口设计需要注意事项&#xff01;为了回避一些常见的误区&#xff0c;今天和大家汇总一下&#xff0c;最近我遇到的一些错误信息&#xff0c;虽然都是小问题&#xff0c;但是还是需要分享一下&#xff0c;以免大家再次犯错。 1&#xff1a;第一个…

【时间之外】IT人求职和创业应知【71】-专利费

目录 2025 ICT产业趋势年会召开&#xff0c;2024年度ICT十大新闻重磅揭晓 海纳致远数字科技申请定制化插件驱动的数据分析专利 阿波罗智联取得语音数据的处理方法、装置、设备和存储介质专利 心勿贪&#xff0c;贵知足。 感谢所有打开这个页面的朋友。人生不如意&#xff0…

问题小记-达梦数据库报错“字符串转换出错”处理

最近遇到一个达梦数据库报错“-6111: 字符串转换出错”的问题&#xff0c;这个问题主要是涉及到一条sql语句的执行&#xff0c;在此分享下这个报错的处理过程。 问题表现为&#xff1a;一样的表结构和数据&#xff0c;执行相同的SQL&#xff0c;在Oracle数据库中执行正常&…

数据结构——队列的模拟实现

大家好&#xff0c;上一篇博客我带领大家进行了数据结构当中的栈的模拟实现 今天我将带领大家实现一个新的数据结构————队列 一&#xff1a;队列简介 首先来认识一下队列&#xff1a; 队列就像我们上学时的排队一样&#xff0c;有一个队头也有一个队尾。 有人入队的话就…

前端面试汇总(不定时更新)

目录 HTML & CSS1. XML、HTML、XHTML 有什么区别&#xff1f;⭐2. XML和JSON的区别&#xff1f;3. 是否了解W3C的规范&#xff1f;⭐4. 什么是语义化标签&#xff1f;⭐⭐5. 行内元素和块级元素的区别&#xff1f;⭐6. 行内元素和块级元素的转换&#xff1f;⭐7. 常用的块级…

在UE5中调用ImGui图形界面库

ImGui是一个小巧灵活、简洁美观的图形界面库 首先我们直接参考Github https://github.com/SLSNe/Unreal5-ImGui 把项目下载下来后 打开项目目录或者引擎目录 项目根目录/Plugins/ImGui/ 或 UE5引擎根目录/Engine/Plugins/ 如果没有Plugins文件夹就新建一个 把项目放里面…

数据结构与算法:稀疏数组

前言 此文以整型元素的二维数组为例&#xff0c;阐述稀疏数组的思想。其他类型或许有更适合压缩算法或者其他结构的稀疏数组&#xff0c;此文暂不扩展。 稀疏数组的定义 在一个二维数据数组里&#xff0c;由于大量的元素的值为同一个值&#xff0c;比如 0或者其他已知的默认值…

【蓝桥杯】43699-四平方和

四平方和 题目描述 四平方和定理&#xff0c;又称为拉格朗日定理&#xff1a; 每个正整数都可以表示为至多 4 个正整数的平方和。如果把 0 包括进去&#xff0c;就正好可以表示为 4 个数的平方和。 比如&#xff1a; 502021222 712121222; 对于一个给定的正整数&#xff0c;可…

ECharts散点图-SymbolShapeMorph,附视频讲解与代码下载

引言&#xff1a; ECharts散点图是一种常见的数据可视化图表类型&#xff0c;它通过在二维坐标系或其它坐标系中绘制散乱的点来展示数据之间的关系。本文将详细介绍如何使用ECharts库实现一个散点图&#xff0c;包括图表效果预览、视频讲解及代码下载&#xff0c;让你轻松掌握…

会话控制(cookie、session 和 token)

1. 介绍 所谓会话控制就是 对会话进行控制HTTP 是一种无状态的协议&#xff0c;它没有办法区分多次的请求是否来自于同一个客户端&#xff0c; 无法区分用户&#xff0c;而产品中又大量存在的这样的需求&#xff0c;所以我们需要通过 会话控制 来解决该问题。 常见的会话控制…

「九」HarmonyOS 5 端云一体化实战项目——「M.U.」应用云侧开发云数据库

1 立意背景 M. 代表 “我”&#xff0c;U. 代表 “你”&#xff0c;这是一款用于记录情侣从相识、相知、相恋、见家长、订婚直至结婚等各个阶段美好记忆留存的应用程序。它旨在为情侣们提供一个专属的空间&#xff0c;让他们能够将一路走来的点点滴滴&#xff0c;如初次相遇时…