flutter架构(3):渲染和布局

flutter架构(1):概述
flutter架构(2):Widget
flutter架构(4):平台嵌入混编
flutter架构(5):web支持

Rendering and layout --渲染和布局

This section describes the rendering pipeline, which is the series of steps that Flutter takes to convert a hierarchy of widgets into the actual pixels painted onto a screen.
本部分描述了渲染管道,这是Flutter将widget的层次结构转换为绘制在屏幕上的实际像素所采取的一系列步骤。

Flutter’s rendering model --flutter的渲染模型

You may be wondering: if Flutter is a cross-platform framework, then how can it offer comparable performance to single-platform frameworks?
您可能想知道:如果Flutter是跨平台frameworks,那么它如何提供与单平台frameworks相当的性能?

It’s useful to start by thinking about how traditional Android apps work. When drawing, you first call the Java code of the Android framework. The Android system libraries provide the components responsible for drawing themselves to a Canvas object, which Android can then render with Skia, a graphics engine written in C/C++ that calls the CPU or GPU to complete the drawing on the device.
首先考虑一下传统的Android app如何工作是十分有用的。 draw时,你首先调用Android框架的Java代码。 Android系统库提供了负责将自己绘制到Canvas对象的组件,然后Android可以使用Skia进行渲染,Skia是用C / C ++编写的图形引擎,调用CPU或GPU来完成设备上的绘制(draw)。

Cross-platform frameworks typically work by creating an abstraction layer over the underlying native Android and iOS UI libraries, attempting to smooth out the inconsistencies of each platform representation. App code is often written in an interpreted language like JavaScript, which must in turn interact with the Java-based Android or Objective-C-based iOS system libraries to display UI. All this adds overhead that can be significant, particularly where there is a lot of interaction between the UI and the app logic.
跨平台framework通常通过在基础的native Android和iOS UI库上创建抽象层来工作,以尝试消除每个平台表示形式的不一致。app代码通常以JavaScript之类的解释性语言编写,必须与基于Java的Android或基于Objective-C的iOS系统库进行交互以显示UI。 所有这些都会增加大量的开销,尤其是在UI和app逻辑之间存在大量交互的情况下。

By contrast, Flutter minimizes those abstractions, bypassing the system UI widget libraries in favor of its own widget set. The Dart code that paints Flutter’s visuals is compiled into native code, which uses Skia for rendering. Flutter also embeds its own copy of Skia as part of the engine, allowing the developer to upgrade their app to stay updated with the latest performance improvements even if the phone hasn’t been updated with a new Android version. The same is true for Flutter on other native platforms, such as iOS, Windows, or macOS.
相比之下,Flutter则通过使用自己的widget集合来绕过系统UI widget,从而将这些抽象最小化。 绘制Flutter视觉效果的Dart代码被编译为native代码,flutter的native代码使用Skia进行渲染。 Flutter还在引擎中嵌入了自己的Skia副本,即使未更新手机的新Android版本,开发人员也可以升级app以保持最新的性能改进。 Flutter在其他本机平台(例如iOS,Windows或macOS)上也是如此。

From user input to the GPU --从用户输入到GPU

The overriding principle that Flutter applies to its rendering pipeline is that simple is fast. Flutter has a straightforward pipeline for how data flows to the system, as shown in the following sequencing diagram:
Flutter应用渲染管道(pipeline)的首要原则是“简洁就是快速”。 Flutter具有直接的管道(pipeline),用于指示数据如何流至系统,如以下顺序图所示:
在这里插入图片描述

Let’s take a look at some of these phases in greater detail.
让我们更详细地了解其中一些步骤。

Build: from Widget to Element --Build:从widget 到 element

Consider this simple code fragment that demonstrates a simple widget hierarchy:
考虑一下这个简单的代码片段,它演示了一个简单的widget层次结构:

Container(
  color: Colors.blue,
  child: Row(
    children: [
      Image.network('https://www.example.com/1.png'),
      Text('A'),
    ],
  ),
);

