原文地址: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。