前言:Flutter官方文档里的一句话:you build your UI out of widgets(使用Flutter开发UI界面时,都是使用Widget),然而,Widget并不是我们真正看到的视图,背后究竟是什么?其实Flutter Framework提供了三种视图树,即:Widget  Element  RenderObject,只不过,我们使用Flutter开发界面时,通常只和widget打交道,就如前文中所展示的Materail风格或者Cupertino(IOS风格)的各种Widget,然而Flutter界面开发是一种响应式编程,并且Widget都是immutable的,那么,真正的渲染,刷新,布局这些问题是谁来处理呢?本文就来了解一下除了Widget,还有哪些基础类在背后支撑Widget的快速轻量渲染;

  Widget部分:

看下官网对Widget的定义和描述:Describes the configuration for an Element. (为Element提供配置信息),从这一句话,隐隐感觉到Widget和Element是紧密关联着的,并且给了Element某些信息,难道是老板和员工的关系?老板给员工发个指令,具体的事情都让员工去做了?我们再尝试着从官方文档中提取一些详细信息,先来看看到底什么是Widget?
我们先来看下Widget这个类中都包含哪些属性:
 
 1:KEY key
 2:int hasCode
 3:TYPE runtimeType
 
Widget是用户界面的一部分,并且是不可变的(immutable)。Widget会被inflate到Element,并由Element管理底层渲染树。Widget本身没有可变状态(所有的字段必须是final)。如果想要把可变状态与Widget关联起来,可以使用StatefulWidget,StatefulWidget通过使用StatefulWidget.createState方法创建State对象,并将之扩充到Element以及合并到树中;
所以,Widget并不会直接管理状态及渲染,而是通过State这个对象来管理状态,下篇文章会主要讲到State这个对象;
给定的Widget可以被包含在树中(零次或多次)。一个给定的Widget可以放置在树中多次,比如:多个TextWidget。每次将一个Widget放入树中时,它都会被扩充到一个Element中,这也意味着多次并入树中的Widget将会被多次扩充进对应的element。
  比如在实际界面开发当中,一个UI视图树可能包含有多个TextWidget(Widget可能被使用多次),但是这些都叫作TextWidget的widget却被填充(inflate)进一个个独立的Element中;
Widget中的Key这个属性控制一个Widget如何替换树中的另一个Widget。如果两个Widget的runtimeType和key属性相等(==),则新的widget通过更新Element(即通过使用新的Widget调用Element.update)来替换旧的Widget。否则,如果两个Widget的runtimeType和key属性不相等,则旧的Element将从树中被移除,新的Widget将被扩充到一个新的Element中,这个新的Element将被插入树中。

这里主要涉及到Widget的更新和移除,插入等操作,在执行此操作前,会用Widget的两个属性:runtimeType和key来进行对比判断;

  Element部分

Flutter创建Element的可见树,相对于Widget来说,是可变的,通常的Flutter界面开发中,我们不用直接操作Element,而是由框架层实现内部逻辑;就如一个UI视图树中,可能包含有多个TextWidget(Widget被使用多次),但是放在内部视图树的视角,这些TextWidget都是填充到一个个独立的Element中;

同样,我们先来看一下Element这个类中的属性:

