原文
How Flutter uses Widgets, Elements and RenderObjects to create
delicious eye-candy at 120fps. Flutter 如何使用 Widgets、Element 和
RenderObject 以 120fps 的速度创建美味的视觉效果。
Flutter is a great UI framework and it’s a bliss to build beautiful user interfaces at light speed. Just jot down a few lines of code, hit save [INSERT MAGIC SUB-SECOND HOT RELOAD HERE] and voilà —your app is running smooth as silk at up to 120fps. However, have you ever asked yourself how it comes that Flutter is so fast? What’s the secret sauce it’s made of? Or — how Flutter actually works? Well, then search no more! Go grab a fresh coffee (or tea) and read on.
Flutter 是一个很棒的 UI 框架,能够以光速构建漂亮的用户界面是一种幸福。 只需记下几行代码,点击保存 [INSERT MAGIC SUB-SECOND HOT RELOAD HERE],然后瞧——你的app运行流畅,速度高达 120fps。 然而,你有没有问过自己,Flutter 为何如此之快? 它的秘诀是什么? 或者——Flutter 是如何工作的? 好吧,那就不要再搜索了! 去拿一杯新鲜的咖啡(或茶)并继续阅读。
Maybe you have already heard that Flutter is all about widgets. Your app is a widget, a text is a widget, the padding around a widget is a widget and you even recognise gestures through a widget. Well, that’s not the entire truth. What if I told you that widgets make Flutter development fast and easy but you could also build a whole Flutter app without even using a single widget. Let’s find out how by digging a little bit deeper into the framework.
也许你已经听说过了:Flutter 全是关于Widget。你的app是widget,一个text是 widget,widget周围的padding是一个widget,你甚至可以通过widget识别手势。 好吧,这还不是全部。 如果我告诉你widget让 Flutter 开发变得快速和容易,但你也可以构建一个完整的 Flutter app,甚至不需要使用任何一个widget。 让我们通过更深入地研究framework来找出方法。
Probably you’ve already seen an overview of Flutter’s architecture in one of those ‘Intro to Flutter’ talks but somehow you weren’t ready to grasp the powerful concept behind all those different layers back then. Maybe you are like me and you just didn’t get it by simply staring at that abstract diagram for the exact 20 seconds the slide was projected onto the wall. Don’t worry, I’m here to help. Have a look at the following graphic:
可能你已经在其中一个“介绍Flutter”的演讲中看到了 Flutter 架构的概述,但不知何故,你当时还没有准备好掌握所有这些不同layer背后的强大概念。 也许你和我一样,只是盯着那张抽象图看幻灯片投影到墙上的那 20 秒,你并没有明白这一点。 别担心,我是来帮忙的。 看看下面的图形:
The different layers of the Flutter framework. For more detail on this, head over to flutter架构(1):概述
Flutter 框架的不同层。 有关这方面的更多详细信息,请访问flutter架构(1):概述
The Flutter framework itself is composed of many layers of abstraction: At top there are the commonly used Material and Cupertino widgets, followed by the more generic Widget layer. Most of the time you’ll find yourself using widgets from those two layers and that’s perfectly fine. The probability that you have already seen (and used) one of them out there in the vast wild should be quite high (think of the Scaffold or the FloatingActionButton from the Material library or the Column and the GestureDetector from the widgets library).
Flutter 框架本身由许多抽象层组成:最上面是常用的 Material 和 Cupertino widget,然后是更通用的 Widget 层。 大多数时候,你会发现自己使用这两个层的widget,这非常好。 你已经很有可能在广阔的野外看到(并使用)其中一个(想想Material库中的 Scaffold 或 FloatingActionButton 或widget库中的 Column 和 GestureDetector)。
Below the widgets layer you’ll find the rendering layer which simplifies the layout and painting process and is another abstraction for the dart:ui library at the bottom. dart:ui is the last Dart layer which basically handles the communication with the Flutter engine.
在widget层下方,你会发现渲染层,它简化了布局和绘画过程,是底部 dart:ui 库的另一个抽象。 dart:ui 是最后一个 Dart 层,它基本上处理与 Flutter 引擎的通信。
To put it simply one can say that the higher levels are easier to handle whereas the lower ones give you more fine-grained control with added complexity.
简而言之,可以说较高的级别更容易处理,而较低的级别可以为你提供更细粒度的控制并增加复杂性。
The dart:ui library exposes the lowest-level services that Flutter frameworks use to bootstrap applications, such as classes for driving the input, graphics text, layout, and rendering subsystems.
dart:ui 库公开了 Flutter framework用于引导app的最低级服务,例如用于驱动输入、图形文本、布局和渲染等相关的子系统的类。
So basically you could write a ‘Flutter’ app by just instantiating classes from the dart:ui package (e.g. Canvas, Paint and TextBox). However, if you are familiar with directly painting onto the canvas, you know that everything that goes beyond painting a still image of a stick figure will be a pain to manage. And then think not only about painting but also about orchestrating the layout and hit-testing the elements of your app.
所以基本上你可以通过实例化 dart:ui 包中的类(例如 Canvas、Paint 和 TextBox)来编写一个“Flutter”app。 但是,如果你熟悉直接在画布上绘画,你就会知道,除了paint一个简笔画的静态image之外的所有事情都将是一件很痛苦的事情。 然后不仅要考虑paint,还要考虑layout和hit-testing(flutter手势实现的一种策略) app的Element。
So what does this exactly mean? It means that you would have to manually calculate all coordinates used in your layout. Then mix in some painting and hit testing to catch user input. Do that for every single frame and keep track of that. This approach may be manageable as long as you plan on building a simple app which just displays some text centered within a blue box, but not so great if you try to build more complex layouts like a shopping app or even a small game. Don’t even dare to think of animations, scrolling or other fancy UI stuff we all love. I am telling you based on my own experience, this is endless fuel for developer nightmares.
那么这到底是什么意思呢? 这意味着你必须手动计算layout中使用的所有坐标。 然后混合一些paint并点击测试以捕捉用户输入。 对每一帧都这样做并跟踪它。 你的计划只是构建一个简单的app,app只在蓝色框内居中显示一些文本,用以上办法还可以管控,但如果你尝试构建更复杂的布局,例如购物app甚至是小游戏,就很难搞。 甚至不敢去想我们都喜欢的动画、滚动或其他花哨的 UI 东西。 我根据我自己的经验告诉你,这是开发者噩梦的无尽燃料。
The Flutter rendering tree. The RenderObject hierarchy is used by the Flutter Widgets library to implement its layout and painting back-end. Generally, while you may use custom RenderBox classes for specific effects in your applications, most of the time your only interaction with the RenderObject hierarchy will be in debugging layout issues.
Flutter render树。 Flutter Widgets 库使用 RenderObject 层次结构来实现背后的layout和paint。 通常,虽然你可以在app中为特定效果使用自定义 RenderBox 类,但大多数时候你与 RenderObject 层次结构的唯一交互将是debug layout 的问题。
The rendering library is the first abstraction layer on top of the dart:ui library and does all the heavy math work for you (e.g. keeping track of the calculated coordinates, etc.). In order to do that it uses so called RenderObjects. You can compare RenderObjects to the engine of a car — they are the components that do the actual work to bring your app onto the screen. This tree composed out of RenderObjects will later get layed out and painted by Flutter. To optimise this complex process Flutter uses a smart algorithm to aggressively cache those expensive computations in an intelligent way to keep the amount of work on every iteration minimal.
render 库是 dart:ui 库之上的第一个抽象层,它为你完成所有繁重的数学工作(例如,跟踪计算的坐标等)。 为了做到这一点,它使用了所谓的 RenderObjects。 你可以将 RenderObjects 与汽车的引擎进行比较——它们是执行实际工作以将你的app显示在屏幕上的组件。 这棵由 RenderObjects 组成的树稍后将由 Flutter 进行 layout和paint。 为了优化这个复杂的过程,Flutter 使用了一种智能算法,以一种智能的方式积极缓存那些昂贵的计算,以保持每次迭代的工作量最小。
Amazing.
令人惊叹。
Most of the time you’ll find Flutter uses a RenderBox instead of another RenderObject. That’s because the people behind the project realised that a simple box layout protocol works very well to build performant UIs. Think of every widget placed in it’s own box which is calculated and then arranged with other pre-laid-out boxes. So if only one widget of your layout changes (e.g. a button or a switch), only this relatively small box needs to be recomputed by the system.
大多数时候你会发现 Flutter 使用 RenderBox 而不是其他的RenderObject。 那是因为项目背后的人意识到一个简单的盒子布局协议可以很好地构建高性能的 UI。 想想放置在它自己的盒子中的每个widget,该盒子被计算然后与其他预先布置的盒子一起排列。 因此,如果你的布局中只有一个widget发生变化(例如按钮或开关),则系统只需要重新计算这个相对较小的box。
The Flutter widgets framework. What else?
Flutter widget框架。 还有什么?
The widgets library — probably the most interesting layer — is another layer of abstraction which provides ready-to-use UI components we can simply drop into our app. All widgets you find in this library also fall in one of the following three categories which are handled by the appropriate RenderObject for you:
widget库——可能是最有趣的层——是另一个抽象层,它提供了现成的 UI 组件,我们可以简单地将其放入我们的app中。 你在此库中找到的所有widget也属于以下三个类别之一,这些类别由适当的 RenderObject 为你处理:
Typically you will use many of those ‘basic’ widgets and compose your own widgets out of that. As an example you could build a button out of a Container which you wrap into a GestureDetector to detect a button press. This is called composition over inheritance.
通常,你将使用许多这些“基本”widget并从中组成你自己的widget。 例如,你可以从 Container 中构建一个按钮,然后将其包装到 GestureDetector 中以检测按钮按下。 这称为组合而不是继承。
However, instead of building every UI component by yourself, the Flutter team has created two libraries which contain frequently used widgets in the Material and Cupertino (iOS-like) style.
然而,Flutter 团队并没有自己构建每个 UI 组件,而是创建了两个库,其中包含 Material 和 Cupertino(类似 iOS)风格的常用widget。
Flutter widgets implementing Material Design & the current iOS design language.
Flutter widget 实现了 Material Design 和当前 iOS 设计语言。
Flutter is all about abstraction and making your life as a developer easier. This is the fourth level which contains pre-built elements from the Material design specs and some recreated iOS-style widgets. Think of AlertDialog, Switch and FloatingActionButton. If you are an iOS user, CupertinoAlertDialog, CupertinoButton and CupertinoSwitch should look familiar to you.
Flutter 完全是关于抽象,让你作为开发人员的生活更轻松。 这是第四层,它包含来自 Material 设计规范的预构建Element和一些重新创建的 iOS 风格的widget。 想想 AlertDialog、Switch 和 FloatingActionButton。 如果你是 iOS 用户,CupertinoAlertDialog、CupertinoButton 和 CupertinoSwitch 对你来说应该很熟悉。
How are RenderObjects connected to Widgets? How does Flutter create the layout? Or what is an Element?
RenderObjects 是如何连接到 Widgets 的? Flutter 如何创建Layout? 或者什么是Element?
Enough with the talking, let’s start walking and see how things add up in real life! Consider the following (simplified) widget tree:
说得够多了,让我们开始走进去,看看现实生活中的事情是如何加起来的! 考虑以下(简化的)widget树:
In the real world widgets like Text are composed out of many other widgets. To keep things simple, we introduce the phantasmal SimpleContainer and SimpleText.
在现实世界中,像 Text 这样的widget是由许多其他widget组成的。 为了简单起见,我们引入了虚构的 SimpleContainer 和 SimpleText。
The app we are building is pretty simple for now. It just consists out of three stateless widgets: SimpleApp, SimpleContainer and SimpleText. So what happens when we hand it over to Flutter’s runApp(SimpleApp()) method?
我们正在构建的app现在非常简单。 它仅由三个stateless widget组成:SimpleApp、SimpleContainer 和 SimpleText。 那么当我们把它交给 Flutter 的 runApp(SimpleApp()) 方法时会发生什么?
The first time runApp() is called, a bunch of things happen in the background:
第一次调用 runApp() 时,后台发生了一堆事情:
Flutter will build the widget tree containing our three stateless widgets.
Flutter 将构建包含我们的三个stateless widget的widget树。
Flutter walks down the widget tree and creates a second tree which contains the corresponding Element objects by calling createElement()on the widget (…what are Element objects again? Hold on, we get there in a second!).
Flutter 沿着widget树向下走,并通过在widget上调用 createElement() 创建第二棵树,其中包含相应的 Element 对象(……又是什么是 Element 对象?等一下,我们马上就到了!)。
A third tree is created and filled with the appropriate RenderObjects which are created by the Element invoking the createRenderObject() method on the corresponding widget.
第三棵树被创建并填充了适当的 RenderObjects,这些 RenderObjects 由 Element 调用相应widget上的 createRenderObject() 方法创建。
Here is a picture of what the current situation looks like after Flutter went through the three steps described above:
这是 Flutter 经过上述三个步骤后的现状图:
A forest of trees. Back to the roots of abstract infographics!
一片树林。 回到抽象信息图表的根源!
The Flutter framework has created three different trees, one for the widgets, one for the elements and one for the render objects. Every Element holds a reference to a Widget and RenderObject. “Hey, I know Widgets but what are Elements and RenderObjects?” I hear you ask.
Flutter 框架创建了三种不同的树,一种用于widget,一种用于Element,另一种用于renderObject。 每个Element都包含对 Widget 和 RenderObject 的引用。 “嘿,我知道widget,但Element和renderObject是什么?” 我听到你的提问。
The RenderObject contains all the logic for rendering the (corresponding) actual widget and is quite expensive to instantiate. It takes care of the layout, painting and hit-testing. It’s a good idea to keep those objects in memory as long as possible and maybe even recycle them (since they are quite costly to instantiate). That’s where the Elements come in. Basically, they are the glue between the immutable Widget tree and the mutable RenderObject tree. Elements are principally objects that are really good at comparing two objects with each other, in our case the widget and the render object. They represent the use of a widget to configure a specific location in the tree and keep a reference to the related Widget and RenderObject.
RenderObject 包含用于呈现(相应)实际widget的所有逻辑,并且实例化非常昂贵。 它负责layout、paint和hit-testing。 将这些对象尽可能长时间地保存在内存中是个好主意,甚至可以循环复用它们(因为实例化它们的成本很高)。 这就是 Elements 的用武之地。基本上,它们是不可变的 Widget 树和可变的 RenderObject 树之间的粘合剂。 Element对象主要是非常擅长将两个对象相互比较,例如我们示例中的widget和renderObject。 它们展示使用widget来配置树中的特定位置并保留对相关 Widget 和 RenderObject 的引用。
Why is it such a good idea to have three trees instead of one? The short answer is it’s really performant. Every time the widget tree changes Flutter uses the tree of Elements to compare the new widget tree with the already existing RenderObjects. When the type of a widget is the same as before, Flutter does not need to recreate the expensive RenderObject and just updates its mutable configuration. Since Widgets are very lightweight and cheap to instantiate they are a perfect for describing the current state (also referred to as ‘configuration’) of the app. The ‘fat’ RenderObjects (which are expensive to create) are not recreated every time and reused whenever possible. As fellow Simon pointed out, “The whole app acts like a huge RecyclerView”.
为什么拥有三棵树而不是一棵树是个好主意? 简短的回答是它真的很高效。 每次 widget 树发生变化时,Flutter 都会使用 Elements 树来比较新的 widget 树和已经存在的 RenderObjects。 当一个 widget 的类型和之前一样时,Flutter 不需要重新创建昂贵的 RenderObject,只需更新它的可变配置。 由于widget非常轻量级且实例化成本低廉,因此它们非常适合描述app的当前状态(也称为“配置”)。 “笨重的”renderObject(创建起来很昂贵)不会每次都重新创建并尽可能重用。 正如 Simon 同事指出的那样,“整个应用就像一个巨大的 RecyclerView”。
However, in the framework those Elements are very well ‘abstracted away’ so you won’t have to deal with them very often. The BuildContext passed in every build(BuildContext context) function is actually the corresponding Element wrapped into the BuildContext interface and that’s why it’s different for every single widget.
然而,在框架中,这些Element被很好地“抽象”掉了,所以你不必经常处理它们。 在每个build(BuildContext context)函数中传递的 BuildContext 实际上是包装到 BuildContext 接口中的相应Element,这就是为什么每个widget都不同的原因。
Since Widgets are immutable, with every configuration change the widget tree needs to be rebuilt. When we change the color of our container to red, a rebuild will be triggered by the framework which will recreate the whole widget tree since it is immutable. Next, with the help of the Elements in the element tree, Flutter will compare the first item in the new widget tree with the first item in the render tree, then the second item in the new widget tree with the second item in the render tree and so on.
由于widget是不可变的,因此每次配置更改时都需要重建widget树。 当我们将container的颜色更改为红色时,framework将触发重建,ramework将重新创建整个widget树,因为它是不可变的。 接下来,在Element树中的 Elements 的帮助下,Flutter 会将新widget树中的第一项与 render树中的第一项进行比较,然后将新widget树中的第二项与 render树中的第二项进行比较 等等。
Change the container color to red. Nothing special here.
将容器颜色更改为红色。 这里没什么特别的。
Flutter will follow a basic rule here: check if the old and the new widgets are from the same type. If not, remove the Widget, the Element and the RenderObject from the tree (including subtrees) and create new objects. If they are from the same type, just update the configuration of the RenderObject to represent the new configuration of the widget and continue travelling down the tree.
Flutter 将在这里遵循一个基本规则:检查旧widget 和 新widget是否同一类型。 如果不是,则从树(包括子树)中删除 Widget、Element 和 RenderObject 并创建新对象。 如果它们来自同一类型,只需更新 RenderObject 的配置以展示widget的新配置并继续沿树向下移动。
In our example, the SimpleApp widget is the same type as before and has the same configuration as the appropriate SimpleAppRender object, so nothing will change. The next item in the widget tree is the SimpleContainer widget but with a different color configuration. As the SimpleContainer still needs a SimpleContainerRender object in order to be drawn, Flutter just updates the color attribute on the SimpleContainerRender object and asks it for a redraw. The other objects will stay untouched.
在我们的示例中,SimpleApp widget与之前的类型相同,并且与相应的 SimpleAppRender 对象具有相同的配置,因此不会发生任何变化。 widget树中的下一项是 SimpleContainer widget,但具有不同的颜色配置。 由于 SimpleContainer 仍然需要一个 SimpleContainerRender 对象才能被绘制,Flutter 只需更新 SimpleContainerRender 对象的颜色属性并要求它重绘。 其他对象将保持不变。
The state of the three trees after rebuilding the widget tree and updating the config of the RenderObjects. Notice that the Element and RenderObjects are still the same instances.
重建widget树并更新 RenderObjects 的配置后三棵树的状态。 请注意,Element 和 RenderObjects 仍然是相同的实例。
This process is fast because Flutter is really good at creating those simple widgets which just represent the current configuration of the app. The ‘heavy’ objects will stay untouched until the corresponding widget type is removed from the widget tree. What happens if the type of a widget changes?
这个过程很快,因为 Flutter 非常擅长创建那些仅代表app当前配置的简单widget。 “笨重”对象将保持不变,直到从widget树中删除相应的widget类型。 如果widget的类型发生变化会发生什么?
The SimpleText got replaced by a SimpleButton.
SimpleText 替换成 SimpleButton。
Again, Flutter will iterate over the newly built widget tree and compare the type of the widgets with the type of the RenderObjects in the render tree.
同样,Flutter 将遍历新构建的widget树,并将widget的类型与 render树中的 RenderObjects 的类型进行比较。
The new widget tree. SimpleText, the corresponding Element and SimpleTextRender have been removed from the trees.
新的widget树。 SimpleText,相应的 Element 和 SimpleTextRender 已从树中移除。
Since the SimpleButton does not match the type of the Element at the position in the element tree, Flutter will remove the Element and corresponding SimpleTextRender from the two other trees. It then continues to traverse down the newly created widget tree and instantiates the appropriate Elements and RenderObjects.
由于 SimpleButton 与Element树中对应位置的 Element 类型不匹配,Flutter 会从另外两棵树中移除 Element 和对应的 SimpleTextRender。 然后它继续向下遍历新创建的widget树并实例化适当的Element和renderObject。
The final trees:
最终生成的树:
And bingo! The new render tree has been built and will now be layed out and painted to the screen. For that Flutter will make use of a lot of optimisations and aggressive caching strategies so you don’t have to take care of them manually. Pretty cool, ugh?
bingo! 新的 render树已经建立,现在将被layout并paint到屏幕上。 因此,Flutter使用了大量优化和积极的缓存策略,因此你不必手动处理它们。 很酷,嗯?
Now you should have a clearer picture in your head about how Flutter actually works and why it is relatively fast in rendering even complex layouts. We could go on and discuss how bringing in the State would make the whole process even more performant but unfortunately I have to tell you that this is it for today (will cover that topic in a an upcoming post — promised!).
现在,你应该对 Flutter 的实际工作原理以及为什么它在渲染复杂布局方面相对较快有了更清晰的了解。 我们可以继续讨论引入 State 将如何使整个过程更加高效,但不幸的是,我必须告诉你,这就是今天的内容(将在下一篇文章中讨论这个主题——承诺!)。