When Flutter needs to render this fragment, it calls the build() method, which returns a subtree of widgets that renders UI based on the current app state. During this process, the build() method can introduce new widgets, as necessary, based on its state. As a simple example, in the preceding code fragment, Container has color and child properties. From looking at the source code for Container, you can see that if the color is not null, it inserts a ColoredBox representing the color:
当Flutter需要呈现这个片段时,它会调用**build()**方法,该方法会返回widget的子树,这些树会根据当前app状态呈现UI。 在这过程中,build()方法可以基于状态按照需要引入新的widget。 举一个简单的例子,在前面的代码片段中,Container具有color和child属性。 通过查看“Container”的“源代码”,您可以看到,如果color不为null,则会插入一个代表颜色的“ ColoredBox”:

if (color != null)
  current = ColoredBox(color: color, child: current);

Correspondingly, the Image and Text widgets might insert child widgets such as RawImage and RichText during the build process. The eventual widget hierarchy may therefore be deeper than what the code represents, as in this case:
相应地,ImageText小部件可能会在build过程中插入子部件,例如RawImageRichText。 因此,最终的widget层次可能比代码所表示的要深,在这种情况下:
(为了便于阅读,这是一个略微的简化。 实际上,树可能更复杂)
在这里插入图片描述

This explains why, when you examine the tree through a debug tool such as the Flutter inspector, part of the Dart DevTools, you might see a structure that is considerably deeper than what is in your original code.
这就解释了为什么当您通过debug工具(例如Flutter inspector)检查树时,该工具是Dart DevTools的一部分,因此您可能会看到比原始代码更深的结构。

During the build phase, Flutter translates the widgets expressed in code into a corresponding element tree, with one element for every widget. Each element represents a specific instance of a widget in a given location of the tree hierarchy. There are two basic types of elements:
在build阶段,Flutter将用代码表示的widget转换为相应的element树,每个widget都有一个element。 每个element代表树层次结构中给定位置的widget的特定实例。 element有两种基本类型:

  1. ComponentElement, a host for other elements.
    ComponentElement,其他element的宿主。
  2. RenderObjectElement, an element that participates in the layout or paint phases.
    RenderObjectElement,一个参与layout或paint阶段的element。

在这里插入图片描述

RenderObjectElements are an intermediary between their widget analog and the underlying RenderObject, which we’ll come to later.
RenderObjectElements是它们的类似widget和基础的RenderObject之间的中介桥梁,我们将在后面介绍。

The element for any widget can be referenced through its BuildContext, which is a handle to the location of a widget in the tree. This is the context in a function call such as Theme.of(context), and is supplied to the build() method as a parameter.
任何widget件的element可以通过它的BuildContext来引用,BuildContext是树中widget位置的句柄。 这是函数调用(例如Theme.of(context))中的context,并作为参数提供给**build()**方法。

Because widgets are immutable, including the parent/child relationship between nodes, any change to the widget tree (such as changing Text(‘A’) to Text(‘B’) in the preceding example) causes a new set of widget objects to be returned. But that doesn’t mean the underlying representation must be rebuilt. The element tree is persistent from frame to frame, and therefore plays a critical performance role, allowing Flutter to act as if the widget hierarchy is fully disposable while caching its underlying representation. By only walking through the widgets that changed, Flutter can rebuild just the parts of the element tree that require reconfiguration.
由于widget是不可变的(immutable),包括节点之间的父/子关系,因此对widget的任何更改(例如,在前面的示例中将Text(‘A’) 更改为Text*‘B’)) 导致return一组新的widget对象。 但这并不意味着必须重新build基础显示。 element树在帧与帧之间是持久性的,因此起着至关重要的性能作用,使Flutter可以在缓存其基础展示时,就像widget层次结构是完全可抛弃的一样。 通过仅浏览已更改的widget,Flutter可以仅重新build element树中需要重新配置的部分。

Layout and rendering --布局和渲染

It would be a rare application that drew only a single widget. An important part of any UI framework is therefore the ability to efficiently lay out a hierarchy of widgets, determining the size and position of each element before they are rendered on the screen.
仅绘制单个widget的app是十分罕见的。 因此,任何UI framework 的重要组成能力是能够有效地布局widget的层次结构,在每一个element于屏幕上显示之前,确定它的大小和位置。

