flutter布局更新

理论上,某个组件的布局变化后,就可能会影响其他组件的布局,所以当有组件布局发生变化后,最笨的办法是对整棵组件树 relayout(重新布局)!但是对所有组件进行 relayout 的成本还是太大,所以我们需要探索一下降低 relayout 成本的方案。实际上,在一些特定场景下,组件发生变化后我们只需要对部分组件进行重新布局即可(而无需对整棵树 relayout )

布局边界(relayoutBoundary)

假如有一个页面的组件树结构如图所示。

假如 Text3 的文本长度发生变化,则会导致 Text4 的位置和 Column2 的大小也会变化;又因为 Column2 的父组件 SizedBox 已经限定了大小,所以 SizedBox 的大小和位置都不会变化。所以最终我们需要进行 relayout 的组件是:Text3、Column2,这里需要注意:

  1. Text4 是不需要重新布局的,因为 Text4 的大小没有发生变化,只是位置发生变化,而它的位置是在父组件 Column2 布局时确定的。
  2. 很容易发现:假如 Text3 和 Column2 之间还有其他组件,则这些组件也都是需要 relayout 的。

在本例中,Column2 就是 Text3 的 relayoutBoundary (重新布局的边界节点)。每个组件的 renderObject 中都有一个 _relayoutBoundary 属性指向自身的布局边界节点,如果当前节点布局发生变化后,自身到其布局边界节点路径上的所有的节点都需要 relayout。

那么,一个组件是否是 relayoutBoundary 的条件是什么呢?这里有一个原则和四个场景,原则是“组件自身的大小变化不会影响父组件”,如果一个组件满足以下四种情况之一,则它便是 relayoutBoundary :

  1. 当前组件父组件的大小不依赖当前组件大小时;这种情况下父组件在布局时会调用子组件布局函数时并会给子组件传递一个 parentUsesSize 参数,该参数为 false 时表示父组件的布局算法不会依赖子组件的大小。

  2. 组件的大小只取决于父组件传递的约束,而不会依赖后代组件的大小。这样的话后代组件的大小变化就不会影响自身的大小了,这种情况组件的 sizedByParent 属性必须为 true(具体我们后面会讲)。

  3. 父组件传递给自身的约束是一个严格约束(固定宽高,下面会讲);这种情况下即使自身的大小依赖后代元素,但也不会影响父组件。

  4. 组件为根组件;Flutter 应用的根组件是 RenderView,它的默认大小是当前设备屏幕大小。

对应的代码实现是 

// parent is! RenderObject 为 true 时则表示当前组件是根组件,因为只有根组件没有父组件。
if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
  _relayoutBoundary = this;
} else {
  _relayoutBoundary = (parent! as RenderObject)._relayoutBoundary;
}

markNeedsLayout

当组件布局发生变化时,它需要调用 markNeedsLayout 方法来更新布局,它的功能主要有两个:

  1. 将自身到其 relayoutBoundary 路径上的所有节点标记为 “需要布局” 。
  2. 请求新的 frame;在新的 frame 中会对标记为“需要布局”的节点重新布局。

核心源码

void markNeedsLayout() {
   _needsLayout = true;
  if (_relayoutBoundary != this) { // 如果不是布局边界节点
    markParentNeedsLayout(); // 递归调用前节点到其布局边界节点路径上所有节点的方法 markNeedsLayout
  } else {// 如果是布局边界节点 
    if (owner != null) {
      // 将布局边界节点加入到 pipelineOwner._nodesNeedingLayout 列表中
      owner!._nodesNeedingLayout.add(this); 
      owner!.requestVisualUpdate();//该函数最终会请求新的 frame
    }
  }
}

 flushLayout()

markNeedsLayout 执行完毕后,就会将其 relayoutBoundary 节点添加到 pipelineOwner._nodesNeedingLayout 列表中,然后请求新的 frame,新的 frame 到来时就会执行 drawFrame 方法

Layout流程

如果组件有子组件,则在 performLayout 中需要调用子组件的 layout 方法先对子组件进行布局,我们看一下 layout 的核心流程:

void layout(Constraints constraints, { bool parentUsesSize = false }) {
  RenderObject? relayoutBoundary;
  // 先确定当前组件的布局边界
  if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
    relayoutBoundary = this;
  } else {
    relayoutBoundary = (parent! as RenderObject)._relayoutBoundary;
  }
  // _needsLayout 表示当前组件是否被标记为需要布局
  // _constraints 是上次布局时父组件传递给当前组件的约束
  // _relayoutBoundary 为上次布局时当前组件的布局边界
  // 所以,当当前组件没有被标记为需要重新布局,且父组件传递的约束没有发生变化,
  // 且布局边界也没有发生变化时则不需要重新布局,直接返回即可。
  if (!_needsLayout && constraints == _constraints && relayoutBoundary == _relayoutBoundary) {
    return;
  }
  // 如果需要布局,缓存约束和布局边界
  _constraints = constraints;
  _relayoutBoundary = relayoutBoundary;

  // 后面解释
  if (sizedByParent) {
    performResize();
  }
  // 执行布局
  performLayout();
  // 布局结束后将 _needsLayout 置为 false
  _needsLayout = false;
  // 将当前组件标记为需要重绘(因为布局发生变化后,需要重新绘制)
  markNeedsPaint();
}

简单来讲布局过程分以下几步:

  1. 确定当前组件的布局边界。

  2. 判断是否需要重新布局,如果没必要会直接返回,反之才需要重新布局。不需要布局时需要同时满足三个条件:

    • 当前组件没有被标记为需要重新布局。

    • 父组件传递的约束没有发生变化。

    • 当前组件的布局边界也没有发生变化时。

  3. 调用 performLayout() 进行布局,因为 performLayout() 中又会调用子组件的 layout 方法,所以这时一个递归的过程,递归结束后整个组件树的布局也就完成了。

  4. 请求重绘。

总结

在进行布局的时候,Flutter 会以 DFS(深度优先遍历)方式遍历渲染树,并 将限制以自上而下的方式 从父节点传递给子节点。子节点若要确定自己的大小,则 必须 遵循父节点传递的限制。子节点的响应方式是在父节点建立的约束内 将大小以自下而上的方式 传递给父节点。

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

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

相关文章

python初体验

Python初学者之旅:从零开始的编程世界探索 开篇词 欢迎来到Python编程的世界!作为一名初学者,你也许对这个简洁明了、功能强大的编程语言充满了好奇与期待。Python以其易于理解的语法、丰富的标准库及活跃的社区深受全球开发者喜爱&#xff…

Linux下线程池详解与实现:提升多任务处理效率的关键

🎬慕斯主页:修仙—别有洞天 ♈️今日夜电波:マイノリティ脈絡—ずっと真夜中でいいのに。 0:24━━━━━━️💟──────── 4:02 🔄 ◀…

隐蔽处工程监管系统

随着科技的飞速发展,信息化、智能化已经成为各行各业发展的必然趋势。在工程建设领域,传统的监管方式已经难以满足现代工程管理的需求。为了提高工程监管的效率和精度,信鸥科技倾力打造了一款全新的工程监管系统,为工程建设行业带…

Weaviate

文章目录 关于 Weaviate核心功能部署方式使用场景 快速上手 (Python)1、创建 Weaviate 数据库2、安装3、连接到 Weaviate4、定义数据集5、添加对象6、查询1)Semantic search2) Semantic search with a filter 使用示例Similarity searchLLMs and searchC…

金蝶BI方案治好我的数据分析困难症

结构分析、趋势分析、分布分析、对比分析……这还是大方向的,细分下来还会根据数据类型和具体场景不同而不同,不仅如此,每个月的数据分析需求还可能不同,导致分析量多且复杂,加班加点也忙不过来。但金蝶BI方案就不一样…

构造函数与析构函数的显示调用

目录 前言: 构造函数的显示调用 显示调用无参构造 隐式调用无参构造 显示调用有参构造 构造函数的执行顺序 析构函数的显示调用 析构函数的调用顺序 显示调用析构函数 前言: 构造函数是类的特殊成员函数,创建对象时编译器会自动调用…

win10开启了hyper-v,docker 启动还是报错 docker desktop windows hypervisor is not present

问题 在安装了docker windows版本后启动 docker报错docker desktop windows hypervisor is not present 解决措施 首先确认windows功能是否打开Hyper-v 勾选后重启,再次启动 启动后仍报这个错误,是Hyper-v没有设置成功 使用cmd禁用再启用 一.禁用h…

oracle docker安装

