Unity笔记:ScrollRect代码阅读

news2024/11/15 14:00:15

大体流程

Unity Docs - UGUI | Class ScrollRect

总的说

自身不负责Rebuild,设置脏之后交由LayoutRebuilder注册到CanvasUpdateRegistry里待rebuild的集合在固定时机统一Rebuild。自身只在Prelayout和Postlayout做一下数据准备和数据更新

自身的ICanvasElement.Rebuild是在CanvasUpdateRegistry.PerformUpdate对集合内的每个合法的(是的会先检验)ICanvasElement依次调用的

  1. 根据各种信息计算view和content的边界
  2. 根据输入(拖动等)更新边界(修改AnchorPosition)

content的移动是这样的流程:

  1. 通过记录当前光标位置和触发滚动的起始位置的差值计算出Content锚点的offset
  2. position += offset
  3. SetContentAnchoredPosition(position)

SetContentAnchoredPosition(position)中如果限制在垂直或者水平就忽略某个维度的内容,随后把Content的锚点设为position并使用UpdateBounds更新

本质上就是通过输入移动Content,位置不动的mask遮罩确保了只显示某一个区域。

开头

[AddComponentMenu("UI/Scroll Rect", 37)]	// 添加到菜单
[SelectionBase]
[ExecuteAlways]		// 编辑模式和Play模式都能执行
[DisallowMultipleComponent]	// 不允许多个同类脚本
[RequireComponent(typeof(RectTransform))]	// 需要具有RectTransform组件

这个[SelectionBase]就是挂在子物体上,点击子物体时在Hierarchy选中根物体。

Drive & Implementaton

UIBehaviour, IInitializePotentialDragHandler, IEventSystemHandler, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler, ICanvasElement, ILayoutElement, ILayoutGroup, ILayoutController
  • UIBehaviour:所有组件的基类,它提供了 UI 组件生命周期管理的基础功能,比如 OnEnableOnDisableOnDestroy
  • IInitializePotentialDragHandler:用于处理潜在的拖拽操作。在用户开始拖拽一个 UI 元素之前被调用。你可以在 OnInitializePotentialDrag 方法中设置拖拽的初始状态
  • IEventSystemHandler:标记接口,不直接定义任何方法。它用于表示某个组件可以处理事件系统的事件。所有 UI 事件接口(如 IDragHandlerIBeginDragHandler 等)都继承自这个接口,确保它们可以被事件系统识别和调用
  • IBeginDragHandler:处理拖拽开始的事件。当用户开始拖拽 UI 元素时,OnBeginDrag 方法会被调用,在这个方法中处理拖拽开始时的逻辑
  • IEndDragHandler:处理拖拽结束的事件。当用户结束拖拽 UI 元素时,OnEndDrag 方法会被调用,在这个方法中处理拖拽结束时的逻辑
  • IDragHandler:处理拖拽进行中的事件。当用户在拖拽 UI 元素时,OnDrag 方法会被调用,在这个方法中处理拖拽的过程中发生的事情,比如更新拖拽位置
  • IScrollHandler:处理滚动事件。当用户在 UI 元素上滚动(如鼠标滚轮)时,OnScroll 方法会被调用,在这个方法中处理滚动的逻辑
  • ICanvasElement:标记接口,用于表示 UI 元素是 Canvas 组件的一部分。实现了这个接口的组件可以在 Canvas 上进行布局计算和更新
  • ILayoutElement:定义了参与布局计算的元素应具有的基本属性,UI布局系统使用这些信息计算布局
  • ILayoutGroup:用于管理一组 UI 元素的布局。它通常作为容器(如 HorizontalLayoutGroupVerticalLayoutGroup)的基类,负责对子元素进行自动排列和调整。
  • ILayoutController:用于控制和管理布局过程。它处理布局计算和更新,确保 UI 元素按照指定的规则进行布局。

参数定义

然后紧接着就是很多参数的定义,以及配套getset的属性。也没啥列举的必要

个别参数在set的时候会标记脏,例如viewport在set时会SetDirtyCaching

ScrollBar的话则是为onValueChanged事件Addlistener,最终调用的是SetNormalizedPosition:一个统一的能分别根据横纵两种模式设置滚动条的函数。当然这个属性在Set的时候会先移除旧ScrollBar的Listener。

OnValueChanged主要内容发生滚动时触发。这会传一个Vector2表示滚动方向,如果仅允许一种滚动,例如仅允许Y轴向的滚动,则只会使用其中的y值

normalizedPosition 则与当前滚动的位置关联,这是一个介于0到1之间的浮点数。在实现无限列表的时候,这里需要额外处理,其逻辑先挖个坑


