[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。

相關文章
相關標籤/搜索