Flutter开发进阶之瞧瞧RenderObject

Flutter开发进阶之瞧瞧RenderObject

通过上回我们了解到Flutter执行buildTree的逻辑线,当Tree构建完成后会交给Flutter底层的渲染事件循环去执行将内容渲染到屏幕的操作。
但是渲染的操作到底是如何串起来的呢?这篇文章将会从Element联系到RenderObject去瞧瞧逻辑线形成闭环。
Flutter开发进阶
在Element的源码中有一个方法,如下。

/*
### Rebuilding
  
  Dirty elements are rebuilt during the next frame. Precisely how this is
  done depends on the kind of element. A [StatelessElement] rebuilds by
  using its widget's [StatelessWidget.build] method. A [StatefulElement]
  rebuilds by using its widget's state's [State.build] method. A
  [RenderObjectElement] rebuilds by updating its [RenderObject].
  
  In many cases, the end result of rebuilding is a single child widget
  or (for [MultiChildRenderObjectElement]s) a list of children widgets.
  
  These child widgets are used to update the [widget] property of the
  element's child (or children) elements. The new [Widget] is considered to
  correspond to an existing [Element] if it has the same [Type] and [Key].
  (In the case of [MultiChildRenderObjectElement]s, some effort is put into
  tracking widgets even when they change order; see
  [RenderObjectElement.updateChildren].)
*/
('vm:prefer-inline')
void rebuild({bool force = false})

我们通过注释可知,Element在构建好Tree后,Dirty elements在下一帧重建,无论是多子Element还是单子Element都是通过RenderObjectElement更新它的RenderObject完成。
让我们来看看RenderObjectElementmount方法,以下。


  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    assert(() {
      _debugDoingBuild = true;
      return true;
    }());
    _renderObject = (widget as RenderObjectWidget).createRenderObject(this);
    assert(!_renderObject!.debugDisposed!);
    assert(() {
      _debugDoingBuild = false;
      return true;
    }());
    assert(() {
      _debugUpdateRenderObjectOwner();
      return true;
    }());
    assert(_slot == newSlot);
    attachRenderObject(newSlot);
    super.performRebuild(); // clears the "dirty" flag
  }

  void attachRenderObject(Object? newSlot) {
    assert(_ancestorRenderObjectElement == null);
    _slot = newSlot;
    _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
    _ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
    final ParentDataElement<ParentData>? parentDataElement = _findAncestorParentDataElement();
    if (parentDataElement != null) {
      _updateParentData(parentDataElement.widget as ParentDataWidget<ParentData>);
    }
  }

RenderObjectElement装载RenderObject的过程中,会创建一个RenderObject与其对应,然后将RenderObject插入对应的卡槽(Slot),并且还需保证子RenderObject遵循上级Widget的相关配置。
根据事件线的逻辑,接下来将看到RenderObject的逻辑,源码代码太长就不一一贴了。

abstract class RenderObject
    with DiagnosticableTreeMixin
    implements HitTestTarget

RenderObject不仅负责Layout、Paint还需要负责hit,这里我们只考虑Layout和Paint
让我们看看markNeedsLayout,以下。

void markNeedsLayout() {
    assert(_debugCanPerformMutations);
    if (_needsLayout) {
      assert(_debugSubtreeRelayoutRootAlreadyMarkedNeedsLayout());
      return;
    }
    if (_relayoutBoundary == null) {
      _needsLayout = true;
      if (parent != null) {
        // _relayoutBoundary is cleaned by an ancestor in RenderObject.layout.
        // Conservatively mark everything dirty until it reaches the closest
        // known relayout boundary.
        markParentNeedsLayout();
      }
      return;
    }
    if (_relayoutBoundary != this) {
      markParentNeedsLayout();
    } else {
      _needsLayout = true;
      if (owner != null) {
        assert(() {
          if (debugPrintMarkNeedsLayoutStacks) {
            debugPrintStack(label: 'markNeedsLayout() called for $this');
          }
          return true;
        }());
        owner!._nodesNeedingLayout.add(this);
        owner!.requestVisualUpdate();
      }
    }
  }

可以看到渲染的逻辑线是推迟到parent的,当合成的Layout需要执行渲染时,会交给owner并添加进需要Layout的集合里,然后通过owner!.requestVisualUpdate();去通知渲染管线进行渲染。
让我们看看源码中怎么说,以下。

/// The owner for this render object (null if unattached).
  ///
  /// The entire render tree that this render object belongs to
  /// will have the same owner.
  PipelineOwner? get owner => _owner;
  PipelineOwner? _owner;

可知在同一个Tree以下都对应同一个PipelineOwner,这个owner负责管理具体的渲染工作,相当于前文说到的ElementBuildOwner的关系。
接下来来我们看看markNeedsPaint方法,以下。