SetDirtyCaching的行为:

  • Set ScrollBar
  • 设置ScrollBar的visibility

SetDirty的行为

  • 调整horizontalScrollbarSpacing(以及垂直的也是)
  • OnEnable(注意OnDisable并没有)

horizontalScrollbarSpacing为例,viewport到底边是有个距离放ScrollBar的,那个距离就是这个Spacing,Vertical版本同理

主要接口实现

Rebuild:

部分函数&功能

标记脏

这个问题涉及重建,例行先看看别人的文章打底:
知乎 - Unity UI重建(Rebuild)源码分析
简书 - UGUI Layout

话说回来,这个组件有两个标记脏的方法:

protected void SetDirty()
{
    if (IsActive())
    {
        LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
    }
}

MarkLayoutForRebuild这个方法主要会递归查到一个根物体,然后为之添加LayoutRebuilder,然后把重建器加到待重建的队列等待重建时统一处理。RegisterCanvasElementForLayoutRebuild这个只对本级注册重建。

这两个注册最后调的接口是一样的。

protected void SetDirtyCaching()
{
    if (IsActive())
    {
        CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
        LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
        m_ViewRect = null;
    }
}

据说,布局重建不一定导致图形重建,例如一个按钮平移了一下。

滚动与拖动

Scroll是滚动,Drag是拖动

由滚动条和滚轮输入的算滚动,点击屏幕拉动的算拖动。

OnScroll是主要的处理滚动的函数:

主要注意拿到Vector2 scrollDelta会给y乘-1,因为向上滚动是正方向。

滚动的根本逻辑是结合scrollDelta和滚动系数去修改RectTransform的anchorPosition


关于拖动的代码再说一下这个:

// 在滚动前设置速度为0
// 因为ScrollRect存在一个属性inertia模拟滚动的惯性
// 所以存在没有拖动但是因为模拟了惯性导致内容依旧在移动的情况(此时速度不为0)
public virtual void OnInitializePotentialDrag(PointerEventData eventData)
{
    if (eventData.button == PointerEventData.InputButton.Left)
    {
        m_Velocity = Vector2.zero;
    }
}

※边界

Bound相关的还是挺重要的。这基本上就是界面滚动最底层的逻辑了

好吧我吐槽一下之前看1.0.0的代码给我看难受了,代码风格和最新的ugui的差很多。不仅没有注释,当他写出Vector2 zero = Vector2.zero后还修改了zero的值后使我深深滴难受了一下。

internal static void AdjustBounds(ref Bounds viewBounds, ref Vector2 contentPivot, ref Vector3 contentSize, ref Vector3 contentPos)
{
    Vector3 vector = viewBounds.size - contentSize;
    if (vector.x > 0f)
    {
        contentPos.x -= vector.x * (contentPivot.x - 0.5f);
        contentSize.x = viewBounds.size.x;
    }

    if (vector.y > 0f)
    {
        contentPos.y -= vector.y * (contentPivot.y - 0.5f);
        contentSize.y = viewBounds.size.y;
    }
}

首先,Bound相关的操作应该是在view那个坐标系下计算的(一般情况下Content是view的子级)。

  • GetBounds返回的是m_Content相对viewRect的位置和大小
  • UpdateBounds则分为两段,前一段是走了一遍AdjustBounds,这次的结果确保Content至少和View一样大(因为只有view小于content才能滚动)。之后如果是MovementType.Clamped,则计算contentBound的max和min的xy是否大于view对应的,相当于是计算contentBound是否有超出view的,如果有(即其结果的平方大于常量float.Epsilon)则调用AdjustBounds(这是第二次调用)
  • AdjustBounds注释说是确保Content至少和view一样大。但是我手动计算逻辑认定会移动Content,使其Pivot位于view的中心位置,然后Content大小放到至少和view一样大。

此外:

  1. 当Content大小大于等于view时是不会触发AdjustBounds
  2. zero变量(或者后面版本的delta)实际上保存的是水平方向和垂直方向上contentBounds超出viewBound的长度

注:float.Epsilon是一个比0大但非常接近0的常量,代表 float 类型中最小的正数,也可以用作表示浮点数运算中可能存在的微小误差的常量。


但是我有一个大大滴疑问:第二次调用AdjustBounds我觉得不会被执行。因为第一次AdjustBounds已经把二者Size搞一样了,第二次跟本进不去if。我不太清楚怎么回事。

LateUpdate

  1. 通过调用EnsureLayoutHasRebuilt确保已经重建
  2. 调用UpdateBounds
  3. 如果超出可滚动范围

