UGUI无线滑动列表

news2024/11/22 16:13:36

        在游戏开发中,经常会遇到需要展示大量数据的情况,例如排行榜、背包等。为了优化显示效果和性能,一个常见的做法是使用无限滑动列表(Infinite Scroll View)。本文将详细解析如何实现无限滑动列表。

基本原理

        无限滑动列表的基本原理是,通过动态加载和回收Item的方式,实现滑动时只创建和显示可见的Item,大大减少了内存开销和渲染性能。

实现思路

无限滑动列表的实现思路如下:

  1. 创建一个滑动框,其中包含一个Content组件,用来容纳所有的Item。
  2. 在Content中使用GridLayoutGroup布局组件,目的是把Item按照规则排列。
  3. 固定显示一定数量的Item,剩下的Item通过滑动来动态创建和销毁。
  4. 根据滑动的方向(水平或竖直),判断滑动的距离和速度,当滑动超过一定阈值时,创建或销毁Item,并根据数据设置其位置和显示内容。

主要实现步骤

下面将具体介绍实现无限滑动列表的主要步骤:

  1. 初始化Init 首先,我们需要在脚本中声明一些变量和属性,用来控制滑动列表的显示和行为。在Init方法中,我们需要获取滑动框的组件,设置布局,设置Content的大小,并实例化固定数量的Item,这里使用了一个itemPrefab作为Item的模板。

  2. 处理滑动OnScroll 在OnScroll方法中,我们需要根据滑动的距离和速度,判断是否需要创建或销毁Item,以及设置其位置和显示内容。这里需要根据滑动的方向(水平或竖直)来做相应的处理。在滑动过程中,我们通过改变Item的位置和显示内容来实现无限滑动的效果。

  3. 设置Item的位置和显示内容 通过SetPos方法,我们可以设置Item的位置。其中,根据滑动的方向(水平或竖直),设置Item的锚点位置和偏移量。

  4. 外部调用 提供了一些外部调用的方法,用于设置显示内容、设置数据总数量和销毁所有的Item。通过SetShow方法,我们可以自定义Item的显示内容。通过SetTotalCount方法,我们可以设置数据的总数量。通过DestoryAll方法,我们可以销毁所有的Item,以便重置滑动列表。

运行实例

代码实现

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Linq;

/// <summary>
/// 无限滑动列表
/// </summary>
public class InfiniteCrollView : MonoBehaviour
{
    private ScrollRect scrollRect;//滑动框组件
    private RectTransform content;//滑动框的Content
    private GridLayoutGroup layout;//布局组件

    [Header("滑动类型")]
    public ScrollType scrollType;
    [Header("固定的Item数量")]
    public int fixedCount;
    [Header("Item的预制体")]
    public GameObject itemPrefab;

    private int totalCount;//总的数据数量
    private List<RectTransform> dataList = new List<RectTransform>();//数据实体列表
    private int headIndex;//头下标
    private int tailIndex;//尾下标
    private Vector2 firstItemAnchoredPos;//第一个Item的锚点坐标

    #region Init

    /// <summary>
    /// 实例化Item
    /// </summary>
    private void InitItem()
    {
        for (int i = 0; i < fixedCount; i++)
        {
            GameObject tempItem = Instantiate(itemPrefab, content);
            dataList.Add(tempItem.GetComponent<RectTransform>());
            SetShow(tempItem.GetComponent<RectTransform>(), i);
        }
    }

    /// <summary>
    /// 设置Content大小
    /// </summary>
    private void SetContentSize()
    {
        int h;
        int v;
        if (scrollType == ScrollType.Horizontal)
        {
            h = totalCount;
            v = 1;
        }
        else
        {
            h = 1;
            v = totalCount;
        }

        content.sizeDelta = new Vector2
            (
                            layout.padding.left + layout.padding.right + h * (layout.cellSize.x + layout.spacing.x) - layout.spacing.x - transform.GetComponent<RectTransform>().rect.width+10,//10是一个调整数为了防止最后一个item显示正常可以按需调整
                layout.padding.top + layout.padding.bottom + v * (layout.cellSize.y + layout.spacing.y) - layout.spacing.y

            );
    }