void markNeedsPaint() {
    assert(!_debugDisposed);
    assert(owner == null || !owner!.debugDoingPaint);
    if (_needsPaint) {
      return;
    }
    _needsPaint = true;
    // If this was not previously a repaint boundary it will not have
    // a layer we can paint from.
    if (isRepaintBoundary && _wasRepaintBoundary) {
      assert(() {
        if (debugPrintMarkNeedsPaintStacks) {
          debugPrintStack(label: 'markNeedsPaint() called for $this');
        }
        return true;
      }());
      // If we always have our own layer, then we can just repaint
      // ourselves without involving any other nodes.
      assert(_layerHandle.layer is OffsetLayer);
      if (owner != null) {
        owner!._nodesNeedingPaint.add(this);
        owner!.requestVisualUpdate();
      }
    } else if (parent is RenderObject) {
      parent!.markNeedsPaint();
    } else {
      assert(() {
        if (debugPrintMarkNeedsPaintStacks) {
          debugPrintStack(
              label: 'markNeedsPaint() called for $this (root of render tree)');
        }
        return true;
      }());
      // If we are the root of the render tree and not a repaint boundary
      // then we have to paint ourselves, since nobody else can paint us.
      // We don't add ourselves to _nodesNeedingPaint in this case,
      // because the root is always told to paint regardless.
      //
      // Trees rooted at a RenderView do not go through this
      // code path because RenderViews are repaint boundaries.
      if (owner != null) {
        owner!.requestVisualUpdate();
      }
    }
  }

markNeedsPaintRepaintBoundary会将渲染子Tree限制在自己的范围内,与markNeedsLayout类似,而且也会通过owner去执行渲染管线的管理。
让我们看看PipelineOwner是如何执行的?

/// Calls [onNeedVisualUpdate] if [onNeedVisualUpdate] is not null.
  ///
  /// Used to notify the pipeline owner that an associated render object wishes
  /// to update its visual appearance.
  void requestVisualUpdate() {
    if (onNeedVisualUpdate != null) {
      onNeedVisualUpdate!();
    } else {
      _manifold?.requestVisualUpdate();
    }
  }

在Flutter中,PipelineOwner负责渲染管线的各个方面,是具体与WidgetsBindingRendererBinding帧事件循环的交互。
让我们看看onNeedVisualUpdate的注释,以下。

/// Called when a render object associated with this pipeline owner wishes to
  /// update its visual appearance.
  ///
  /// Typical implementations of this function will schedule a task to flush the
  /// various stages of the pipeline. This function might be called multiple
  /// times in quick succession. Implementations should take care to discard
  /// duplicate calls quickly.
  ///
  /// When the [PipelineOwner] is attached to a [PipelineManifold] and
  /// [onNeedVisualUpdate] is provided, the [onNeedVisualUpdate] callback is
  /// invoked instead of calling [PipelineManifold.requestVisualUpdate].
  final VoidCallback? onNeedVisualUpdate;

可知作为一个调度任务可能被多次重复调用,当PipelineOwner连接到PipelineManifold并提供onNeedVisualUpdate时,会直接执行onNeedVisualUpdate
继续查看PipelineManifold,以下。

abstract class PipelineManifold implements Listenable {
  /// Whether [PipelineOwner]s connected to this [PipelineManifold] should
  /// collect semantics information and produce a semantics tree.
  ///
  /// The [PipelineManifold] notifies its listeners (managed with [addListener]
  /// and [removeListener]) when this property changes its value.
  ///
  /// See also:
  ///
  ///  * [SemanticsBinding.semanticsEnabled], which [PipelineManifold]
  ///    implementations typically use to back this property.
  bool get semanticsEnabled;

  /// Called by a [PipelineOwner] connected to this [PipelineManifold] when a
  /// [RenderObject] associated with that pipeline owner wishes to update its
  /// visual appearance.
  ///
  /// Typical implementations of this function will schedule a task to flush the
  /// various stages of the pipeline. This function might be called multiple
  /// times in quick succession. Implementations should take care to discard
  /// duplicate calls quickly.
  ///
  /// A [PipelineOwner] connected to this [PipelineManifold] will call
  /// [PipelineOwner.onNeedVisualUpdate] instead of this method if it has been
  /// configured with a non-null [PipelineOwner.onNeedVisualUpdate] callback.
  ///
  /// See also:
  ///
  ///  * [SchedulerBinding.ensureVisualUpdate], which [PipelineManifold]
  ///    implementations typically call to implement this method.
  void requestVisualUpdate();
}

在Flutter中,PipelineManifold通常不会暴露在外,它管理PipelineOwner Tree下所有PipelineOwner,它实现了PipelineOwner访问共享,PipelineManifold通过调用SchedulerBinding.ensureVisualUpdate实现通知渲染执行。
让我们继续进行下一步的探索,以下。

