[Flutter翻譯]Flutter Anatomy - 佈局內部的第1部分

Sunbreak發表於2020-07-12

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

原文作者:medium.com/@jasperamor…

釋出時間:2019年8月21日

[Flutter翻譯]Flutter Anatomy - 佈局內部的第1部分

照片:Mathew Schwartz on Unsplash。

Flutter Anatomy是一系列關於什麼讓Flutter...Flutter的文章。我們試圖深入瞭解Flutter如何工作,以更好地瞭解框架的一些偉大功能。

在這篇文章中,我們開始看Flutter如何使用獨特的方法來計算螢幕佈局,這有助於Flutter的速度和流暢的UI渲染。這也是讓Flutter能夠使用非常簡單的Widget組成模型,即使是複雜的螢幕也是如此。

對於圖形使用者介面框架來說,佈局是決定使用者介面元素的大小和位置的活動。尺寸和位置被稱為幾何。 對於我們經常在移動應用中看到的流體和相對佈局,計算UI元素的幾何形狀變得很困難。佈局經理通常必須在UI元素的層次結構中進行幾次傳遞,以計算父元素及其子元素的尺寸和位置--這被稱為多傳遞佈局。

另一方面,Flutter使用線性(以及在可能的情況下使用子線性)佈局。但這意味著什麼?

簡單地說,它意味著Flutter中的佈局是通過UI Widgets樹來計算每一個UI元素的幾何形狀的一個通道(向下和向上)。(這並不總是可能的,我們將在以後的文章中討論)。

一個重要的含義是,Widget樹的子集可以被更新,而不必計算整個螢幕的佈局。這種優化就是子線型佈局的意思。

如果你熟悉瀏覽器的重繪和迴流,Flutter的方法應該已經看起來是一個大規模的優化了。

這是如何工作的呢?

核心概念是父Widget對允許子Widget的大小進行限制。基於這些約束,子Widgets將其計算出的大小傳回給父Widget。最後,父Widget決定子Widget的位置。

當然也有例外,但我們會在後面的文章中講到。

讓我們用一個簡單的圖來視覺化。

[Flutter翻譯]Flutter Anatomy - 佈局內部的第1部分

小元件樹中的約束和尺寸

藍色的父節點約束黃色的子節點,而黃色的子節點又約束其子節點。這決定了子節點的最大和最小尺寸。然後,小元件根據這些約束計算出的大小會傳回樹上。

約束是什麼樣子的?

約束是子節點允許的最大和最小高度和寬度的簡單組合。我們將在另一篇文章中更詳細地研究約束)。

[Flutter翻譯]Flutter Anatomy - 佈局內部的第1部分

限制條件

假設上面樹中的藍色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 Anatomy - 佈局內部的第1部分

簡單Flutter佈局

要想了解更多的情況,我們來看看渲染樹。


渲染樹(快速繞行)

等等,這個 "渲染樹 "是什麼東西?這個'渲染樹'是什麼東西?我們先繞道來了解一下這是什麼。

在Flutter中,渲染樹是計算Widget樹佈局的結果。換句話說,渲染樹包含了描述如何繪製一個Widget的低階UI物件。在本文中,我們感興趣的是,每個UI物件(稱為RenderObject)都有一個幾何體--即將在螢幕上繪製的東西的大小和位置。


讓我們再來看看我們簡單UI的渲染樹。

[Flutter翻譯]Flutter Anatomy - 佈局內部的第1部分

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設定的首選寬度和高度的記錄。

這裡是一個簡單的視覺化的情況。

[Flutter翻譯]Flutter Anatomy - 佈局內部的第1部分

簡單佈局父約束

那又怎樣?

這種方法的簡單性掩蓋了它的重要性。讓我們來研究一些重要的意義。

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>
複製程式碼

這種佈局看起來如下。

[Flutter翻譯]Flutter Anatomy - 佈局內部的第1部分

簡單的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)
        ])

    }

}
複製程式碼

佈局看起來如下。

[Flutter翻譯]Flutter Anatomy - 佈局內部的第1部分

iOS上的簡單佈局

安卓系統

馬上就到了

TL;DR

Flutter中的螢幕佈局方法很簡單但很強大。父Widgets對子Widgets實施約束,所以通過Widget樹的一次傳遞就足以計算佈局和定位。

本文將通過一個非常簡單的例子來展示Flutter中的關鍵佈局概念,以及這與其他UI框架有何不同。

在接下來的文章中,我們將深入研究一些佈局機制,以及如何使用這些知識來改進你的Flutter UIs。

相關文章