Element property表格
property Type Desc implement
depth int 树根Element的深度必须大于0
int get depth => _depth;
dirty bool 如果Element已经被标注成需要重建,返回true
bool get dirty => _dirty;
hashCode int  
@overrideint get hashCode => _cachedHash;
owner BuildOwner 管理Element生命周期
@overrideBuildOwner get owner => _owner;
renderObject RenderObject 如果此对象是RenderObjectElement,则渲染对象是树中此位置处的对象。否则,这个getter将沿着树走下去,直到找到一个RenderObjectElement。  
size Size  省略  省略
slot    省略  省略
widget Widget 这个Element的配置信息
@overrideWidget get widget => _widget;
runtimeType      
可以从Element的属性中看到renderObject和widget,说明Element持有这两个类的实例;
再来看看文档中对于Element的介绍:An instantiation of a Widget at a particular location in the tree.  Element是在树中特定位置Widget的实例;
这里进一步描述了Widget与Element之间的关系:Element是Widget的实例,也就是说,Element才是真正干活的员工,执行老板战略部署;我们接着看文档中的描述:
Widget描述如何配置子树,但由于Widget是不可变的(immutable),因此可以使用相同的Widget同时配置多个子树。Element表示Widget配置树中的特定位置的实例。随着时间的推移,与给定Element关联的Widget可能随时会发生变化,例如,如果父Widget重建并为此位置创建新的Widget。Element构成一棵树。大多数Element都有一个唯一的子Element,但是一些Widget(例如RenderObjectElement的子类)可以有多个子Element。
 
 
Element具有以下生命周期:
  • 框架层通过调用即将被用来作为Element的初始化配置信息的Widget的Widget.createElement方法来创建Element;
  • 框架层通过调用mount方法来将新创建的Element添加到给定父级中给定槽点的树上。 mount方法负责将任何Widget扩充到Widget并根据需要调用attachRenderObject,以将任何关联的渲染对象附加到渲染树上。
  • 此时,Element被视为“激活的”,并可能出现在屏幕上。
     
  • 在某些情况下,父(Element)可能会更改用于配置此Element的Widget,例如因为父Element重新创建了新状态。发生这种情况时,框架层将调用新的Widget的update方法。新Widget将始终具有与旧Widget相同的runtimeType和key属性。如果父Element希望在树中的此位置更改Widget的runtimeType或key,可以通过unmounting(卸载)此Element并在此位置扩充新Widget来实现。
  • 在某些时候,祖先Element可能会决定从树中移除该Element(或中间祖先Element),祖先Element自己通过调用deactivateChild来完成该操作。停用中间祖先将从渲染树中移除该Element的渲染对象,并将此Element添加到owner属性中的非活动元素列表中,从而让框架层调用deactivate方法作用在此Element上。
  • 此时,该Element被视为“无效状态”,并且不会出现在屏幕上。一个Element可以保持”非活动"状态,直到当前动画帧结束。在动画帧结束时,任何仍处于非活动状态的Element都将被卸载。
  • 如果Element被重新组合到树中(例如,因为它或其祖先之一有一个全局键(global key)被重用),框架层将从owner属性中的非活动Element列表中移除该Element,并调用该Element的activate方法,并重新附加Element的渲染对象到渲染树。 (此时,Element再次被视为“活动状态”并可能出现在屏幕上。)
  • 如果Element在当前动画帧的末尾没有被重新组合到树中,则框架层将调用该元素的unmount方法。
  • 此时,该元素被视为“已停用”,并且将来不会并入树中。

由此我们可知:Element存放Widget上下文,通过遍历视图树,Element 同时持有 Widget 和 RenderObject;

  RenderObject部分

官网定义:An object in the render tree.渲染树中的一个对象。从其名字,我们可以很直观地知道,它就是负责渲染的工作;
RenderObject的属性太多,且该类的细节涉及很多渲染知识,我们会在后面的系列中再详细说明其工作原理,在此先继续看下官网对其描述:RenderObject类的层次结构是渲染库的核心。
RenderObjects有一个父级,并有一个名为parentData的插槽,其中父级RenderObject可以存储特定于子级的数据,例如子级位置。 RenderObject类也实现了基本的布局和绘制协议。
但是,RenderObject类没有定义子模型(例如,节点是否有零个,一个或多个子节点)。它也没有定义坐标系(例如,子级是否位于笛卡尔坐标系,极坐标系等)或特定的布局协议(例如布局是宽度高度还是尺寸约束或者父级在子级布置之前还是之后设置子级的大小和位置等;或者确实是否允许子级读取他们父级的parentData插槽)。RenderBox子类引入布局系统使用笛卡尔坐标。
编写一个RenderObject子类
在大多数情况下,RenderObject本身的子类化过度,RenderBox将是一个更好的起点。但是,如果渲染对象不想使用笛卡尔坐标系,那么它应该直接从RenderObject继承。这允许它通过使用约束的新子类而不是使用BoxConstraints来定义自己的布局协议,并且可能使用全新的一组对象和值来表示输出的结果而不仅仅是一个Size。这种增加的灵活性的代价是无法依赖RenderBox的功能。例如,RenderBox实现了一个内在的尺寸调整协议,它允许您在没有完全铺设的情况下测量一个子级,以这样的方式,如果该子级改变了尺寸,父级将再次布置(考虑到子级的新尺寸)。这是一个微妙的和容易出错的功能。
编写RenderBox的大多数方面也适用于编写RenderObject,因此推荐先阅读RenderBox的相关讨论。主要区别在于布局和命中测试,因为这些是RenderBox主要专注的方面。