/// Schedules a new frame using [scheduleFrame] if this object is not
  /// currently producing a frame.
  ///
  /// Calling this method ensures that [handleDrawFrame] will eventually be
  /// called, unless it's already in progress.
  ///
  /// This has no effect if [schedulerPhase] is
  /// [SchedulerPhase.transientCallbacks] or [SchedulerPhase.midFrameMicrotasks]
  /// (because a frame is already being prepared in that case), or
  /// [SchedulerPhase.persistentCallbacks] (because a frame is actively being
  /// rendered in that case). It will schedule a frame if the [schedulerPhase]
  /// is [SchedulerPhase.idle] (in between frames) or
  /// [SchedulerPhase.postFrameCallbacks] (after a frame).
  void ensureVisualUpdate() {
    switch (schedulerPhase) {
      case SchedulerPhase.idle:
      case SchedulerPhase.postFrameCallbacks:
        scheduleFrame();
        return;
      case SchedulerPhase.transientCallbacks:
      case SchedulerPhase.midFrameMicrotasks:
      case SchedulerPhase.persistentCallbacks:
        return;
    }
  }

继续沿SchedulerBinding的执行路径,以下。

void scheduleFrame() {
    if (_hasScheduledFrame || !framesEnabled) {
      return;
    }
    assert(() {
      if (debugPrintScheduleFrameStacks) {
        debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
      }
      return true;
    }());
    ensureFrameCallbacksRegistered();
    platformDispatcher.scheduleFrame();
    _hasScheduledFrame = true;
  }

最终void scheduleFrame()是具体下一帧绘制的方法,由此完成闭环。
综上所述,我们了解到,Flutter具体执行渲染时会构建RenderObject Tree(通过将RenderObject插入Slot),具体操作交给PipelineOwner管理,而同一Tree下的PipelineOwner对接唯一的PipelineManifold,最后通知SchedulerBinding去执行帧绘制的操作。

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

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

相关文章

点餐小程序php毕设项目

主要技术框架&#xff1a; 主要功能模块&#xff1a; 商品管理 订单管理 用户管理 优惠券管理 商品分类管理 评论管理 轮播图管理 截图 获取源码 https://blog.lusz.top/article?article_id-2

【Linux系统编程(进程编程)】创建进程的场景,fork和vfork的使用及区别

文章目录 一、进程关键概念二、创建进程函数fork的使用一、进程创建实战 三、创建进程函数fork的使用补充四、进程创建发生了什么事&#xff1f;五、创建新进程的实际应用场景 & fork总结一、fork创建一个子进程的一般目的&#xff1f;二、fork编程实战 六、vfork也能创建进…

grid布局

文章目录 1. 概念2. 组成2.1. 网格行2.2. 网格列2.3. 网格间距2.4. 网格线2.5. 网格容器2.6. fr 单位 3. 网格跨行跨列3.1. 跨列3.2. 跨行 4. 网格布局案例4.1. 演示效果4.2. 分析思路4.3. 代码实现 1. 概念 网格是一组相交的水平线和垂直线&#xff0c;它定义了网格的列和行。…

【排序算法】实现快速排序值(霍尔法三指针法挖坑法优化随即选key中位数法小区间法非递归版本)

文章目录 &#x1f4dd;快速排序&#x1f320;霍尔法&#x1f309;三指针法&#x1f320;挖坑法✏️优化快速排序 &#x1f320;随机选key&#x1f309;三位数取中 &#x1f320;小区间选择走插入&#xff0c;可以减少90%左右的递归&#x1f309; 快速排序改非递归版本&#x1…

工业相机采图方式、图像格式(BYTE、HObject和Mat)转换

1、概述 机器视觉项目中&#xff0c;如何采集到合适的图像是项目的第一步&#xff0c;也是最重要的一步&#xff0c;直接关系到后面图像处理算法及最终执行的结果。所以采用不同的工业相机成像以及如何转换成图像处理库所需要的格式成为项目开发中首先要考虑的问题。 2、工业…

分布式组件 Nacos

1.在之前的文章写过的就不用重复写。 写一些没有写过的新东西 2.细节 2.1命名空间 &#xff1a; 配置隔离 默认&#xff1a; public &#xff08;默认命名空间&#xff09;:默认新增所有的配置都在public空间下 2.1.1 开发 、测试 、生产&#xff1a;有不同的配置文件 比如…

【ZYNQ】基于ZYNQ 7020的OPENCV源码交叉编译

