Unity笔记:ScrollRect代码阅读

大体流程

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.mfbz.cn/a/875254.html

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

相关文章

Visual Studio配置opencv环境

(1)打开属性页面(鼠标放在解决方案上,点击右键会有一个属性选项弹出) (2)配置opencv的include和opencv2路径,具体路径和版本根据自己电脑配置 (3)配置opencv…

OpenAI o1预览模型发布:推理能力更强 可达理科博士生水准

今日凌晨,OpenAI正式推出了OpenAI o1预览模型。 对于复杂推理任务而言,新模型代表着人工智能能力的崭新水平,其特点就是会在回答之前花更多时间进行思考,就像人类思考解决问题的过程一样。 OpenAI曾解释过,2023年发布…

卡车配置一键启动无钥匙进入手机控车

‌ 卡车智能一键启动无钥匙进入手机控车,通过手机应用程序与汽车内置硬件、软件的无线通信,实现对汽车的远程控制‌。 卡车改装一键启动的步骤包括安装门把手的感应装置、拆卸仪表台和门板,取出内部的待接线束,并将一键启动…

ip地址a段b段c段是什么意思

在互联网的世界里,每一个设备都需要一个独特的标识符来相互识别和通信,这就是IP地址。IP地址不仅仅是一串数字,它背后隐藏着网络的组织结构和设备的连接方式。本文将深入探讨IP地址中的A段、B段、C段的含义,以及它们在网络通信中的…

VSCode创建项目和编译多文件

前言 在刚安装好VSCode后,我简单尝试了仅main.cpp单文件编译代码,没有问题,但是当我尝试多文件编译时,就出现了无法识别cpp文件。 内容 创建项目 首先点击左上角“文件”;在菜单中选择“打开文件夹”;在…

建材家居家具电器整站网站打包下载预览图及地址二

木质装饰材料网站模板_建材家居家具电器类下载有预览图在博客首页.zip 响应式高端品牌建材陶瓷瓷砖网站模板_建材家居家具电器类下载有预览图在博客首页.zip 响应式创意家居网站模板_建材家居家具电器类下载有预览图在博客首页.zip 木纹地板墙砖类网站模板_建材家居家具电器…

极狐GitLab CI/CD 作业一直处于等待状态,如何解决?

本分分享 GitLab CI/CD Job 不工作的的故障排查方法:当 GitLab Runner 不接受 Job,Job 一直处于等待状态,如何解决此问题。 极狐GitLab 为 GitLab 在中国的发行版,中文版本对中国用户更友好。极狐GitLab 支持一键私有化部署&…

加密与安全_ sm-crypto 国密算法sm2、sm3和sm4的Java库

文章目录 Presm-crypto如何使用如何引入依赖 sm2获取密钥对加密解密签名验签获取椭圆曲线点 sm3sm4加密解密 Pre 加密与安全_三种方式实现基于国密非对称加密算法的加解密和签名验签 sm-crypto https://github.com/antherd/sm-crypto 国密算法sm2、sm3和sm4的java版。基于js…

linux入门到实操-4 linux系统网络配置、连接测试、网络连接模式、修改静态IP、配置主机名

教程来源:B站视频BV1WY4y1H7d3 3天搞定Linux,1天搞定Shell,清华学神带你通关_哔哩哔哩_bilibili 整理汇总的课程内容笔记和课程资料(包含课程同版本linux系统文件等内容),供大家学习交流下载:…

QML学习三:qml设计器报错 Line: 0: The Design Mode requires a valid Qt kit

开发环境:Qt 6.5.3 LTS 1、Qt 6.5.3 LTS 2、Pyside6 3、Python 3.11.4 4、win11 默认不打开设计器的时候可以看到我们默认是有Python的环境,而且点击运行是可以运行的。但是当打开qml设计器时提示下面这个错误,提示需要一个可用的套件。 …

通信工程学习:什么是ASON自动交换光网络

ASON:自动交换光网络 ASON(Automatically Switched Optical Network),即自动交换光网络,是一种在选路和信令控制下完成自动交换功能的新一代光网络。它代表了未来智能光网络发展的主流方向,是下一代智能光传…

论文笔记:基于LLM和多轮学习的漫画零样本角色识别与说话人预测

整理了ACM MM2024 Zero-Shot Character Identification and Speaker Prediction in Comics via Iterative Multimodal Fusion)论文的阅读笔记 背景模型框架实现细节 模型数据集实验可视化消融实验 背景 最近读到一篇新文章,主要是做漫画中的零样本角色识…

Java并发:互斥锁,读写锁,Condition,StampedLock

3,Lock与Condition 3.1,互斥锁 3.1.1,可重入锁 锁的可重入性(Reentrant Locking)是指在同一个线程中,已经获取锁的线程可以再次获取该锁而不会导致死锁。这种特性允许线程在持有锁的情况下,可…

如何在 Selenium 中获取网络调用请求?

引言 捕获网络请求对于理解网站的工作方式以及传输的数据至关重要。Selenium 作为一种 Web 自动化工具,可以用于捕获网络请求。本文将讨论如何使用 Selenium 在 Java 中捕获网络请求并从网站检索数据。 我们可以使用浏览器开发者工具轻松捕获网络请求或日志。大多数现代 Web…

【iOS】UIViewController的生命周期

UIViewController的生命周期 文章目录 UIViewController的生命周期前言UIViewController的一个结构UIViewController的函数的执行顺序运行代码viewWillAppear && viewDidAppear多个视图控制器跳转时的生命周期pushpresent 小结 前言 之前对于有关于UIViewControlller的…

MySQL:bin log

redo log 它是物理日志,记录内容是“在某个数据页上做了什么修改”,属于 InnoDB 存储引擎。 而 binlog 是逻辑日志,记录内容是语句的原始逻辑,类似于“给 ID2 这一行的 c 字段加 1”,属于MySQL Server 层。 不管用什…

学习平台|基于java的移动学习平台系统小程序(源码+数据库+文档)

学习平台|学习平台系统|在线学习平台系统小程序 目录 基于java的移动学习平台系统小程序 一、前言 二、系统设计 三、系统功能设计 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取: 博主介绍:✌️大厂码…

C++奇迹之旅:快速上手Priority_queue的使用与模拟实现

文章目录 📝priority_queue的介绍和使用🌠 priority_queue的介绍🌉priority_queue的使用 🌠仿函数的使用🌠C语言有趣的模仿push_back🌠priority_queue的模拟实现🚩总结 📝priority_q…

java重点学习-集合(List)

七 集合(List) 7.1 复杂度分析 7.2 数组 1.数组(Array)是一种用连续的内存空间存储相同数据类型 数据的线性数据结构。 2.数组下标为什么从0开始 寻址公式是:baseAddressi*dataTypeSize,计算下标的内存地址效率较高 3.查找的时间复杂度 随机(…

HarmonyOS Next系列之实现一个左右露出中间大两边小带缩放动画的轮播图(十二)

系列文章目录 HarmonyOS Next 系列之省市区弹窗选择器实现(一) HarmonyOS Next 系列之验证码输入组件实现(二) HarmonyOS Next 系列之底部标签栏TabBar实现(三) HarmonyOS Next 系列之HTTP请求封装和Token…