Layout

布局
布局协议从约束的子类开始。有关如何编写Constraints子类的更多信息,请参阅Constraints中的讨论。
performLayout方法应该接受约束并应用它们。布局算法的输出是设置在对象上的字段,用于描述父对象布局的对象几何图形。例如,使用RenderBox的输出是RenderBox.size字段。如果父级指定parentUsesSize为true,则在调用子级布局时,此输出只能由父级读取。任何时候渲染对象上的任何变化都会影响该对象的布局,它应该调用markNeedsLayout。 
Flutter渲染对象树的根是一个RenderView。这个对象有单独的子级,它必须是一个RenderBox。因此,如果你想在渲染树中有一个自定义的RenderObject子类,你有两种选择:你可能需要替换RenderView本身,或者你需要一个RenderBox作为它的子类。 (后者是更常见的情况。)
它会覆盖performLayout方法来创建一个适合您的类的Constraints对象,并将其传递给该子对象的布局方法。
渲染对象的布局应该仅取决于其子布局的输出,并且只有在布局调用中将parentUsesSize设置为true时才是如此。此外,如果设置为true,且要呈现子对象,则父对象必须调用子对象的布局,否则在子对象更改布局输出时不会通知父对象。
可以设置传输附加信息的渲染对象协议。 例如,在RenderBox协议中,您可以查询您的子级的固有尺寸和基线几何。 但是,如果这样做了,那么当父级在最后一个布局阶段使用它时,每当附加信息发生变化时,子级都必须在父级上调用markNeedsLayout。 有关如何实现此操作的示例,请参阅RenderBox.markNeedsLayout方法。 它覆盖了RenderObject.markNeedsLayout,以便如果父节点查询了内部或基准信息,则每当子节点的几何结构发生变化时,它都会被标记为dirty。
 

转载请注明出处

From crash_coder linguowu
linguowu0622@gamil.com
 