目录 安装准备 检查编译器 安装OpenCV编译的依赖项 下载OpenCV源码 下载CMake 编译配置 编译器说明 参考链接 安装准备 使用的各个程序的版本内容如下&#xff1a; 类别 软件名称 软件版本 虚拟机 VMware VMware-workstation-full-15.5.0-14665864 操作系统 Ub…

【QT入门】 Qt实现自定义信号

往期回顾&#xff1a; 【QT入门】图片查看软件(优化)-CSDN博客 【QT入门】 lambda表达式(函数)详解-CSDN博客 【QT入门】 Qt槽函数五种常用写法介绍-CSDN博客 【QT入门】 Qt实现自定义信号 一、为什么需要自定义信号 比如说现在一个小需求&#xff0c;我们想要实现跨ui通信&a…

Hive入门

什么是hive&#xff1f; - Hive是Facebook开发并贡献给Hadoop开源社区的。它是建立在 Hadoop体系架构上的一层 SQL抽象&#xff0c;使得数据相关人 员使用他们最为熟悉的SQL语言就可以进行海量数据的处理、 分析和统计工作 - Hive将数据存储于HDFS的数据文件映射为一张数据库…

Java程序设计 4、5章 练习题

一、填空题 1.假设有 String s1 "Welcome to Java"; String s2 s1; String s3 new String("Welcome to Java"); 那么下面表达式的结果是什么&#xff1f; (1) s1 s2 ___________true_______________ (2) s1 s3 ______…

SOPHON算能服务器SDK环境配置和相关库安装

目录 1 SDK大包下载 2 安装libsophon 2.1 安装依赖 1.2 安装libsophon 2 安装 sophon-mw 参考文献&#xff1a; 1 SDK大包下载 首先需要根据之前的博客&#xff0c;下载SDK大包&#xff1a;SOPHON算能科技新版SDK环境配置以及C demo使用过程_sophon sdk yolo-CSDN博客 …

第 6 章 ROS-xacro练习(自学二刷笔记)

重要参考&#xff1a; 课程链接:https://www.bilibili.com/video/BV1Ci4y1L7ZZ 讲义链接:Introduction Autolabor-ROS机器人入门课程《ROS理论与实践》零基础教程 6.4.3 Xacro_完整使用流程示例 需求描述: 使用 Xacro 优化 URDF 版的小车底盘模型实现 结果演示: 1.编写 X…

idea使用token方式登录GitHub

总体上分为两大步&#xff1a;1.GitHub生成token。2.idea配置token登录GitHub。 注&#xff1a;idea配置GitHub的前提是本地已经安装了git程序。 一、GitHub生成token 1.登录GitHub 2.进入token创建页面&#xff08;右上角点击头像–>settings–>页面向下滚动左侧菜单栏…

linux热键,man手册介绍

目录 热键 tab ctrl c ctrl r man 区段 快捷键 热键 tab 可以看到以输入的内容为开头的指令,但无法选择: 当输入的内容匹配到的内容只有一个时,可以自动补全 可以用于输入路径时,自动补全文件名 ctrl c 让当前的程序停掉,可以在 程序或指令出问题而自己无法停止时 使用…

HSP_01章_Python 语言概述

文章目录 06 开发环境安装10 注意事项11 学习方法14 Pycharm 常用快捷键14 Python 常用转义字符15 Python 注释Comment16 [Python 中文文档地址](https://docs.python.org/zh-cn/3.11/) 06 开发环境安装 python 版本命令: python cmd 退出: exit() 环境变量配置: 计算机 > 高…

【Linux】从零认识进程 — 中下篇

送给大家一句话&#xff1a; 人一切的痛苦&#xff0c;本质上都是对自己无能的愤怒。而自律&#xff0c;恰恰是解决人生痛苦的根本途径。—— 王小波 从零认识进程 1 进程优先级1.1 什么是优先级1.2 为什么要有优先级1.3 Linux优先级的特点 && 查看方式1.4 其他概念 2…

如何鉴别真假ZLibrary?2024 ZLibrary最新可用地址,持续更新,2024年在 zlibrary 上发现几本有意思的电子书

之前分享过全网电子书都在这了&#xff1a;ZLibrary 官方通道来了&#xff0c;不再担心找不到最新地址&#xff0c;配合这个脚本简直完美&#xff0c;最新ZLibrary可用地址 zlibrary-sg.se 如何确认一个网站是真的ZLibrary &#xff1f;存在一个API 接口/eapi/info &#xff0…

pytest全局配置+前后只固件配置

pytest全局配置前后只固件配置 通过读取pytest.ini配置文件运行通过读取pytest.ini配置文件运行无条件跳过pytest.initest_mashang.pyrun.py 有条件跳过test_mashang.py pytest框架实现的一些前后置&#xff08;固件、夹具&#xff09;处理方法一&#xff08;封装&#xff09;方…