深入Flutter(五) 专业化API

文章系列
深入Flutter(一) 积极推进“组合”方式
深入Flutter(二) 线性时间复杂度的 build 和 layout
深入Flutter(三) Element树和RenderObject树
深入Flutter(四) Infinite scrolling – 无限滚动

原文:Inside Flutter

Specializing APIs to match the developer’s mindset --专业化API以符合开发人员的思维方式

The base class for nodes in Flutter’s Widget, Element, and RenderObject trees does not define a child model. This allows each node to be specialized for the child model that is applicable to that node.
Flutter的Widget,Element和RenderObject树中的节点的基类未定义子模型。 这允许每个节点特殊开发适用于该节点的子模型。

Most Widget objects have a single child Widget, and therefore only expose a single child parameter. Some widgets support an arbitrary number of children, and expose a children parameter that takes a list. Some widgets don’t have any children at all and reserve no memory, and have no parameters for them. Similarly, RenderObjects expose APIs specific to their child model. RenderImage is a leaf node, and has no concept of children. RenderPadding takes a single child, so it has storage for a single pointer to a single child. RenderFlex takes an arbitrary number of children and manages it as a linked list.
大多数Widget对象只有一个子Widget,因此仅公开一个子参数。 一些widget支持任意数量的子代,并公开带有列表的子代参数。 有些widget根本没有子级,也不做任何持有,也没有任何参数。 同样,RenderObjects公开特定于其子模型的API。 RenderImage是叶节点,没有子级的概念。 RenderPadding需要一个孩子,因此它可以存储指向一个子项的单个指针。 RenderFlex可以接收任意数量的子代并将其作为链接列表进行管理。

In some rare cases, more complicated child models are used. The RenderTable render object’s constructor takes an array of arrays of children, the class exposes getters and setters that control the number of rows and columns, and there are specific methods to replace individual children by x,y coordinate, to add a row, to provide a new array of arrays of children, and to replace the entire child list with a single array and a column count. In the implementation, the object does not use a linked list like most render objects but instead uses an indexable array.
在极少数情况下,会使用更复杂的子模型。 RenderTable渲染对象的构造函数采用一系列由子代组成的数组,该类公开用于控制行和列数的getter和setter,并且有一些特定的方法可以用x,y坐标替换单个子代,以添加一行(row),提供 一个新的子数组数组,并将整个子列表替换为一个数组和一个列(column)数。 在实现中,该对象不像大多数渲染对象那样使用链接列表(linked list),而是使用可索引数组(array list)。

The Chip widgets and InputDecoration objects have fields that match the slots that exist on the relevant controls. Where a one-size-fits-all child model would force semantics to be layered on top of a list of children, for example, defining the first child to be the prefix value and the second to be the suffix, the dedicated child model allows for dedicated named properties to be used instead.
Chip widget和InputDecoration对象的字段与相关控件上存在的插槽匹配。 如果“一刀切”的子模型会强制将语义分层放置在子列表的顶部,例如,将第一个子项定义为前缀值,将第二个子项定义为后缀,则专用子模型可以 用于专用的命名属性。

This flexibility allows each node in these trees to be manipulated in the way most idiomatic for its role. It’s rare to want to insert a cell in a table, causing all the other cells to wrap around; similarly, it’s rare to want to remove a child from a flex row by index instead of by reference.
这种灵活性使这些树中的每个节点都可以按照其角色最惯用的方式进行操作。 很少要在表格中插入一个单元格,而导致其他所有单元格都回绕; 同样,很少要通过索引而不是通过引用从flex行中删除子级。

The RenderParagraph object is the most extreme case: it has a child of an entirely different type, TextSpan. At the RenderParagraph boundary, the RenderObject tree transitions into being a TextSpan tree.
RenderParagraph对象是最极端的情况:它有一个完全不同类型的子对象TextSpan。 在RenderParagraph 边界处,RenderObject树过渡为 TextSpan树。

The overall approach of specializing APIs to meet the developer’s expectations is applied to more than just child models.
专门用于满足开发人员期望的API的总体方法不仅仅适用于子模型。