The base class for every node in the render tree is RenderObject, which defines an abstract model for layout and painting. This is extremely general: it does not commit to a fixed number of dimensions or even a Cartesian coordinate system (demonstrated by this example of a polar coordinate system). Each RenderObject knows its parent, but knows little about its children other than how to visit them and their constraints. This provides RenderObject with sufficient abstraction to be able to handle a variety of use cases.
渲染树中每个节点的基类是RenderObject,它定义了layout和paint的抽象模型。 这是非常普遍的情况:它并不commit固定数量的尺寸,甚至不commit笛卡尔坐标系(这个例子,用polar坐标系演示)。 每个RenderObject都知道其父级,但是对子级几乎一无所知,除了知道如何访问子级和constraint。 这为RenderObject提供了足够的抽象,使其能够处理各种场景。

During the build phase, Flutter creates or updates an object that inherits from RenderObject for each RenderObjectElement in the element tree. RenderObjects are primitives: RenderParagraph renders text, RenderImage renders an image, and RenderTransform applies a transformation before painting its child.
在build阶段,Flutter为element树中的每个RenderObjectElement创建或更新一个继承了RenderObject的对象。 RenderObjects是基元:RenderParagraph渲染文本,RenderImage渲染图像,RenderTransform则用于绘制其子级之前进行转换。

在这里插入图片描述
At the end of this single walk through the tree, every object has a defined size within its parent’s constraints and is ready to be painted by calling the paint() method.

在遍历树的最后,每个对象在其父容器对象的constrain范围内都有一个定义的大小,可以通过调用paint()方法进行绘制。

The box constraint model is very powerful as a way to layout objects in O(n) time:
box constraint模型作为能够在O(n)时间内布局对象的一种方法,功能非常强大:

  1. Parents can dictate the size of a child object by setting maximum and minimum constraints to the same value. For example, the topmost render object in a phone app constrains its child to be the size of the screen. (Children can choose how to use that space. For example, they might just center what they want to render within the dictated constraints.)
    父容器可以通过将constraint最大和最小设置为相同的值来决定子对象的大小。 例如,移动app中最顶部的渲染对象将其子对象限制为屏幕大小。 (子级可以选择如何使用该空间。例如,他们可能只是将要渲染的内容放在constraint指定的范围内。)
  2. A parent can dictate the child’s width but give the child flexibility over height (or dictate height but offer flexible over width). A real-world example is flow text, which might have to fit a horizontal constraint but vary vertically depending on the quantity of text.
    父容器可以决定子级的height,但给予子级的height灵活变化(或明确指定height,但width灵活变化)。 现实世界中的示例是flow text,它可能必须适应水平constraint,但根据text字数而垂直变化。

This model works even when a child object needs to know how much space it has available to decide how it will render its content. By using a LayoutBuilder widget, the child object can examine the passed-down constraints and use those to determine how it will use them, for example:
即使子对象需要知道它有多少空间来决定如何呈现其内容,这个模型也能生效。 通过使用LayoutBuilder widget,子对象可以检查传递的constraint,并且使用这些constraint来确定将如何使用,例如:

 Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        if (constraints.maxWidth < 600) {
          return OneColumnLayout();
        } else {
          return TwoColumnLayout();
        }
      },
    );

More information about the constraint and layout system, along with worked examples, can be found in the Understanding constraints topic.
有关constraint和layout系统的更多信息以及工作示例,请参见flutter:掌握布局约束Constraint

The root of all RenderObjects is the RenderView, which represents the total output of the render tree. When the platform demands a new frame to be rendered (for example, because of a vsync or because a texture decompression/upload is complete), a call is made to the compositeFrame() method, which is part of the RenderView object at the root of the render tree. This creates a SceneBuilder to trigger an update of the scene. When the scene is complete, the RenderView object passes the composited scene to the Window.render() method in dart:ui, which passes control to the GPU to render it.
所有RenderObjects的root都是RenderView,它表示渲染树的所有输出。 当平台要求渲染新帧时(例如,由于vsync或因为纹理的解压缩/上传完成),将调用CompositeFrame()方法,该方法是渲染树的root处RenderView对象的一部分 。 这将创建一个SceneBuilder来触发场景的更新。 场景完成后,RenderView对象将组合好的场景传递给dart:ui中的Window.render()方法,该方法将控制权传递给GPU进行渲染。

Further details of the composition and rasterization stages of the pipeline are beyond the scope of this high-level article, but more information can be found in this talk on the Flutter rendering pipeline.

管道的组成和栅格化阶段的更多详细信息不在本高层次文章的讨论范围之内,但可以在in this talk on the Flutter rendering pipeline【需翻墙】找到更多信息

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值