Flutter视图基础简介--Widget、Element、RenderObject的更多相关文章

  1. flutter系列之:构建Widget的上下文环境BuildContext详解

    目录 简介 BuildContext的本质 BuildContext和InheritedWidget BuildContext的层级关系 总结 简介 我们知道Flutter中有两种Widget,分别是 ...

  2. 现代3D图形编程学习-基础简介(2) (译)

    本书系列 现代3D图形编程学习 基础简介(2) 图形和渲染 接下去的内容对渲染的过程进行粗略介绍.遇到的部分内容不是很明白也没有关系,在接下去的章节中,会被具体阐述. 你在电脑屏幕上看到的任何东西,包 ...

  3. 现代3D图形编程学习-基础简介(1) (译)

    本书系列 现代3D图形编程学习 基础简介 并不像本书的其他章节,这章内容没有相关的源代码或是项目.本章,我们将讨论向量,图形渲染理论,以及OpenGL. 向量 在阅读这本书的时候,你需要熟悉代数和几何 ...

  4. MFC单文档视图程序简介

    在视图应用程序中,应用程序的数据由文档对象代表,数据的视图由视图对象代表.MFC的Cdocument类是文档对象的基类,Cview类是视图对象的基类.应用程序的主窗口,其操作功能在MFC的Cframe ...

  5. 1.CSS基础简介

    一.基础简介 1.简介 CSS(Cascading Style Sheet)可译为“层叠样式表”或“级联样式表”,它定义如何显示 HTML 元素,用于控制Web页面的外观.通过使用CSS实现页面的内容 ...

  6. 1.bootstrap基础简介

    一·基础简介 1.Bootstrap,来自 Twitter,是一个用于快速开发 Web 应用程序和网站的前端框架,是目前最受欢迎的前端框架. Bootstrap 是基于 HTML.CSS.JavaSc ...

  7. Android MediaPlayer 基础简介

    本文链接: Android MediaPlayer 基础简介 简单介绍MediaPlayer的基本概念,状态,常用的方法与监听器. 什么是MediaPlayer MediaPlayer类可以用来播放音 ...

  8. 理解 Flutter 的基础概念:Widget

    Widget 的本意是组件的意思,熟悉 Web 应用开发的人在后期必定会接触到 Vue.React 等框架,这些框架都有一个核心的概念 -- 组件.组件的目的也很简单,那就是重复率用一段代码,并且能够 ...

  9. 【Flutter学习】之Widget数据共享之InheritedWidget

    一,概述 业务开发中经常会碰到这样的情况,多个Widget需要同步同一份全局数据,比如点赞数.评论数.夜间模式等等.在安卓中,一般的实现方式是观察者模式,需要开发者自行实现并维护观察者的列表.在flu ...

随机推荐

  1. join sql图

    SELECT * FROM TableA INNER JOIN TableB ON TableA.name = TableB.name   id  name       id   name --  - ...

  2. 使用phpStorm编辑器进行PHP代码的xdebug调试

    首先需要安装Xdebug,如果没有安装可以查看PHP断点调试工具Xdebug的安装这篇文章.phpStorm是开发者经常用的一款编辑器,当然也支持Xdebug调试,下面说一下配置步骤. phpStor ...

  3. ios9出现的问题

    升级后需要注意两个地方  1 在build Settings 搜索bitcode 设置成no 2 在info.plist里添加以下属性  程序中报错:  App Transport Security ...

  4. 随笔-未整理-linux下流量查看

    nethogs: 按进程查看流量占用 iptraf: 按连接/端口查看流量 ifstat: 按设备查看流量 ethtool: 诊断工具 tcpdump: 抓包工具 ss: 连接查看工具 其他: dst ...

  5. SSH案例--入门级

    1.项目功能展示 (1)注册 (2)修改地址与级别信息,点击修改   (3)再添加一位成员,进行删除 点击第二行的删除 (4)登录模块测试 输入数据库中没有的信息: 输入数据库中存在的信息: 2. W ...

  6. Excel2010画动态甘特图

    哈哈!你居然真的看简介点进来啦,我也想八一八Henry gantt本人的故事,可是我查了好些资料,一个槽点都没有发现,不过人生经历还是蛮拼的: 此人活了58年,前半生就是一个中规中距的机械工程师&am ...

  7. 基于Quartz.net的远程任务管理系统 三

    在上一篇中,已经把服务端都做好了.那接下来就是Web的管理端了,因为很多时候服务器是有专门的运维来管理的,我们没有权限去操作,所以有个可以管理Job的工具还是很有必要的. Web管理端,我选择现在很成 ...

  8. WPF自定义进度条

    <!--进度条 4812--> <LinearGradientBrush x:Key="ProgressBarIndicatorAnimatedFill" Sta ...

  9. 部署网络存储ISCSI

    1.什么是ISCSIInternet Small Computer System Interface 互联网小型计算机接口技术,是一种将SCS存储与以太网技术相结合,可以用来在互联网中传输SCSI接口 ...

  10. Android 中 LayoutParams 的用法

    一个控件应当使用它的父控件的 LayoutParams 类型.因此,一个 TableVow 应该使用 TableLayout.Params . 所以,以一个 TableRow 为例: TableRow ...