Some rather trivial widgets exist specifically so that developers will find them when looking for a solution to a problem. Adding a space to a row or column is easily done once one knows how, using the Expanded widget and a zero-sized SizedBox child, but discovering that pattern is unnecessary because searching for space uncovers the Spacer widget, which uses Expanded and SizedBox directly to achieve the effect.
存在一些平时不太重要的widget,以便开发人员在寻找问题的解决方案时会找到它们。 一旦知道了如何使用Expanded widget和大小为零的SizedBox子级就可以轻松地向行或列中添加空格,但是无需发现该模式,因为使用Spacer widget会搜索未覆盖的空间,相当于直接使用Expanded和SizedBox来达到的效果。

Similarly, hiding a widget subtree is easily done by not including the widget subtree in the build at all. However, developers typically expect there to be a widget to do this, and so the Visibility widget exists to wrap this pattern in a trivial reusable widget.
同样,想要隐藏widget子树,只需要:不把widget子树包含在build中,就可以轻松实现。 但是,开发人员通常希望有一个widget来执行此操作,因此,Visibility widget 可以将这种模式包装在可重复使用的widget中。

Explicit arguments --明确的参数

UI frameworks tend to have many properties, such that a developer is rarely able to remember the semantic meaning of each constructor argument of each class. As Flutter uses the reactive paradigm, it is common for build methods in Flutter to have many calls to constructors. By leveraging Dart’s support for named arguments, Flutter’s API is able to keep such build methods clear and understandable.
UI框架往往具有许多属性,因此开发人员很少能够记住每个类的每个构造函数参数的语义含义。 由于Flutter使用了响应式编程,因此Flutter中的build方法通常会多次调用构造函数。 通过利用Dart对命名参数的支持,Flutter的API可以使此类构建方法清晰易懂。

This pattern is extended to any method with multiple arguments, and in particular is extended to any boolean argument, so that isolated true or false literals in method calls are always self-documenting. Furthermore, to avoid confusion commonly caused by double negatives in APIs, boolean arguments and properties are always named in the positive form (for example, enabled: true rather than disabled: false).
这种模式扩展到具有多个参数的多个方法,尤其是扩展到任何boolean参数,以便方法调用中孤立的true或false literal始终是自记录的。 此外,为避免API中通常由双负数引起的混乱,boolean参数和属性始终以正数形式命名(例如,enabled:true而不是disable:false)。

Paving over pitfalls --铺路陷阱

A technique used in a number of places in the Flutter framework is to define the API such that error conditions don’t exist. This removes entire classes of errors from consideration.
Flutter framework在许多地方使用的一种技术是定义API,让错误条件不复存在。 这移除了整个错误类别的考虑。

For example, interpolation functions allow one or both ends of the interpolation to be null, instead of defining that as an error case: interpolating between two null values is always null, and interpolating from a null value or to a null value is the equivalent of interpolating to the zero analog for the given type. This means that developers who accidentally pass null to an interpolation function will not hit an error case, but will instead get a reasonable result.
例如,插值(interpolation)函数允许插值的一端或两端为null,而不是将其定义为错误情况:在两个null值之间进行插值始终为null,并且从null值插值到null值等于 插入给定类型的零变化。 这意味着不小心将null传递给插值函数的开发人员不会遇到错误情况,而是会获得合理的结果。

A more subtle example is in the Flex layout algorithm. The concept of this layout is that the space given to the flex render object is divided among its children, so the size of the flex should be the entirety of the available space. In the original design, providing infinite space would fail: it would imply that the flex should be infinitely sized, a useless layout configuration. Instead, the API was adjusted so that when infinite space is allocated to the flex render object, the render object sizes itself to fit the desired size of the children, reducing the possible number of error cases.
Flex layout算法是一个更微妙的示例。 这种layout的概念是,给予flex渲染对象的空间在它的子对象之间划分,因此flex的大小应为可用空间的整体。 在默认基础设计中,提供无限的空间将失败:这将意味着将范围调整为无限大小,一个无用的layout配置。改进的方式是, flex 的 API进行了调整,以便在将无限空间分配给flex渲染对象时,渲染对象自己调整大小以适合所需的子代大小,从而减少了可能发生的错误情况。

