本文已经获得做者的容许,将其原文The Layer Cake翻译成中文。鉴于本人的英语能力以及表达能力有限,请英语水平足够的朋友前往原文地址去阅读=。=。 算法
Widget
。你的APP是个
Widget
、Text是个
Widget
,
Widget
周围的
padding
也是
Widget
,甚至
recognise gestures
(手势识别)也是一个
Widget
。可是这些并非所有的事实。若是我告诉你
Widget
的确很棒,可以帮助你快速的构建出APP,可是我不使用任何一个
Widget
就可以完成App的构建你相信吗?让咱们先来深刻框架来看看如何作到这一切吧。
也许你已经在一些相似于‘Flutter入门介绍’的文章中对Flutter有了比较大体的了解。可是你并无可以真正的理解这些层级所表明的概念。也许你像我同样看着这张图看了20s殊不知道怎么理解。不用担忧,我会帮助你的。看下下面的这个图吧。 canvas
Material
和
Cupertino
Widgets。咱们大多数状况下使用的就是这两类Widget。 在Widget层下面,你会发现
Rendering
层。
Rendering
层简化了布局和绘制过程。它是
dart:ui
的的抽象化。
dart:ui
是框架的最底层,它负责处理与
Engine
层的交流沟通。 简而言之,等级越高的层越容易使用,可是等级越低的层,暴露出来的api越多,越可以增长自定义功能。
dart:ui library
暴露出最底层的服务,这些服务被用来引导Application,例如用来驱动输入、绘制文字、布局和渲染子系统。api
因此你能够仅仅经过使用实例化dart:ui
库中的类(例如Canvas
、Paint
和TextField
)来构建一个Flutter App。可是若是你对于直接在canvas
上绘制比较熟悉,就会知道使用这些底层api绘制一个图案是既难又繁琐的。 接下来考虑一些不是绘制的东西吧,例如布局和命中测试。 这些意味着什么呢? 这意味着你必须手动的计算全部在你布局中使用的坐标。而后混合一些绘制和命中测试来捕获用户的输入。对每一帧进行上述操做并追踪它们。这个方法对于那些比较简单的APP,好比一个在蓝色区域内展现文字这种比较适用。若是对于那些比较复杂的APP或者简单的游戏来讲可够你受的了。更不用说产品经理最喜好的动画、滚动和一些酷炫的UI效果了。用我多年的开发经验告诉你,这些是开发者无穷无尽的梦魇。缓存
Flutter的
Rendering tree
(渲染树)。RenderObject
的层级结构被Flutter Widgets库使用来实现其布局和后台的绘制。一般来讲,尽管你可能会使用RenderBox
来在你的应用中实现自定义的效果,可是大多数状况下咱们惟一与RenderObject
的交互就是在调试布局信息的时候。框架
Rendering library
是dart:ui library
上第一个抽象层。它替你作了全部繁重的数学计算工做(例如跟踪须要不断计算的坐标)。它使用RenderObjects
来处理这些工做。你能够把RenderObjects
想象成一个汽车的发动机,它承担了全部把你的APP展现到屏幕的工做。Rendering tree
中的全部RenderObjects
都会被Flutter分层和绘制。为了优化这个复杂的过程,Flutter使用了一个智能算法来缓存这些实例化很耗费性能的对象从而实如今性能最优化。 大多数状况,你会发现Flutter使用RenderBox
而不是RenderObject
。这是由于项目的构建者发现使用一个简单和盒布局约束就可以成功的构建出有效稳定的UI。想象一下全部的Widget都被放置在它们的盒中。这个盒中的相关参数都计算好了,而后被放置到其余已经整理好的盒中间。因此若是在你的布局中仅有一个Widget改变了,只须要装载其的盒被系统从新计算便可。less
Flutter Widgets框架工具
Widget库或许是最有意思的库。它是另一个用来提供开箱即用的Widget的抽象层。这个库中全部的Widget都属于如下三种使用适当的RenderObject
处理的Widget之一。布局
Layout
例如Column
和Row
Widgets用来帮助咱们轻松的处理其余Widget的布局。Painting
例如Text
和Image
Widgets容许咱们展现(绘制)一些内容在屏幕上。Hit-Testing
例如GestureDetector
容许咱们识别出不一样的手势,例如点击和滑动。大多数状况下咱们会使用一些“基础”Widget来组成咱们须要的Widget。例如咱们使用GestureDetec
来包裹Container
,Container
中包裹Button来处理按钮点击。这叫作组合而不是继承。 然而除了本身构建每一个UI组件,Flutter团队还建立了两个包含经常使用的Material
和Cupertino
风格的Widgets的库。性能
使用Material和Cupertino设计规范的Widgets库。学习
Flutter为了减小开发者的负担,建立了这个拥有Material
和Cupertino
风格的Widgets层。
RenderObject
是如何与Widgets
链接起来的呢?Flutter是如何建立布局?Element又是什么呢? 已经说的够多了,让咱们在实践中学习吧。考虑以下Widgets树。
Stateless Widget
组成:
SimpleApp
、
SimpleContainer
、
SimpleText
。因此若是咱们调用Flutter的runApp()方法会发生什么呢? 当
runApp()
被调用时,第一时间会在后台发生如下事件。
createElement()
来建立相应的Element对象,最后将这些对象组建成Element
树。Element
经过createRenderObject()
建立的RenderObject。 下图是Flutter通过这三个步骤后的状态:
Flutter建立了三个不一样的树,一个对应着Widget
,一个对应着Element
,一个对应着RenderObject
。每个Element
中都有着相对应的Widget
和RenderObject
的引用。
那什么又是RenderObject
呢? RenderObject
中包含了全部用来渲染实例Widget
的逻辑。它负责layout
、painting
和hit-testing
。它的生成十分耗费性能,因此咱们应该尽量的缓存它。咱们把它在内存中尽量的保存更长的时间,甚至回收利用它们(由于它们的实例化真的很耗费资源)。这个时候Element
就登场了。Element
是存在于可变Widget
树和不可变RenderObject
树之间的桥梁。Element
擅长比较两个Object
,在Flutter里面就是Widget
和RenderObject
。它的做用是配置好Widget
在树中的位置,而且保持对于相对应的RenderObject
和Widget
的引用。 为何使用三个树而不是一个树呢? 简而言之是为了性能。当Widget
树改变的时候,Flutter使用Element
树来比较新的Widget
树和原来的RenderObject
树。若是某一个位置的Widget
和RenderObject
类型不一致,才须要从新建立RenderObject
。若是其余位置的Widget
和RenderObject
类型一致,则只须要修改RenderObject
的配置,不用进行耗费性能的RenderObject
的实例化工做了。由于Widget
是很是轻量级的,实例化耗费的性能不多,因此它是描述APP的状态(也就是configuration)的最好工具。重量级的RenderObject
(建立十分耗费性能)则须要尽量少的建立,并尽量的复用。就像Simon所说:整个Flutter APP就像是一个RecycleView。 然而,在框架中,Element
是被抽离开来的,因此你不须要常常和它们打交道。每一个Widget的build(BuildContext context)
方法中传递的context
就是实现了BuildContext
接口的Element
,这也就是为何相同类别的单个Widget
不一样的缘由。
由于Widget
是不可变的,当某个Widget
的配置改变的时候,整个Widget
树都须要被重建。例如当咱们改变一个Container
的颜色为红色的时候,框架就会触发一个重建整个Widget
树的动做。而后在Element
的帮助下,Flutter比较新的Widget
树中的第一个Widget
类型和RenderObject
树中第一个RenderObject
的类型。接下来比较Widget
树中第二个Widget
和RenderObject
树中第二个RenderObject
的类型,以此类推,直到Widget
树和RendObject
树比较完成。
Widget
和老的Widget
是不是同一个类型。 若是不是同一个类型,那就把
Widget
、
Element
、
RenderObject
分别从它们的树(包括它们的子树)上移除,而后建立新的对象。 若是是一个类型,那就仅仅修改
RenderObject
中的配置,而后继续向下遍历。 在咱们的例子中,
SimpleApp Widget
是和原来同样的类型,它的配置也是和原来的
SimpleAppRender
同样的,因此什么都不会发生。下一个item在Widget树中是
SimpleContainer Widget
,它的类型和原来是同样的,可是它的颜色变化了,
RenderObject
的配置发生变化了。由于
SimpleObject
仍然须要一个
SimpleContainerRender
来渲染,Flutter只是更新了
SimpleContainerRender
的颜色属性,而后要求它从新渲染。其余的对象都保持不变。
Widgets
。那些重量级的
RenderObject
则是保持不变,直到与其相对应类型的
Widget
从
Widget
树中被移除。那若是Widget的类型发生改变了会发生什么呢?
RenderObject
树中的
RenderObject
类型进行对比。
SimpleButton
的类型与
Element
树中相对应位置的
Element
的类型不一样(实际上仍是与
RenderObject
的类型进行比较),Flutter将会从各自的树上删除这个
Element
和相对应的
SimpleTextRender
。而后Flutter将会重建与
SimpleButton
相对应的
Element
和
RenderObject
。
RenderObject
树已经被重建,并将会计算布局,而后绘制在屏幕上面。Flutter内部使用了不少优化方法和缓存策略来处理,因此你不须要手动处理这个。
如今你应该对Flutter为何能以如此快的速度渲染复杂布局有了大体的了解了。我但愿这篇文章可以帮助你更好的理解Flutter内部的设计理念。个人Twitter是 Frederik Schweiger,期待与你的交流。