[Flutter翻译]Flutter Anatomy - 布局内部的第1部分

原文地址:medium.com/snapp-mobil…html

原文做者:medium.com/@jasperamor…算法

发布时间:2019年8月21日浏览器

照片:Mathew Schwartz on Unsplash。bash

Flutter Anatomy是一系列关于什么让Flutter...Flutter的文章。咱们试图深刻了解Flutter如何工做,以更好地了解框架的一些伟大功能。app

在这篇文章中,咱们开始看Flutter如何使用独特的方法来计算屏幕布局,这有助于Flutter的速度和流畅的UI渲染。这也是让Flutter可以使用很是简单的Widget组成模型,即便是复杂的屏幕也是如此。框架

对于图形用户界面框架来讲,布局是决定用户界面元素的大小和位置的活动。尺寸和位置被称为几何。 对于咱们常常在移动应用中看到的流体和相对布局,计算UI元素的几何形状变得很困难。布局经理一般必须在UI元素的层次结构中进行几回传递,以计算父元素及其子元素的尺寸和位置--这被称为多传递布局。less

另外一方面,Flutter使用线性(以及在可能的状况下使用子线性)布局。但这意味着什么?ide

简单地说,它意味着Flutter中的布局是经过UI Widgets树来计算每个UI元素的几何形状的一个通道(向下和向上)。(这并不老是可能的,咱们将在之后的文章中讨论)。布局

一个重要的含义是,Widget树的子集能够被更新,而没必要计算整个屏幕的布局。这种优化就是子线型布局的意思。优化

若是你熟悉浏览器的重绘和回流,Flutter的方法应该已经看起来是一个大规模的优化了。

这是如何工做的呢?

核心概念是父Widget对容许子Widget的大小进行限制。基于这些约束,子Widgets将其计算出的大小传回给父Widget。最后,父Widget决定子Widget的位置。

固然也有例外,但咱们会在后面的文章中讲到。

让咱们用一个简单的图来可视化。

小组件树中的约束和尺寸

蓝色的父节点约束黄色的子节点,而黄色的子节点又约束其子节点。这决定了子节点的最大和最小尺寸。而后,小组件根据这些约束计算出的大小会传回树上。

约束是什么样子的?

约束是子节点容许的最大和最小高度和宽度的简单组合。咱们将在另外一篇文章中更详细地研究约束)。

限制条件

假设上面树中的蓝色widget宽为100.0,高为100.0,那么黄色的子widget的宽度和高度均可以最小为0.0,最大为100.0。

请注意,咱们在Flutter中并无设置X/Y位置,尽管对于某些widget来讲,一些子部件的定位是可能的,例如,使用Positionied widget与Stack Widget 。


让咱们经过建立一个受其父体约束的子部件,来看看这种基于约束的方法在行动。 咱们建立一个宽度为100.0、高度为100.0的容器(父容器)。父容器的子容器将是另外一个容器,但但愿比其父容器更宽--咱们设置宽度为200.0,高度为100.0。

子容器的首选宽度和高度将违反其父容器所规定的约束。父容器会告诉子容器,它的最大宽度和高度只能是100.0。基于Flutter布局算法,子代将其大小限制在w=100.0,h=100.0。

让咱们根据这个简单的例子来深刻了解一些代码。

class SimpleParentChildExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
        color: Colors.blue,
        width: 100.0,
        height: 100.0,
        child: Container(
          color: Colors.yellow,
          height: 100.0,
          width: 200.0,
        ));
  }
复制代码

这段代码很琐碎,运行应用后,能够看到咱们指望看到的东西--黄色的子Widget与父Widget大小相同。

简单Flutter布局

要想了解更多的状况,咱们来看看渲染树。


渲染树(快速绕行)

等等,这个 "渲染树 "是什么东西?这个'渲染树'是什么东西?咱们先绕道来了解一下这是什么。

在Flutter中,渲染树是计算Widget树布局的结果。换句话说,渲染树包含了描述如何绘制一个Widget的低级UI对象。在本文中,咱们感兴趣的是,每一个UI对象(称为RenderObject)都有一个几何体--即将在屏幕上绘制的东西的大小和位置。


让咱们再来看看咱们简单UI的渲染树。

Container Widget其实是由两个Widgets组成的,这就是为何你会看到每一个Container的RenderConstrainedBox和RenderDecoratedBox。为了更清楚,我将渲染对象分别用蓝色(父容器)和黄色(子容器)标记。

你能够去Container Widget的源码中看一看,看看它是如何工做的 bit.ly/2PqGvQq

与咱们理解Flutter中的Layout有关的是三个绿色箭头。这些箭头告诉咱们如下内容。

  • 黄色子Widget的BoxConstraints与蓝色父Widget的BoxConstraints相匹配(即w=100.0,h=100.0)。
  • 渲染对象的大小是w=100.0,h=100.0--这符合父Widget所规定的BoxContraints,而不是咱们在黄色子Widget中设置的200.0的宽度。换句话说,没有隐藏的溢出)。
  • 'extraConstraints'属性保留了咱们为子Widget设置的首选宽度和高度的记录。