The approach is also used to avoid having constructors that allow inconsistent data to be created. For instance, the PointerDownEvent constructor does not allow the down property of PointerEvent to be set to false (a situation that would be self-contradictory); instead, the constructor does not have a parameter for the down field and always sets it to true.
该方法还用于避免:允许使用创建不一致数据的构造函数。 例如,PointerDownEvent构造函数不允许将PointerEvent的down属性设置为false(这种情况会自相矛盾)。 相反,构造函数没有用于down字段的参数,而是始终将其设置为true。

In general, the approach is to define valid interpretations for all values in the input domain. The simplest example is the Color constructor. Instead of taking four integers, one for red, one for green, one for blue, and one for alpha, each of which could be out of range, the default constructor takes a single integer value, and defines the meaning of each bit (for example, the bottom eight bits define the red component), so that any input value is a valid color value.
通常,该方法是为输入域中的所有值定义有效的解释。 最简单的示例是Color构造函数。 构造函数不是使用四个整数(一个用于红色,一个用于绿色,一个用于蓝色,一个用于alpha透明度,每个整数都可能超出范围。 例如,低八位定义红色部分),默认做法是:采用一个整数值并定义每个位的含义,这样任何输入值都是有效的颜色值。

A more elaborate example is the paintImage() function. This function takes eleven arguments, some with quite wide input domains, but they have been carefully designed to be mostly orthogonal to each other, such that there are very few invalid combinations.
一个更详尽的示例是paintImage()函数。 此函数采用11个参数,有些参数的输入域很宽,但经过精心设计,它们彼此之间几乎是正交的,因此几乎没有无效的组合。

Reporting error cases aggressively --积极的报告error 场景

Not all error conditions can be designed out. For those that remain, in debug builds, Flutter generally attempts to catch the errors very early and immediately reports them. Asserts are widely used. Constructor arguments are sanity checked in detail. Lifecycles are monitored and when inconsistencies are detected they immediately cause an exception to be thrown.
并非所有error条件都可以设计出来。 对于仍然存在的那些error,在debug版本中,Flutter通常尝试尽早发现error并立即报告它们。 Asserts 被广泛使用。 构造函数的参数经过详细的完整性检查。 生命周期受到监视,当检测到不一致时,它们会立即抛出异常(thrown exception)。

In some cases, this is taken to extremes: for example, when running unit tests, regardless of what else the test is doing, every RenderBox subclass that is laid out aggressively inspects whether its intrinsic sizing methods fulfill the intrinsic sizing contract. This helps catch errors in APIs that might otherwise not be exercised.
在某些情况下,这将推向极端的:例如,在运行单元测试时,无论测试还在做什么,每个layout的RenderBox子类都会积极检查其固有的尺寸调整方法是否满足固有的尺寸调整约定。 这有助于捕获API中可能无法执行的错误(catch error)。

When exceptions are thrown, they include as much information as is available. Some of Flutter’s error messages proactively probe the associated stack trace to determine the most likely location of the actual bug. Others walk the relevant trees to determine the source of bad data. The most common errors include detailed instructions including in some cases sample code for avoiding the error, or links to further documentation.
当抛出异常(thrown exception)时,异常将包含尽可能多的信息。 Flutter的某些错误消息会主动探测相关的堆栈跟踪以确定实际错误的最可能位置。 其他则走在相关的树,以确定不良数据的来源。 最常见的错误包括详细说明,包括在某些情况下避免错误的示例代码,或指向更多文档的链接。

Reactive paradigm --响应式