    /// <summary>
    /// 设置布局
    /// </summary>
    private void SetLayout()
    {
        layout.startCorner = GridLayoutGroup.Corner.UpperLeft;
        layout.startAxis = GridLayoutGroup.Axis.Horizontal;
        layout.childAlignment = TextAnchor.UpperLeft;
        layout.constraintCount = 1;
        if (scrollType == ScrollType.Horizontal)
        {
            scrollRect.horizontal = true;
            scrollRect.vertical = false;
            layout.constraint = GridLayoutGroup.Constraint.FixedRowCount;
        }
        else if (scrollType == ScrollType.Vertical)
        {
            scrollRect.horizontal = false;
            scrollRect.vertical = true;
            layout.constraint = GridLayoutGroup.Constraint.FixedColumnCount;
        }
    }

    /// <summary>
    /// 得到第一个数据的锚点位置
    /// </summary>
    private void GetFirstItemAnchoredPos()
    {
        firstItemAnchoredPos = new Vector2
            (
                layout.padding.left + layout.cellSize.x / 2,
                -layout.padding.top - layout.cellSize.y / 2
            );
    }

    #endregion

    #region Main

    /// <summary>
    /// 滑动中
    /// </summary>
    private void OnScroll(Vector2 v)
    {
        if (dataList.Count == 0)
        {
            Debug.LogWarning("先调用SetTotalCount方法设置数据总数量再调用Init方法进行初始化");
            return;
        }

        if (scrollType == ScrollType.Vertical)
        {
            //向上滑
            while (content.anchoredPosition.y >= layout.padding.top + (headIndex + 1) * (layout.cellSize.y + layout.spacing.y)
            && tailIndex != totalCount - 1)
            {
                //将数据列表中的第一个元素移动到最后一个
                RectTransform item = dataList[0];
                dataList.Remove(item);
                dataList.Add(item);

                //设置位置
                SetPos(item, tailIndex + 1);
                //设置显示
                SetShow(item, tailIndex + 1);

                headIndex++;
                tailIndex++;
            }
            //向下滑
            while (content.anchoredPosition.y <= layout.padding.top + headIndex * (layout.cellSize.y + layout.spacing.y)
                && headIndex != 0)
            {
                //将数据列表中的最后一个元素移动到第一个
                RectTransform item = dataList.Last();
                dataList.Remove(item);
                dataList.Insert(0, item);

                //设置位置
                SetPos(item, headIndex - 1);
                //设置显示
                SetShow(item, headIndex - 1);

                headIndex--;
                tailIndex--;
            }
        }
        else if (scrollType == ScrollType.Horizontal)
        {
            //向左滑
            while (content.anchoredPosition.x <= -layout.padding.left - (headIndex + 1) * (layout.cellSize.x + layout.spacing.x)
            && tailIndex != totalCount - 1)
            {
                //将数据列表中的第一个元素移动到最后一个
                RectTransform item = dataList[0];
                dataList.Remove(item);
                dataList.Add(item);

                //设置位置
                SetPos(item, tailIndex + 1);
                //设置显示
                SetShow(item, tailIndex + 1);

                headIndex++;
                tailIndex++;
            }
            //向右滑
            while (content.anchoredPosition.x >= -layout.padding.left - headIndex * (layout.cellSize.x + layout.spacing.x)
            && headIndex != 0)
            {
                //将数据列表中的最后一个元素移动到第一个
                RectTransform item = dataList.Last();
                dataList.Remove(item);
                dataList.Insert(0, item);

                //设置位置
                SetPos(item, headIndex - 1);
                //设置显示
                SetShow(item, headIndex - 1);

                headIndex--;
                tailIndex--;
            }
        }
    }

    #endregion

    #region Tool