修改下载的Image的REPOSITORY和TAG属性 修改下载的Image的REPOSITORY和TAG属性&#xff1a;docker tag <IMAGE ID> <REPOSITORY NAME> docker tag 3fa112fd3642 aliyun/oracle_11g 参考网址 使用docker images时&#xff0c;可能会出现REPOSITORY和TAG均为none的镜…

【JVM】JVM 运行时数据区简介

文章目录 &#x1f334;简介&#x1f332;堆&#xff08;线程共享&#xff09;&#x1f384;本地方法栈&#xff08;线程私有&#xff09;&#x1f333;程序计数器&#xff08;线程私有&#xff09;&#x1f340;方法区&#xff08;线程共享&#xff09;&#x1f338;JDK 1.8 元…

文件的读取与操作

文件类型&#xff1a; 从文件功能的角度来分类&#xff1a; 1.程序⽂件 程序⽂件包括源程序⽂件&#xff08;后缀为.c&#xff09;,⽬标⽂件&#xff08;windows环境后缀为.obj&#xff09;,可执⾏程序&#xff08;windows 环境后缀为.exe&#xff09;。 2. 数据⽂件 ⽂件…

Office办公软件之word的使用(一)

前几天调整公司招标文件的格式&#xff0c;中途遇到一些问题&#xff0c;感觉自己还不是太熟悉操作&#xff0c;通过查阅资料&#xff0c;知道了正确的操作&#xff0c;就想着给记下来。如果再次遇到&#xff0c;也能很快地找到解决办法。 一、怎么把标题前的黑点去掉 解决办法…

latex $$斜体间距太大 解决方案

不要直接$NPSB$&#xff0c; 而是使用$\textit{NPSB}$

Node Sass does not yet support your current environment

项目运行时报错&#xff1a;Node Sass does not yet support your current environment 原因是node版本过高。 解决办法&#xff1a; 使用nvm管理node版本&#xff0c;&#xff08;如何安装nvm&#xff1f;请点击跳转&#xff09; 具体步骤如下&#xff1a; 1.查看当前node…

工业新力军!你不知道的工业电脑触摸一体机

作为普通用户&#xff0c;接触最多的电脑肯定是商用台式电脑、笔记本电脑以及平板电脑等&#xff0c;这类电脑产品面向的均是个人需求。那工业级触摸一体机电脑又是什么&#xff1f;它究竟有何特点能够在工业行业中大放异彩呢&#xff1f; 工业电脑的好处是&#xff1a;1、壳子…

电源设计中的去耦电容深入理解及应用实例,非常实用!

很多新手设计电路&#xff0c;通常会觉得电源的设计很简单&#xff0c;不就是线性电源和开关电源吗&#xff1f;找个参考设计抄一下就行了。。。。。 因此&#xff0c;电源往往是我们在电路设计过程中最容易忽略的环节。相反&#xff0c;电源虽然是设计中非常基础的部分&#x…

Python爬虫如何快速入门

写了几篇网络爬虫的博文后&#xff0c;有网友留言问Python爬虫如何入门&#xff1f;今天就来了解一下什么是爬虫&#xff0c;如何快速的上手Python爬虫。 一、什么是网络爬虫 网络爬虫&#xff0c;英文名称为Web Crawler或Spider&#xff0c;是一种通过程序在互联网上自动获取…

接口测试详解

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 1、什么是接口测试 顾名思义&#xff0c;接口测试是对系统或组件之间的接口进行测试&#xff0…

工业级POE交换机的SSH配置步骤

工业级POE交换机的SSH&#xff08;Secure Shell&#xff09;配置可以通过以下步骤进行&#xff1a; 1. 连接到POE交换机&#xff1a;使用一个支持SSH协议的终端工具&#xff08;如PuTTY&#xff09;连接到POE交换机的管理接口。 2. 登录到POE交换机&#xff1a;输入正确的用户…

c++核心学习--继承2

4.6.7多继承语法 4.6.8菱形继承 利用虚继承解决菱形继承的问题&#xff1a;继承之前加上关键字virtual变为虚继承

C++|类封装、类的分文件编写练习:设计立方体类、点和圆的关系

文章目录 练习案例1&#xff1a;设计立方体类CPP代码 练习案例2:点和圆的关系CPP代码 代码总结类的分文件编写 练习案例1&#xff1a;设计立方体类 设计立方体类(Cube) 求出立方体的面积和体积 分别用全局函数和成员函数判断两个立方体是否相等。 CPP代码 class Cube { pub…