Mutable tree-based APIs suffer from a dichotomous access pattern: creating the tree’s original state typically uses a very different set of operations than subsequent updates. Flutter’s rendering layer uses this paradigm, as it is an effective way to maintain a persistent tree, which is key for efficient layout and painting. However, it means that direct interaction with the rendering layer is awkward at best and bug-prone at worst.
可变树的基本API遭遇二分法访问模式:创建树的原始状态通常使用的操作集与后续更新完全不同。 Flutter的渲染层使用了这种(二分法访问)模式,因为这是维护持久树的有效方法,这对于高效的layout和paint至关重要。 但是,这意味着与渲染层的直接交互很尴尬,最糟糕的是容易产生bug。

Flutter’s widget layer introduces a composition mechanism using the reactive paradigm to manipulate the underlying rendering tree. This API abstracts out the tree manipulation by combining the tree creation and tree mutation steps into a single tree description (build) step, where, after each change to the system state, the new configuration of the user interface is described by the developer and the framework computes the series of tree mutations necessary to reflect this new configuration.
Flutter的widget层引入了一种通过响应式(灵感来自facebook的react)来操纵底层渲染树的组合机制。 该API通过将树创建树变化步骤组合到单个树描述(build)步骤中,从而抽象出树操作,其中,在每次更改系统状态后,开发人员和framework计算出一系列反映这种新配置所必需的树变化

Interpolation --Interpolation(插值:动画的一种模式)

Since Flutter’s framework encourages developers to describe the interface configuration matching the current application state, a mechanism exists to implicitly animate between these configurations.
由于Flutter的framework鼓励开发人员描述与当前app state匹配的interface配置,因此存在一种在这些配置之间进行隐式动画处理的机制。

For example, suppose that in state S1 the interface consists of a circle, but in state S2 it consists of a square. Without an animation mechanism, the state change would have a jarring interface change. An implicit animation allows the circle to be smoothly squared over several frames.
例如,假设在状态S1中,界面由圆形组成,但是在状态S2中,界面由正方形组成。 没有动画机制,状态更改将具有令人讨厌的界面更改(显得突兀)。 隐式动画允许圆形在数帧上平滑地变成正方形

Each feature that can be implicitly animated has a stateful widget that keeps a record of the current value of the input, and begins an animation sequence whenever the input value changes, transitioning from the current value to the new value over a specified duration.
隐式动画的每个功能都有一个stateful widge,该widge记录输入的当前值,并在输入值发生更改时开始动画序列,并在指定的持续时间内从当前值转换为新值。

This is implemented using lerp (linear interpolation) functions using immutable objects. Each state (circle and square, in this case) is represented as an immutable object that is configured with appropriate settings (color, stroke width, etc) and knows how to paint itself. When it is time to draw the intermediate steps during the animation, the start and end values are passed to the appropriate lerp function along with a t value representing the point along the animation, where 0.0 represents the start and 1.0 represents the end8, and the function returns a third immutable object representing the intermediate stage.
这是通过使用“不可变(immutable)对象”以及“lerp(线性插值)功能”实现的。 每个状态(在这种情况下为圆形状态正方形状态)都表示为一个不可变的对象,该对象配置了适当的设置(颜色color,笔触宽度stroke width等),并且知道如何绘制自身。 当需要在动画期间绘制中间步骤时,将开始值和结束值以及t值传递到相应的lerp函数,其中t值表示动画中的点,其中0.0表示开始,1.0表示结束,并且该函数 return表示中间阶段的第三个不可变对象。

For the circle-to-square transition, the lerp function would return an object representing a “rounded square” with a radius described as a fraction derived from the t value, a color interpolated using the lerp function for colors, and a stroke width interpolated using the lerp function for doubles. That object, which implements the same interface as circles and squares, would then be able to paint itself when requested to.
对于圆到正方形的过渡,lerp函数将return一个表示“圆角正方形”的对象,其半径描述为从t值得出的分数,使用lerp函数对颜色进行插值的颜色以及对笔划宽度进行double插值 。 “圆角正方形”的对象实现了与圆形和正方形相同的界面,然后可以在需要时绘制自身。