布局更新周期

Canvas.willRenderCanvases在 Canvas 即将开始渲染之前被调用。布局重建函数是+=到这上面的,届时会分别遍历m_LayoutRebuildQueue跟m_GraphicRebuildQueue并对合法的ICanvasElement实例执行ICanvasElement.Rebuild

渲染机制

CnBlogs - 浅谈UGUI的渲染机制

两种重建与三种脏标记

  1. 图像重建和布局重建
  2. 布局脏、顶点脏、材质脏

Github Pages - UGUI Rebuild
知乎 - 【Unity源码学习】网格重建

  1. RectTransform的属性发生变化导致SetLayoutDirty
  2. 顶点变化(如图像fill参数变化时)会导致SetVerticesDirty进而图像重建
  3. Graphic派生出的带有color属性的,这个属性改了也会顶点脏
  4. 材质变化也是图像重建
  5. 重建是先布局后图像

Question:这俩重建哪个开销大?
A:图像重建大

通过阅读理解到的优化建议

方向:

  1. 避免重建(某些元素本不需要)
  2. 避免OverDraw
  3. 减少CPU和GPU的IO(比如通过图集)

Tips:

  1. 禁用 Canvas 组件不会通过 Canvas 层次结构触发昂贵的 OnDisable/OnEnable 回调
  2. 自动布局代价很昂贵
  3. 重建画布产生的主要是CPU成本

在这里插入图片描述

  1. 使用全屏 UI 时,应隐藏其他所有内容,或者在全屏 UI 期间降低 Application.targetFrameRate
  2. 避免元素堆叠(针对OverDraw)
  3. 多个Mask之间可以进行合批
  4. Mask内外不能进行合批.\
  5. RectMask2D本身不产生drawcall

参考

博客园 - Unity编辑器扩展基础总结 | 第2章 标准编辑器扩展

简书 - Unity优化 | 如何优化UGUI的ScrollRect

51CTO - 【Unity UGUI】ScrollRect 动态缩放格子大小,自动定位到中间的格子

ScrollRect 探究

知乎 - UGUI源码解析(十)ScrollRect

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

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

相关文章

判断是否在同一个键盘行

给你一个字符串数组 words ,只返回可以使用在 美式键盘 同一行的字母打印出来的单词。键盘如下图所示。 美式键盘 中: 第一行由字符 "qwertyuiop" 组成。第二行由字符 "asdfghjkl" 组成。第三行由字符 "zxcvbnm" 组成。…

构建医护人员排班系统:Spring Boot的实践与探索

相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统,它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等,非常适…

如何使用UltraISO(软碟通)制作U盘启动盘

一、打开UltralSO.exe、先格式化U盘,文件 -> 打开 找到要刻录的ISO镜像文件; 二、选择你的U盘。U盘会在左下角出现,点击便可。 三、启动->写入磁盘映像; 四、写入方式选择 "USB-HDD",点击“写入”按钮…

ZeroTier 内网穿透工具在 Linux 上的安装与配置教程

感谢浪浪云支持发布 浪浪云活动链接 :https://langlangy.cn/?i8afa52 文章目录 1. 安装 ZeroTier1.1 系统需求1.2 更新系统软件包1.3 安装 curl1.4 安装 ZeroTier1.5 启动 ZeroTier 服务1.6 验证安装 2. 创建和管理虚拟网络2.1 注册和登录2.2 创建新网络2.3 配置网…

【嘶吼文化-注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 暴力破解密码,造成用户信息泄露短信盗刷的安全问题,影响业务及导致用户投诉带来经济损失,尤其是后付费客户,风险巨大,造成亏损无底洞…

私有VLAN,从原理到配置,全给你说明白

号主:老杨丨11年资深网络工程师,更多网工提升干货,请关注公众号:网络工程师俱乐部 晚上好,我的网工朋友 传统的VLAN划分虽然能够有效地隔离不同部门或功能区域的网络流量,但在某些情况下,比如共…

【JSP `page` 指令详解:构建高效的动态网页】

JSP page 指令详解&#xff1a;构建高效的动态网页 在 JavaServer Pages (JSP) 中&#xff0c;<% page %> 指令用于配置 JSP 页面的一些关键属性。这些属性控制着页面的行为和生成的 Servlet 的特性&#xff0c;例如字符编码、是否启用会话、缓冲区大小等。合理使用 page…

​了解MySQL 的二进制日志文件​Binlog

1. SQL 语句的几种类型 首先介绍一下&#xff0c;对于一个 SQL 语句&#xff0c;它常常被分为以下几种类型&#xff1a; DDL&#xff08;Data Definition Language&#xff0c;数据定义语言&#xff09;&#xff1a;用来操作数据库、表、列等&#xff0c;比如 CREATE、ALTER…