    /// <summary>
    /// 设置位置
    /// </summary>
    private void SetPos(RectTransform trans, int index)
    {
        if (scrollType == ScrollType.Horizontal)
        {
            trans.anchoredPosition = new Vector2
            (
                index == 0 ? layout.padding.left + firstItemAnchoredPos.x :
                layout.padding.left + firstItemAnchoredPos.x + index * (layout.cellSize.x + layout.spacing.x),
                firstItemAnchoredPos.y
            );
        }
        else if (scrollType == ScrollType.Vertical)
        {
            trans.anchoredPosition = new Vector2
            (
                firstItemAnchoredPos.x,
                index == 0 ? -layout.padding.top + firstItemAnchoredPos.y :
                -layout.padding.top + firstItemAnchoredPos.y - index * (layout.cellSize.y + layout.spacing.y)
            );
        }
    }

    #endregion

    #region 外部调用

    /// <summary>
    /// 初始化
    /// </summary>
    public void Init()
    {
        scrollRect = GetComponent<ScrollRect>();
        content = scrollRect.content;
        layout = content.GetComponent<GridLayoutGroup>();
        scrollRect.onValueChanged.AddListener((Vector2 v) => OnScroll(v));

        //设置布局
        SetLayout();

        //设置头下标和尾下标
        headIndex = 0;
        tailIndex = fixedCount - 1;

        //设置Content大小
        SetContentSize();

        //实例化Item
        InitItem();

        //得到第一个Item的锚点位置
        GetFirstItemAnchoredPos();
    }

    /// <summary>
    /// 设置显示
    /// </summary>
    public void SetShow(RectTransform trans, int index)
    {
        //=====根据需求进行编写
        trans.GetComponentInChildren<Text>().text = index.ToString();
        trans.name = index.ToString();
    }

    /// <summary>
    /// 设置总的数据数量
    /// </summary>
    public void SetTotalCount(int count)
    {
        totalCount = count;
    }

    /// <summary>
    /// 销毁所有的元素
    /// </summary>
    public void DestoryAll()
    {
        for (int i = dataList.Count - 1; i >= 0; i--)
        {
            DestroyImmediate(dataList[i].gameObject);
        }
        dataList.Clear();
    }

    #endregion
}

/// <summary>
/// 滑动类型
/// </summary>
public enum ScrollType
{
    Horizontal,//竖直滑动
    Vertical,//水平滑动
}

        这篇代码实现了一个无限滑动列表,在Unity引擎中展示。它可以在水平方向或垂直方向上滑动,并根据固定的Item数量在滑动过程中动态加载和复用Item对象,以实现无限滑动的效果。下面将按照步骤逐一解析该代码。

代码解析

首先,代码定义了一个InfiniteScrollView类,该类继承自MonoBehaviour类,用于管理滑动列表的各项功能。该类主要包含以下成员变量:

  1. scrollRect:滑动框组件,用于控制滑动的行为。
  2. content:滑动框的Content,用于放置Item对象。
  3. layout:布局组件,用于控制Item对象的排列方式。
  4. scrollType:滑动类型枚举,用于指定滑动是水平还是垂直。
  5. fixedCount:固定的Item数量,用于定义一次滑动的可显示Item的数量。
  6. itemPrefab:Item的预制体,用于生成Item对象。

接下来,代码定义了一些辅助函数来初始化和操作滑动列表。其中包括:

  1. InitItem():实例化Item,根据fixedCount生成对应数量的Item对象,并将它们添加到dataList中。
  2. SetContentSize():根据totalCount和布局设置,计算并设置Content的大小,以便正确显示所有的Item。
  3. SetLayout():根据scrollType设置滑动框的布局相关属性,如起始角落、排列方式和约束条件。
  4. GetFirstItemAnchoredPos():计算第一个Item的锚点位置,以便在滑动列表中正确显示。

然后,代码定义了一个OnScroll()函数,用于监听滑动事件,并在滑动过程中处理Item的加载和复用。具体实现如下:

  1. 针对垂直滑动方式,当content的纵向偏移量超过下一个Item位置时,将dataList中的第一个元素移动到最后一个位置,并更新头尾下标,以及移动后的位置和展示。
  2. 针对水平滑动方式,当content的横向偏移量超过下一个Item位置时,将dataList中的第一个元素移动到最后一个位置,并更新头尾下标,以及移动后的位置和展示。

