原文地址: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有关的是三个绿色箭头。这些箭头告诉咱们如下内容。
这里是一个简单的可视化的状况。
简单布局父约束
这种方法的简单性掩盖了它的重要性。让咱们来研究一些重要的意义。
有不少嵌套的Widgets的屏幕能够有效地布局。即便您的屏幕变得复杂,Flutter能够(在大多数状况下)经过全部Widgets的一次传递来计算布局,并再次返回。
这就是为何您能够在您的布局中使用大量的Widgets,而且仍然能够看到快速流畅的屏幕。
屏幕被更新以反映新的状态(例如显示新的数据)或显示动画或响应输入。当这种状况发生时,Flutter没必要每次都计算屏幕的整个布局--只计算已经改变的屏幕部分。
这种优化就是所谓的子线性布局。它是可能的,由于父Widget的约束不受子Widget中发生的动画或其余更新的影响。所以Flutter不须要再次从新计算父部件的布局。这就是所谓的中继边界。
与浏览器中的回流和重绘相比,DOM树深处的变化可能会致使一路到根部的变化。HTML开发者须要考虑到这一点,而Flutter开发者则不须要--嗯......这几乎是真的,请看下一点。
咱们将在后面的文章中更详细地研究这个问题,也会研究使局部布局效率低下的异常状况。
在Flutter的布局机制中,拥有Stateless和Stateful Widgets的方法是有意义的。
Stateless Widgets能够提供布局约束,对于一个屏幕来讲是不会改变的。Stateful Widgets能够改变Widget的数据或者一些视觉约束,好比AnimatedWidget。
屏幕的部分布局意味着StatefulWidgets能够很是高效。然而要看到这个好处,咱们必须避免将StatefulWidgets做为咱们屏幕的大部分的父节点。
参见 bit.ly/2VBRRYm ,了解如何优化Stateful Widgets中子代布局的技巧。
快速看看其余UI框架如何处理布局是颇有趣的。这有助于咱们看到Flutter的方法与你以前可能看到的有些不一样。
让咱们在三个不一样的框架中画出咱们简单的2框布局。
在浏览器中,父代的大小天然是要适合子代的。在本例中,咱们能够看到父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中,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上的简单布局
立刻就到了
Flutter中的屏幕布局方法很简单但很强大。父Widgets对子Widgets实施约束,因此经过Widget树的一次传递就足以计算布局和定位。
本文将经过一个很是简单的例子来展现Flutter中的关键布局概念,以及这与其余UI框架有何不一样。
在接下来的文章中,咱们将深刻研究一些布局机制,以及如何使用这些知识来改进你的Flutter UIs。