VSCode 创建Python 项目(最简单,最少步骤,无痛从pycharm迁移项目)

第一步&#xff1a;下载 下载地址&#xff1a;https://code.visualstudio.com/docs/?dvwin64user 第二步&#xff1a;配置 2.1&#xff1a;VsCode设置中文 按住键盘上的“CtrlShiftP”组合键&#xff0c;打开命令面板。 在命令面板中输入“Configure Display Language”。点击…

vue3 二次封装el-select增加分页功能

实现效果 需求来源于实时搜索客户名称,使用el-select相比用弹窗嵌套表格轻便不少。但是当远程搜索获得的数据量大时,可以滚动加载 也可以加上分页。 封装分页组件 <!-- el-pagination 二次封装 --> <template><div><el-paginationv-model:current-page…

【Linux篇】常用命令(笔记)

目录 一、认识Linux 1. Linux的组成 &#xff08;1&#xff09;文件系统&#xff08;FILE SYSTEMS&#xff09; &#xff08;2&#xff09;内核 &#xff08;3&#xff09;用户接口&#xff08;Shell&#xff09; &#xff08;4&#xff09;应用程序 2. Linux的目录结构…

工作分享,小红书企业內推码附送

小红书2025校园招聘全球启动&#xff0c;附有內推码 内推码&#xff1a;QMT16MXVARJL 内推链接&#xff1a;https://job.xiaohongshu.com/link?referer_codeQMT16MXVARJL 内推链接

C++常见异常汇总(三): fatal error: google/protobuf/port_def.inc

文章目录 1、fatal error : sw/redis/redis.h2、fatal error: dwarf.h: No such file or directory3、fatal error: elfutils/libdw.h: No such file or directory4、fatal error: libunwind.h: No such file or directory5、fatal error: google/protobuf/port_def.inc6、erro…

k8s部署springcloud-alibaba项目

本文由个人总结&#xff0c;如需转载使用请标明原著及原文地址 本文需要一些知识储备&#xff0c;有一定的自学能力&#xff0c;有一定的自行解决问题的能力&#xff0c;不然直接看的话压力会比较大&#xff0c;建议有一定知识储备后作为提升来学 本文的前置条件是会docker&a…

Codesys 与 ARMxy ARM 工业控制器:工业控制的黄金组合

在当今高度自动化的工业领域&#xff0c;高效、稳定且灵活的工业控制系统至关重要。ARMxy ARM 工业控制器与 Codesys 的结合&#xff0c;为工业控制系统带来了全新的可能性。 ARMxy ARM 工业控制器以其卓越的性能和丰富的功能&#xff0c;成为工业控制领域的佼佼者。它拥有丰富…

MathType7.4免费永久数学公式编辑器下载

MathType 7.4&#xff1a;数学公式编辑器的全新升级 &#x1f31f; 闪亮登场&#xff0c;MathType 7.4 来了&#xff01;&#x1f31f; 嘿&#xff0c;亲爱的朋友们&#xff01;&#x1f44b;&#x1f44b; 今天&#xff0c;我要给你们带来一个超级好用的小助手——MathType …

大数据新视界 --大数据大厂之Flink强势崛起:大数据新视界的璀璨明珠

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

好用的视频压缩工具有哪些?这4款千万不要错过

视频压缩的方法有很多种&#xff0c;像我们手机里的视频剪辑工具&#xff0c;手机和电脑自带的压缩功能&#xff0c;在线压缩网站&#xff0c;专业压缩软件压缩等等。不同的场景和需求下大家可以选择不同的工具&#xff0c;但是如果碰到需要大量和经常压缩视频的话&#xff0c;…

Ubuntu增强功能

文章目录 共享粘贴板共享文件夹vim 共享粘贴板 双击 输入认证用户密码 显示这个界面 命令行界面 reboot就会重新启动 虚拟机 设置下面的选项&#xff0c;就可以实现共享粘贴 共享文件夹 先在本机&#xff08;windows&#xff09;新建一个共享文件夹 再在ubuntu建一个共…

Python和MATLAB及C++信噪比导图(算法模型)

&#x1f3af;要点 视频图像修复模数转换中混合信号链噪音测量频谱计算和量化周期性视觉刺激脑电图高斯噪声的矩形脉冲 总谐波失真 周期图功率谱密度各种心率失常检测算法胶体悬浮液跟踪检测计算交通监控摄像头图像噪音计算 Python信噪比 信噪比是科学和工程中使用的一种测…