最后,代码提供了一些外部调用的函数,以便进行初始化和设置。它们包括:

  1. Init():初始化滑动列表,内部调用了上述的SetLayout、SetContentSize、InitItem和GetFirstItemAnchoredPos函数。
  2. SetShow():设置Item的显示内容,可以根据需求进行自定义。
  3. SetTotalCount():设置滑动列表中Item的总数量。
  4. DestoryAll():销毁所有的Item对象。

通过以上的解析和编写指导,该技术博客将尽可能详细地介绍了无限滑动列表的实现原理和具体操作步骤,帮助读者了解和使用该功能

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

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

相关文章

市电电压双向越限报警保护器电路设计

该报警保护器能在市电电压高于或低于规定值时&#xff0c;进行声光报警&#xff0c;同时自动切断电器电源&#xff0c;保护用电器不被损坏。该装置体积小、功能全、制作简单、实用性强。 一、电路工作原理 电路原理如图 3 所示。 市电电压一路由C3降压&#xff0c;DW稳压&am…

驱动开发:应用DeviceIoContro模板精讲

在笔者上一篇文章《驱动开发&#xff1a;应用DeviceIoContro开发模板》简单为大家介绍了如何使用DeviceIoContro模板快速创建一个驱动开发通信案例&#xff0c;但是该案例过于简单也无法独立加载运行&#xff0c;本章将继续延申这个知识点&#xff0c;通过封装一套标准通用模板…

一、枚举类型——新特性(switch 中的箭头语法)

支持模式匹配 你可以认为模式匹配&#xff08;pattern matching&#xff09;是在 switch 关键字上进行了显著的功能扩充。 它是分成了多个模块、 历经了 Java 的多个版本持续实现的。这保证了每个模块在其他模块加入前都可以安全地运行。最后&#xff0c;所有的模块集中到一起…

LLM大模型应用开发的本地环境搭建

尽管 ChatGPT 仍然很受欢迎&#xff0c;但泄露的 Google 内部文件表明开源社区正在迎头赶上并取得重大突破。 我们现在能够在消费级 GPU 上运行大型 LLM 模型。 因此&#xff0c;如果你是一名开发人员&#xff0c;想要在本地环境中尝试这些 LLM 并用它构建一些应用程序&#x…

Kubernetes进阶实战2

Kubernetes具有以下几个重要特性 简言之&#xff0c;Kubernetes整合并抽象了底层的硬件和系统环境等基础设施&#xff0c;对外提供了一个统一的资源池供终端用户通过API进行调用。 Kubernetes具有以下几个重要特性。 &#xff08;1&#xff09;自动装箱 构建于容器之上&#x…

定时器T0流水灯

89C52RC芯片 12Mhz&#xff1a;FC18 11.0592Mhz &#xff1a;FC67 定时器T0初值计算 12Mhz 11.0592Mhz main.c #include<regx52.h> #include<intrins.h> //_crol_循环左移函数 #include "Timer0.h" #include "Key.h" /*定时器&#xff0c;…

玩转k8s:资源管理

1 资源管理介绍 在kubernetes中&#xff0c;所有的内容都抽象为资源&#xff0c;用户需要通过操作资源来管理kubernetes。 kubernetes的本质上就是一个集群系统&#xff0c;用户可以在集群中部署各种服务&#xff0c;所谓的部署服务&#xff0c;其实就是在kubernetes集群中运行…

【C++】哈希表的改造——unordered_map和unordered_set的模拟实现

文章目录 1. unordered系列的容器封装1.1 改造1:模版参数类型的改造1.1.1 HashNode改造1.1.2 HashTable改造 1.2 改造2:迭代器的增加与封装1.2.1 迭代器类的实现1.2.2 迭代器的封装 1.3 改造3:insert的改写封装1.4 析构函数的实现1.5 unordered_map&unordered_set的封装实现…

Range_image 可视化

范围图像与点云的区别 范围图像&#xff08;Range Image&#xff09;和点云&#xff08;Point Cloud&#xff09;是两种常见的表示和处理三维点数据的方式&#xff0c;它们之间有以下区别&#xff1a; 数据结构&#xff1a;点云是一组三维点的集合&#xff0c;每个点包含位置信…

