美文网首页Android开发Android开发经验谈Android技术知识
你连Flutter都不会凭什么拿高薪! 阿里P8大牛带你深入理解

你连Flutter都不会凭什么拿高薪! 阿里P8大牛带你深入理解

作者: e240c12aa0ad | 来源:发表于2020-07-09 11:58 被阅读0次

    原文:Flutter: The Advanced Layout Rule Even Beginners Must Know

    作者:Marcelo Glasberg

    译者:Vadaski

    校对:Luke ChengAlex

    前言

    这篇文章最初来自于 Marcelo Glasberg 在 Medium 发表的 Flutter: The Advanced Layout Rule Even Beginners Must Know。后被 Flutter Team 发现并收录到 flutter.dev

    在认真阅读完这篇文章后,我认为它对 Flutter 开发者来说具有相当的 指导意义,每一位 Flutter 开发都应该认真理解其中的布局约束过程,是非常必要的!因此,在翻译本文的过程中,我们对译文反复打磨,尽可能保留原文向传递给读者的内容。希望让每一位看到此文的开发者都能够有所收获。

    深入理解布局约束

    我们会经常听到一些开发者在学习 Flutter 时的疑惑:为什么我设置了 width:100, 但是看上去却不是 100 像素宽呢。(注意,本文中的“像素”均指的是逻辑像素) 通常你会回答,将这个 Widget 放进 Center 中,对吧?

    别这么干。

    如果你这样做了,他们会不断找你询问这样的问题:为什么 FittedBox 又不起作用了? 为什么 Column 又溢出边界,亦或是 IntrinsicWidth 应该做什么。

    其实我们首先应该做的,是告诉他们 Flutter 的布局方式与 HTML 的布局差异相当大 (这些开发者很可能是 Web 开发),然后要让他们熟记这条规则:

    • 首先,上层 widget 向下层 widget 传递约束条件。

    • 然后,下层 widget 向上层 widget 传递大小信息。

    • 最后,上层 widget 决定下层 widget 的位置。

    如果我们在开发时无法熟练运用这条规则,在布局时就不能完全理解其原理,所以越早掌握这条规则越好!

    更多细节:

    • Widget 会通过它的 父级 获得自身的约束。 约束实际上就是 4 个浮点类型的集合: 最大/最小宽度,以及最大/最小高度。

    • 然后,这个 widget 将会逐个遍历它的 children 列表。向子级传递 约束(子级之间的约束可能会有所不同),然后询问它的每一个子级需要用于布局的大小。

    • 然后,这个 widget 就会对它子级的 children 逐个进行布局。 (水平方向是 x 轴,竖直是 y 轴)

    • 最后,widget 将会把它的大小信息向上传递至父 widget(包括其原始约束条件)。

    例如,如果一个 widget 中包含了一个具有 padding 的 Column, 并且要对 Column 的子 widget 进行如下的布局:


    那么谈判将会像这样:

    Widget: "嘿!我的父级。我的约束是多少?"

    Parent: "你的宽度必须在 80300 像素之间,高度必须在 3085 之间。"

    Widget: "嗯...我想要 5 个像素的内边距,这样我的子级能最多拥有 290 个像素宽度和 75 个像素高度。"

    Widget: "嘿,我的第一个子级,你的宽度必须要在 0290,高度在 075 之间。"

    First child: "OK,那我想要 290 像素的宽度,20 个像素的高度。"

    Widget: "嗯...由于我想要将我的第二个子级放在第一个子级下面,所以我们仅剩 55 个像素的高度给第二个子级了。"

    Widget: "嘿,我的第二个子级,你的宽度必须要在 0290,高度在 055 之间。"

    Second child: "OK,那我想要 140 像素的宽度,30 个像素的高度。"

    Widget: "很好。我的第一个子级将被放在 x: 5 & y: 5 的位置, 而我的第二个子级将在 x: 80 & y: 25 的位置。"

    Widget: "嘿,我的父级,我决定我的大小为 300 像素宽度,60 像素高度。"

    限制

    正如上述所介绍的布局规则中所说的那样, Flutter 的布局引擎有一些重要限制:

    • 一个 widget 仅在其父级给其约束的情况下才能决定自身的大小。 这意味着 widget 通常情况下 不能任意获得其想要的大小

    • 一个 widget 无法知道,也不需要决定其在屏幕中的位置。 因为它的位置是由其父级决定的。

    • 当轮到父级决定其大小和位置的时候,同样的也取决于它自身的父级。 所以,在不考虑整棵树的情况下,几乎不可能精确定义任何 widget 的大小和位置。

    样例

    下面的示例由 DartPad 提供,具有良好的交互体验。 使用下面水平滚动条的编号切换 29 个不同的示例。

    你可以在 flutter.cn 上找到该源码。

    如果你愿意的话,你还可以在 这个 Github 仓库中 获取其代码。

    以下各节将介绍这些示例。

    样例 1

    Container(color: Colors.red)
    
    

    整个屏幕作为 Container 的父级,并且强制 Container 变成和屏幕一样的大小。

    所以这个 Container 充满了整个屏幕,并绘制成红色。

    样例 2

    Container(width: 100, height: 100, color: Colors.red)
    
    

    红色的 Container 想要变成 100 x 100 的大小, 但是它无法变成,因为屏幕强制它变成和屏幕一样的大小。

    所以 Container 充满了整个屏幕。

    样例 3

    Center(
       child: Container(width: 100, height: 100, color: Colors.red)
    )
    
    

    屏幕强制 Center 变得和屏幕一样大,所以 Center 充满了屏幕。

    然后 Center 告诉 Container 可以变成任意大小,但是不能超出屏幕。 现在,Container 可以真正变成 100 × 100 大小了。

    样例 4

    Align(
       alignment: Alignment.bottomRight,
       child: Container(width: 100, height: 100, color: Colors.red),
    )
    
    

    与上一个样例不同的是,我们使用了 Align 而不是 Center

    Align 同样也告诉 Container,你可以变成任意大小。 但是,如果还留有空白空间的话,它不会居中 Container。 相反,它将会在允许的空间内,把 Container 放在右下角(bottomRight)。

    样例 5

    Center(
       child: Container(
          color: Colors.red,
          width: double.infinity,
          height: double.infinity,
       )
    )
    
    

    屏幕强制 Center 变得和屏幕一样大,所以 Center 充满屏幕。

    然后 Center 告诉 Container 可以变成任意大小,但是不能超出屏幕。 现在,Container 想要无限的大小,但是由于它不能比屏幕更大, 所以就仅充满屏幕。

    样例 6

    Center(child: Container(color: Colors.red))
    
    

    屏幕强制 Center 变得和屏幕一样大,所以 Center 充满屏幕。

    然后 Center 告诉 Container 可以变成任意大小,但是不能超出屏幕。 由于 Container 没有子级而且没有固定大小,所以它决定能有多大就有多大, 所以它充满了整个屏幕。

    但是,为什么 Container 做出了这个决定? 非常简单,因为这个决定是由 Container widget 的创建者决定的。 可能会因创造者而异,而且你还得阅读 Container 文档 来理解不同场景下它的行为。

    样例 7

    Center(
       child: Container(
          color: Colors.red,
          child: Container(color: Colors.green, width: 30, height: 30),
       )
    )
    
    

    屏幕强制 Center 变得和屏幕一样大,所以 Center 充满屏幕。

    然后 Center 告诉红色的 Container 可以变成任意大小,但是不能超出屏幕。 由于 Container 没有固定大小但是有子级,所以它决定变成它 child 的大小。

    然后红色的 Container 告诉它的 child 可以变成任意大小,但是不能超出屏幕。

    而它的 child 是一个想要 30 × 30 大小绿色的 Container。由于红色的 Container 和其子级一样大,所以也变为 30 × 30。由于绿色的 Container 完全覆盖了红色 Container, 所以你看不见它了。

    样例 8

    Center(
       child: Container(
         color: Colors.red,
         padding: const EdgeInsets.all(20.0),
         child: Container(color: Colors.green, width: 30, height: 30),
       )
    )
    
    

    红色 Container 变为其子级的大小,但是它将其 padding 带入了约束的计算中。 所以它有一个 30 x 30 的外边距。由于这个外边距,所以现在你能看见红色了。 而绿色的 Container 则还是和之前一样。

    样例 9

    ConstrainedBox(
       constraints: BoxConstraints(
          minWidth: 70, 
          minHeight: 70,
          maxWidth: 150, 
          maxHeight: 150,
       ),
       child: Container(color: Colors.red, width: 10, height: 10),
    )
    
    

    你可能会猜想 Container 的尺寸会在 70 到 150 像素之间,但并不是这样。 ConstrainedBox 仅对其从其父级接收到的约束下施加其他约束。

    在这里,屏幕迫使 ConstrainedBox 与屏幕大小完全相同, 因此它告诉其子 Widget 也以屏幕大小作为约束, 从而忽略了其 constraints 参数带来的影响。

    样例 10

    Center(
       child: ConstrainedBox(
          constraints: BoxConstraints(
             minWidth: 70, 
             minHeight: 70,
             maxWidth: 150, 
             maxHeight: 150,
          ),
          child: Container(color: Colors.red, width: 10, height: 10),
       )    
    )
    
    

    现在,Center 允许 ConstrainedBox 达到屏幕可允许的任意大小。 ConstrainedBoxconstraints 参数带来的约束附加到其子对象上。

    Container 必须介于 70 到 150 像素之间。虽然它希望自己有 10 个像素大小, 但最终获得了 70 个像素(最小为 70)。

    样例 11

    Center(
      child: ConstrainedBox(
         constraints: BoxConstraints(
            minWidth: 70, 
            minHeight: 70,
            maxWidth: 150, 
            maxHeight: 150,
            ),
         child: Container(color: Colors.red, width: 1000, height: 1000),
      )  
    )
    
    

    现在,Center 允许 ConstrainedBox 达到屏幕可允许的任意大小。 ConstrainedBoxconstraints 参数带来的约束附加到其子对象上。

    Container 必须介于 70 到 150 像素之间。 虽然它希望自己有 1000 个像素大小, 但最终获得了 150 个像素(最大为 150)。

    样例 12

    Center(
       child: ConstrainedBox(
          constraints: BoxConstraints(
             minWidth: 70, 
             minHeight: 70,
             maxWidth: 150, 
             maxHeight: 150,
          ),
          child: Container(color: Colors.red, width: 100, height: 100),
       ) 
    )
    
    

    现在,Center 允许 ConstrainedBox 达到屏幕可允许的任意大小。 ConstrainedBoxconstraints 参数带来的约束附加到其子对象上。

    Container 必须介于 70 到 150 像素之间。 虽然它希望自己有 100 个像素大小, 因为 100 介于 70 至 150 的范围内,所以最终获得了 100 个像素。

    样例 13

    UnconstrainedBox(
       child: Container(color: Colors.red, width: 20, height: 50),
    )
    
    

    屏幕强制 UnconstrainedBox 变得和屏幕一样大,而 UnconstrainedBox 允许其子级的 Container 可以变为任意大小。

    样例 14

    UnconstrainedBox(
       child: Container(color: Colors.red, width: 4000, height: 50),
    )
    
    

    屏幕强制 UnconstrainedBox 变得和屏幕一样大, 而 UnconstrainedBox 允许其子级的 Container 可以变为任意大小。

    不幸的是,在这种情况下,容器的宽度为 4000 像素, 这实在是太大,以至于无法容纳在 UnconstrainedBox 中, 因此 UnconstrainedBox 将显示溢出警告(overflow warning)。

    样例 15

    OverflowBox(
       minWidth: 0.0,
       minHeight: 0.0,
       maxWidth: double.infinity,
       maxHeight: double.infinity,
       child: Container(color: Colors.red, width: 4000, height: 50),
    );
    
    

    屏幕强制 OverflowBox 变得和屏幕一样大, 并且 OverflowBox 允许其子容器设置为任意大小。

    OverflowBoxUnconstrainedBox 类似,但不同的是, 如果其子级超出该空间,它将不会显示任何警告。

    在这种情况下,容器的宽度为 4000 像素,并且太大而无法容纳在 OverflowBox 中, 但是 OverflowBox 会全部显示,而不会发出警告。

    样例 16

    UnconstrainedBox(
       child: Container(
          color: Colors.red, 
          width: double.infinity, 
          height: 100,
       )
    )
    
    

    这将不会渲染任何东西,而且你能在控制台看到错误信息。

    UnconstrainedBox 让它的子级决定成为任何大小, 但是其子级是一个具有无限大小的 Container

    Flutter 无法渲染无限大的东西,所以它抛出以下错误: BoxConstraints forces an infinite width.(盒子约束强制使用了无限的宽度)

    样例 17

    UnconstrainedBox(
       child: LimitedBox(
          maxWidth: 100,
          child: Container( 
             color: Colors.red,
             width: double.infinity, 
             height: 100,
          )
       )
    )
    
    

    这次你就不会遇到报错了。 UnconstrainedBoxLimitedBox 一个无限的大小; 但它向其子级传递了最大为 100 的约束。

    如果你将 UnconstrainedBox 替换为 Center, 则LimitedBox 将不再应用其限制(因为其限制仅在获得无限约束时才适用), 并且容器的宽度允许超过 100。

    上面的样例解释了 LimitedBoxConstrainedBox 之间的区别。

    样例 18

    FittedBox(
       child: Text('Some Example Text.'),
    )
    
    

    屏幕强制 FittedBox 变得和屏幕一样大, 而 Text 则是有一个自然宽度(也被称作 intrinsic 宽度), 它取决于文本数量,字体大小等因素。

    FittedBoxText 可以变为任意大小。 但是在 Text 告诉 FittedBox 其大小后, FittedBox 缩放文本直到填满所有可用宽度。

    样例 19

    [图片上传中...(image-1e20d2-1594265213190-11)]

    <figcaption></figcaption>

    Center(
       child: FittedBox(
          child: Text('Some Example Text.'),
       )
    )
    
    

    但如果你将 FittedBox 放进 Center widget 中会发生什么? Center 将会让 FittedBox 能够变为任意大小, 取决于屏幕大小。

    FittedBox 然后会根据 Text 调整自己的大小, 然后让 Text 可以变为所需的任意大小, 由于二者具有同一大小,因此不会发生缩放。

    样例 20

    Center(
       child: FittedBox(
          child: Text('This is some very very very large text that is too big to fit a regular screen in a single line.'),
       )
    )
    
    

    然而,如果 FittedBox 位于 Center 中, 但 Text 太大而超出屏幕,会发生什么?

    FittedBox 会尝试根据 Text 大小调整大小, 但不能大于屏幕大小。然后假定屏幕大小, 并调整 Text 的大小以使其也适合屏幕。

    样例 21

    Center(
       child: Text('This is some very very very large text that is too big to fit a regular screen in a single line.'),
    )
    
    

    然而,如果你删除了 FittedBoxText 则会从屏幕上获取其最大宽度, 并在合适的地方换行。

    样例 22

    FittedBox(
       child: Container(
          height: 20.0, 
          width: double.infinity,
       )
    )
    
    

    FittedBox 只能在有限制的宽高中 对子 widget 进行缩放(宽度和高度不会变得无限大)。 否则,它将无法渲染任何内容,并且你会在控制台中看到错误。

    样例 23

    Row(
       children:[
          Container(color: Colors.red, child: Text('Hello!')),
          Container(color: Colors.green, child: Text('Goodbye!')),
       ]
    )
    
    

    屏幕强制 Row 变得和屏幕一样大,所以 Row 充满屏幕。

    UnconstrainedBox 一样, Row 也不会对其子代施加任何约束, 而是让它们成为所需的任意大小。 Row 然后将它们并排放置, 任何多余的空间都将保持空白。

    样例 24

    Row(
       children:[
          Container(color: Colors.red, child: Text('This is a very long text that won’t fit the line.')),
          Container(color: Colors.green, child: Text('Goodbye!')),
       ]
    )
    
    

    由于 Row 不会对其子级施加任何约束, 因此它的 children 很有可能太大 而超出 Row 的可用宽度。在这种情况下, Row 会和 UnconstrainedBox 一样显示溢出警告。

    样例 25

    Row(
       children:[
          Expanded(
             child: Container(color: Colors.red, child: Text('This is a very long text that won’t fit the line.'))
          ),
          Container(color: Colors.green, child: Text('Goodbye!')),
       ]
    )
    
    

    Row 的子级被包裹在了 Expanded widget 之后, Row 将不会再让其决定自身的宽度了。

    取而代之的是,Row 会根据所有 Expanded 的子级 来计算其该有的宽度。

    换句话说,一旦你使用 Expanded, 子级自身的宽度就变得无关紧要,直接会被忽略掉。

    样例 26

    Row(
       children:[
          Expanded(
             child: Container(color: Colors.red, child: Text(‘This is a very long text that won’t fit the line.’)),
          ),
          Expanded(
             child: Container(color: Colors.green, child: Text(‘Goodbye!’),
          ),
       ]
    )
    
    

    如果所有 Row 的子级都被包裹了 Expanded widget, 每一个 Expanded 大小都会与其 flex 因子成比例, 并且 Expanded widget 将会强制其子级具有与 Expanded 相同的宽度。

    换句话说,Expanded 忽略了其子 Widget 想要的宽度。

    样例 27

    Row(children:[
      Flexible(
        child: Container(color: Colors.red, child: Text('This is a very long text that won’t fit the line.'))),
      Flexible(
        child: Container(color: Colors.green, child: Text(‘Goodbye!’))),
      ]
    )
    
    

    如果你使用 Flexible 而不是 Expanded 的话, 唯一的区别是,Flexible 会让其子级具有与 Flexible 相同或者更小的宽度。 而 Expanded 将会强制其子级具有和 Expanded 相同的宽度。 但无论是 Expanded 还是 Flexible 在它们决定子级大小时都会忽略其宽度。

    这意味着,Row 要么使用子级的宽度, 要么使用ExpandedFlexible 从而忽略子级的宽度。

    样例 28

    Scaffold(
       body: Container(
          color: blue,
          child: Column(
             children: [
                Text('Hello!'),
                Text('Goodbye!'),
             ]
          )))
    
    

    屏幕强制 Scaffold 变得和屏幕一样大, 所以 Scaffold 充满屏幕。 然后 Scaffold 告诉 Container 可以变为任意大小, 但不能超出屏幕。

    当一个 widget 告诉其子级可以比自身更小的话, 我们通常称这个 widget 对其子级使用 宽松约束(loose)

    样例 29

    Scaffold(
    body: SizedBox.expand(
       child: Container(
          color: blue,
          child: Column(
             children: [
                Text('Hello!'),
                Text('Goodbye!'),
             ],
          ))))
    
    

    如果你想要 Scaffold 的子级变得和 Scaffold 本身一样大的话, 你可以将这个子级外包裹一个 SizedBox.expand

    当一个 widget 告诉它的子级必须变成某个大小的时候, 我们通常称这个 widget 对其子级使用 严格约束(tight)

    严格约束(Tight) vs 宽松约束(loose)

    以后你经常会听到一些约束为严格约束或宽松约束, 你花点时间来弄明白它们是值得的。

    严格约束给你了一种获得确切大小的选择。 换句话来说就是,它的最大/最小宽度是一致的,高度也一样。

    如果你到 Flutter 的 box.dart 文件中搜索 BoxConstraints 构造器,你会发现以下内容:

    BoxConstraints.tight(Size size)
       : minWidth = size.width,
         maxWidth = size.width,
         minHeight = size.height,
         maxHeight = size.height;
    
    

    如果你重新阅读 样例 2, 它告诉我们屏幕强制 Container 变得和屏幕一样大。 为何屏幕能够做到这一点, 原因就是给 Container 传递了严格约束。

    一个宽松约束换句话来说就是设置了最大宽度/高度, 但是让允许其子 widget 获得比它更小的任意大小。 换句话来说,宽松约束的最小宽度/高度为 0

    BoxConstraints.loose(Size size)
       : minWidth = 0.0,
         maxWidth = size.width,
         minHeight = 0.0,
         maxHeight = size.height;
    
    

    如果你访问 样例 3, 它将会告诉我们 Center 让红色的 Container 变得更小, 但是不能超出屏幕。Center 能够做到这一点的原因就在于 给 Container 的是一个宽松约束。 总的来说,Center 起的作用就是从其父级(屏幕)那里获得的严格约束, 为其子级(Container)转换为宽松约束。

    了解如何为特定 widget 制定布局规则

    掌握通用布局是非常重要的,但这还不够。

    应用一般规则时,每个 widget 都具有很大的自由度, 所以没有办法只看 widget 的名称就知道可能它长什么样。

    如果你尝试推测,可能就会猜错。 除非你已阅读 widget 的文档或研究了其源代码, 否则你无法确切知道 widget 的行为。

    布局源代码通常很复杂,因此阅读文档是更好的选择。 但是当你在研究布局源代码时,可以使用 IDE 的导航功能轻松找到它。

    下面是一个例子:

    • 在你的代码中找到一个 Column 并跟进到它的源代码。 为此,请在 (Android Studio/IntelliJ) 中使用 command+B(macOS)或 control+B(Windows/Linux)。 你将跳到 basic.dart 文件中。由于 Column 扩展了 Flex, 请导航至 Flex 源代码(也位于 basic.dart 中)。

    • 向下滚动直到找到一个名为 createRenderObject() 的方法。 如你所见,此方法返回一个 RenderFlex。 它是 Column 的渲染对象, 现在导航到 flex.dart 文件中的 RenderFlex 的源代码。

    • 向下滚动,直到找到 performLayout() 方法, 由该方法执行列布局。

    最后,十分感谢参与校对的程路Alex,以及帮助打磨译文的 CaiJingLong任宇杰孙恺 以上几位同学,谢谢!

    希望看完这篇文章,能够对你有所收获。如果你遇到任何疑惑,或者想要与我讨论,欢迎在底部评论区一起交流,或是通过邮箱与我联系。Happy coding!

    中文链接:debug.flutter.cn/docs/develo…

    如何系统化学习Android高级架构技术?

    对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

    这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

    由于篇幅有限,这里以图片的形式给大家展示一小部分。

    Android高级工程师Flutter系统学习知识体系


    Flutter进阶学习全套手册

    详细整理在石墨文档可以见;

    Android架构视频+BAT面试专题PDF+学习笔记​
    【Android高级工程师进阶系统面试题】:下载链接

    网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

    技术进阶之路很漫长,一起共勉吧~

    相关文章

      网友评论

        本文标题:你连Flutter都不会凭什么拿高薪! 阿里P8大牛带你深入理解

        本文链接:https://www.haomeiwen.com/subject/tkejcktx.html