This technique allows the state machinery, the mapping of states to configurations, the animation machinery, the interpolation machinery, and the specific logic relating to how to paint each frame to be entirely separated from each other.
这种技术允许状态机(state machinery)中,state到配置(configuration)的映射,动画机(animation machinery),插值机(interpolation machinery),如何绘制每个帧有关的特定逻辑彼此完全分开。

This approach is broadly applicable. In Flutter, basic types like Color and Shape can be interpolated, but so can much more elaborate types such as Decoration, TextStyle, or Theme. These are typically constructed from components that can themselves be interpolated, and interpolating the more complicated objects is often as simple as recursively interpolating all the values that describe the complicated objects.
这种方法广泛适用。 在Flutter中,可以插补(interpolate)诸如Color和Shape之类的基本类型,甚至可以插补(interpolate)更复杂的类型,如Decoration,TextStyle或Theme。 它们通常由本身可以插值的组件构成,插值更复杂的对象通常与递归插值描述复杂对象的所有值一样简单。

Some interpolatable objects are defined by class hierarchies. For example, shapes are represented by the ShapeBorder interface, and there exists a variety of shapes, including BeveledRectangleBorder, BoxBorder, CircleBorder, RoundedRectangleBorder, and StadiumBorder. A single lerp function cannot have a priori knowledge of all the possible types, and therefore the interface instead defines lerpFrom and lerpTo methods, which the static lerp method defers to. When told to interpolate from a shape A to a shape B, first B is asked if it can lerpFrom A, then, if it cannot, A is instead asked if it can lerpTo B. (If neither is possible, then the function returns A from values of t less than 0.5, and returns B otherwise.)
一些可插补对象由类层次结构定义。 例如,形状由ShapeBorder接口表示,并且存在多种形状,包括BeveledRectangleBorder,BoxBorder,CircleBorder,RoundedRectangleBorder和StadiumBorder。 单个lerp函数不能预知所有可能类型,因此接口定义了lerpFrom和lerpTo方法,static lerp方法将这些方法推导给它们。 当被告知从形状A插值到形状B时,首先询问B是否可以lerpFrom A,然后,如果不能,询问A是否可以lerpTo B。(如果两者都不可行,在t的值小于0.5时,该函数 return A ,大于0.5,则returnB。)

This allows the class hierarchy to be arbitrarily extended, with later additions being able to interpolate between previously-known values and themselves.
这样类的层级结构可以任意扩展,以后的扩增可以在先前已知的值及其自身之间进行内插。

In some cases, the interpolation itself cannot be described by any of the available classes, and a private class is defined to describe the intermediate stage. This is the case, for instance, when interpolating between a CircleBorder and a RoundedRectangleBorder.
在某些情况下,插值本身无法用任何公用类来描述,只能定义私有类来描述中间阶段。 例如,在CircleBorder和RoundedRectangleBorder之间进行插值时就是这种情况。

This mechanism has one further added advantage: it can handle interpolation from intermediate stages to new values. For example, half-way through a circle-to-square transition, the shape could be changed once more, causing the animation to need to interpolate to a triangle. So long as the triangle class can lerpFrom the rounded-square intermediate class, the transition can be seamlessly performed.
该机制还有一个附加的优点:它可以处理从中间阶段到新值的插值。 例如,在圆形状到正方形过渡的中间,可以再次更改形状,例如:在动画中插值三角形。 只要三角形类可以从四舍五入的中间类中消失,就可以无缝地执行过渡。

Conclusion --结论

Flutter’s slogan, “everything is a widget,” revolves around building user interfaces by composing widgets that are, in turn, composed of progressively more basic widgets. The result of this aggressive composition is a large number of widgets that require carefully designed algorithms and data structures to process efficiently. With some additional design, these data structures also make it easy for developers to create infinite scrolling lists that build widgets on demand when they become visible.

Flutter的口号是“一切都是widget”,围绕着通过build “更多基础widget组成的widget”来构建用户界面。 这种激进的组合的结果是大量widget,这些widget需要精心设计的算法和数据结构才能有效地进行处理。 通过一些额外的设计,这些数据结构还使开发人员可以轻松地创建无限长度的scrolling list页面,在可见时按需build widget。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值