React解决setState异步带来的多次修改合一和修改后立即使用没有变化问题

我们编写这样一段代码 import React from "react" export default class App extends React.Component {constructor(props){super(props);this.state {cont: 0}}componentDidMount() {this.setState({cont: this.state.cont1})}render(){return (<div>{ thi…

Ubuntu下编译VTK

1.先安装QT&#xff0c;不知道不装行不行&#xff0c;我们项目需要。 2.去VTK官网下载VTK源码。 3.解压源码。 4.编译需要用cmake-gui&#xff0c;装QT的一般都有&#xff0c;但需要把路径添加到PATH才能用。 5.打开cmake-gui&#xff0c;设置源码路径&#xff0c;编译输出路…

项目——学生信息管理系统6

目录 权限的处理 在 MainFrm中定义 一个 权限判断的方法 在 MainFrm 的构造方法中调用一下 测试用学生身份登录 测试用教师身份登录 接下来&#xff0c;我们到学生列表页面里面 ManageStudentFrm&#xff0c;继续权限的设置&#xff0c;学生只能查看自己的信息&#xff0c…

神坑:ElasticSearch8集群启动报错“Device or resource busy”(Docker方式)

昨天在Docker中配置ElasticSearcch8集群模式时&#xff0c;先初步配置了master主节点。然后主节点启动就报错&#xff0c;看日志&#xff0c;提示“Device or resource busy”。异常第一句大概这个样子&#xff1a; Exception in thread "main" java.nio.file.FileS…

WMS 窗口属性

WMS 窗口属性 1、窗口类型与层级1.1 Application Window普通应用程序窗口1.2 Sub Window子窗口1.3 System Window系统窗口 2、层级值与窗口类型2.1 WindowState2.2 WindowManagerPolicy 窗口管理的策略机制2.3 WindowToken句柄 3、窗口属性 LayoutParams Activity的预览窗口Sta…

基于51单片机的秒表系统

目录 基于51单片机的秒表系统一、原理图二、部分代码三、视频演示 基于51单片机的秒表系统 一、原理图 二、部分代码 #include <reg52.h>#define duanxuan P2 #define weixuan P1unsigned char code wxcode[]{0X01, 0x02, 0x04, 0x08, 0X10, 0X20, 0X40, 0X80};…

C# WPF 路径动画

路径动画&#xff1a;一个东西沿着你画的的线跑。 微软对这个有很详细的说明&#xff0c;有需要请参照微软Learn网站 cs的代码 PathGeometry pathGeometry new PathGeometry();PathFigure pathFigure new PathFigure();//pathFigure.IsClosed true;pathFigure.StartPoint…

试着攻击自己写的网站

背景介绍 概念简介 CSRF XSS攻击是一种常见的安全攻击&#xff0c;它通过伪造用户输入&#xff0c;利用CSRF漏洞获取用户的敏感信息或者执行恶意操作。CSRF XSS攻击不仅会给用户带来损失&#xff0c;还会对互联网安全造成威胁。因此&#xff0c;防范CSRF XSS攻击已经成为了当…

C++ - 20230629

一. 思维导图 二. 练习 #include <iostream> using namespace std;class Person { private:int age;int *p; public://无参构造Person():p(new int(89)){age 18;}//有参构造Person(int age,int num){this->age age;this->pnew int(num);}//拷贝构造函数Person(P…

jmeter:BeanShell预处理程序获取/设置/引用变量

BeanShell预处理程序 1、局部变量 获取局部变量&#xff1a;vars.get("变量名") 设置局部变量&#xff1a;vars.put("变量名",变量值) 调用 ${变量名} 2、全局变量 获取局部变量&#xff1a;props.get("变量名") 设置局部变量&#xff1a…

使用 JCommander 解析命令行参数

前言 如果你想构建一个支持命令行参数的程序&#xff0c;那么 jcommander 非常适合你&#xff0c;jcommander 是一个只有几十 kb 的 Java 命令行参数解析工具&#xff0c;可以通过注解的方式快速实现命令行参数解析。 这篇教程会通过介绍 jcommadner &#xff0c;快速的创建一…