这里是一个简单的可视化的状况。

简单布局父约束

那又怎样?

这种方法的简单性掩盖了它的重要性。让咱们来研究一些重要的意义。

1/ 复杂屏幕的高效布局

有不少嵌套的Widgets的屏幕能够有效地布局。即便您的屏幕变得复杂,Flutter能够(在大多数状况下)经过全部Widgets的一次传递来计算布局,并再次返回。

这就是为何您能够在您的布局中使用大量的Widgets,而且仍然能够看到快速流畅的屏幕。

2/ 屏幕更新时的部分布局

屏幕被更新以反映新的状态(例如显示新的数据)或显示动画或响应输入。当这种状况发生时,Flutter没必要每次都计算屏幕的整个布局--只计算已经改变的屏幕部分。

这种优化就是所谓的子线性布局。它是可能的,由于父Widget的约束不受子Widget中发生的动画或其余更新的影响。所以Flutter不须要再次从新计算父部件的布局。这就是所谓的中继边界。

与浏览器中的回流和重绘相比,DOM树深处的变化可能会致使一路到根部的变化。HTML开发者须要考虑到这一点,而Flutter开发者则不须要--嗯......这几乎是真的,请看下一点。

咱们将在后面的文章中更详细地研究这个问题,也会研究使局部布局效率低下的异常状况。

3/ 混合无状态和有状态的Widgets

在Flutter的布局机制中,拥有Stateless和Stateful Widgets的方法是有意义的。

Stateless Widgets能够提供布局约束,对于一个屏幕来讲是不会改变的。Stateful Widgets能够改变Widget的数据或者一些视觉约束,好比AnimatedWidget。

屏幕的部分布局意味着StatefulWidgets能够很是高效。然而要看到这个好处,咱们必须避免将StatefulWidgets做为咱们屏幕的大部分的父节点。

参见 bit.ly/2VBRRYm ,了解如何优化Stateful Widgets中子代布局的技巧。

其余UI框架有何不一样?

快速看看其余UI框架如何处理布局是颇有趣的。这有助于咱们看到Flutter的方法与你以前可能看到的有些不一样。

让咱们在三个不一样的框架中画出咱们简单的2框布局。

HTML

在浏览器中,父代的大小天然是要适合子代的。在本例中,咱们能够看到父DIV被推出来,宽度为200px。固然也能够将溢出样式属性设置为 "隐藏",这样子DIV就被剪掉了,视觉上是100px×100px。

<html>

<body>

  <div style="width: 100px; height: 100px; background-color: blue;">
    <div style="width: 200px; height: 100px; background-color: yellow"></div>
  </div>

</body>

</html>
复制代码

这种布局看起来以下。

简单的HTML布局

iOS

在iOS中,UIViews能够 "包含 "其余UIVIew做为子视图,这并不彻底是父子关系。关键的区别在于,这些视图居住在不一样的层中,而子视图位于其父视图之上。这意味着子视图的宽度将达到200。另外一个重要的区别是,UIViews有位置--在这种状况下,约束用于使用锚约束来定位一个View与另外一个View的相对位置。

class ViewController: UIViewController {

    lazy var yellowSquare: UIView = {
        let square = UIView(frame: .zero)
        square.backgroundColor = .yellow
        square.translatesAutoresizingMaskIntoConstraints = false
        return square
    }()

    lazy var blueSquare: UIView = {
        let square = UIView(frame: .zero)
        square.backgroundColor = .blue
        square.translatesAutoresizingMaskIntoConstraints = false
        return square
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        blueSquare.addSubview(yellowSquare)

        blueSquare.addConstraints([
            yellowSquare.topAnchor.constraint(equalTo: blueSquare.topAnchor),
            yellowSquare.bottomAnchor.constraint(equalTo: blueSquare.bottomAnchor),
            yellowSquare.leadingAnchor.constraint(equalTo: blueSquare.leadingAnchor),
            yellowSquare.trailingAnchor.constraint(equalTo: blueSquare.trailingAnchor),
            yellowSquare.widthAnchor.constraint(equalToConstant: 200),
            yellowSquare.heightAnchor.constraint(equalToConstant: 100)
        ])

        view.addSubview(blueSquare)

        view.addConstraints([
            blueSquare.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 50),
            blueSquare.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 50)
        ])

    }

}
复制代码

布局看起来以下。

iOS上的简单布局

安卓系统

立刻就到了

TL;DR

Flutter中的屏幕布局方法很简单但很强大。父Widgets对子Widgets实施约束,因此经过Widget树的一次传递就足以计算布局和定位。

本文将经过一个很是简单的例子来展现Flutter中的关键布局概念,以及这与其余UI框架有何不一样。

在接下来的文章中,咱们将深刻研究一些布局机制,以及如何使用这些知识来改进你的Flutter UIs。

相关文章
相关标签/搜索