0%

Flutter 筆記(ㄧ) - 基礎認識

前言

本篇記錄了 Flutter UI Framework 最基本的結構來認識它的渲染機制還有一些基本 Widget 的理解,以及 Flutter 所採用的 UI as Code 的開發方式是如何進行的。系列文章主要是 Stephen Grider 教學的筆記心得。

基本概念

  • 在Flutter裡面, 幾乎所有東西都是小部件, 包括 alignment, padding
  • Stateless widgets 是不可變的, 也就是說所有的屬性都不能改變 - 所有的數值都是final
  • Stateful widgets 維護在 widget 的生命週期中可能會改變的狀態(使用者交互, 更新資料等等)

    • 實作一個 Stateful widget 需要至少兩個 class :

      1. SatefulWidget class 創造一個
      2. State class 的實體
    • SatefulWidget class 本身是不可變的(和 Stateles s一樣), 但是 State class 可以在小部件的生命週期中持續存在

    • widget 的 state 會被緩衝(不被重新創建), 當 widget 被重新渲染會創建新的 widget 但他仍然使用被緩衝 state 的資料

Stateflul Widget v.s. Stateless Widget

  • Stateless Widget 雖說裡面的屬性和狀態不能被改變, 但資料可以在外面被改變傳進來, 來重新渲染該widget

  • Stateful Widget 就更好理解

    1. 可以透過外面傳進來的資料去改變
    2. 可以透過本地的 state 來改變

debugging UI

檢查整個app UI的介面 :

  1. 添加依賴

    • import ‘package:flutter/rendering.dart’;

  2. 在main裡面呼叫UI debugger

    • debugPaintSizeEnabled = true;


其他debugger

1
2
3
4
// 查看text的基準線(少用)
debugPaintBaselinesEnabled = true;
// 查看在螢幕上的觸控
debugPaintPointersEnabled = true;

ListView

定義 : 最常被用來滑動的 widget. 他展示了她一個接一個的子 widget 在滑動的方向上 (如果是橫向展示子 widget 被要求填滿 ListView)

以下有三種不同的選項來建構ListView :
ListView doc

Flutter 系統簡介

概述

  • Flutter 系統簡介

    • Flutter 框架的本身是由許多層的抽象組成堆疊的
  • 可以大概簡化成

    • Material Cupertino & widget 是最上層也最常接觸到的一層
      • e.g. 像是 Scaffold & FloatingActionButton … 是屬於 Material 庫 ; Column & GestureDetector … 是屬於 Widgets 庫
    • rendering layer 主要負責簡化外觀渲染的工作
    • dart:ui 就是基本負責與更底層的 Flutter engine 溝通

小結
越上層越容易控制使用,但越下層可以有更多複雜度的精細掌握


dart:ui


功能
該 lib 提供最底層的服務給 Flutter 框架用來引導整個應用程式,像是提供類別來驅動輸入 圖像文字 外觀 還有渲染系統


根據上述,有了 dart:ui 就可以開始開發了,因為它提供最基本的 (Canvas, Paint TextBox) 等物件

  • 缺點是難以管理
    1. 需要設計繪畫的所有座標
    2. 管理好每一張 frame
  • 除了,小型專案適用

Rendering Library


The Flutter rendering tree

  • 定義

    • 一個在 render tree 裡的物件
  • 功能

    • RenderObject hierarchy 是被 Flutter 的 Widgets lib 來使用的,用來實現其佈局還有後端的渲染

  • 根據上述,該層完成所有 Flutter 框架中負擔最重的部分(包含所有計算繪畫追蹤 frame 等部分)

  • RenderObject 想像成車子的引擎,他們負責實際渲染到 app 畫面的工作

  • The tree is composed out of RenderObjects

  • 實例化一個 RenderObject 是非常昂貴且耗時的,所以會盡量的 cache 以避免資源的消耗 (所以 flutter 為何如此高效)

Widgets


Widgets 和 RenderObject 的關係

這大概是我們最可以輕易使用的 UI component,每一個可以在 widget lib 裡面找到的 widget 都會有一個相對應的 RenderObject 在 rendering lib 裏來負責最終的渲染


所有的 Widgets 可以分成這三類

  1. Layout e.g. column row

  2. Painting e.g. Text image

  3. Hit-Testing e.g. GestureDetector 用來辨識所有的觸控

所有複雜的 widget 都是由以上這三種類別 wrap 而成

  • e.g. 製作一個 Button 可以由 GestureDetector 再 包裝 wrap 一個 Container 而成

  • 這種方式在 OO 叫做 composition 而不是 inheritance

Material & Cupertino library


Widgets lib all the time ?
其實除了自己組合所有複雜的 widget 也可以使用已經實作好的 Android & iOS 常用的設計元件


建構 Flutter UI 的詳細流程 (Widget, Element, RenderObject)

以此程式為例

  • 由外往裡包的 widget 分別是 SimpleApp => SimpleContainer => SimpleText

整個 building (呼叫 runApp()) 的過程大概可以分成三個步驟,每一個步驟都有自己的 tree

  1. Flutter 會建立一個包含這三個 widget 的 widget tree
  2. Flutter 會迭代整個 widget tree 並且創建第二個樹 - element tree。在迭代的過程中,每一個 widget 都會呼叫 createElement() 來實例化相對應得 Element 物件,並且將它塞到 element tree 裡面
  3. 以此類推,第三棵樹 - RenderTree,透過 Element 呼叫 createRenderObject() 產生 RenderObject 並且塞到 RenderTree

圖示

Widget? Element? RenderObject?

每一個 Element 都會有一個 reference 參考到與其對應的 Widget 和 RenderObject


為什麼需要使用 Element 的前情提要:

RenderObject 包含了渲染與之相對應 Widget 的所有邏輯,因此實例化是相當昂貴的!所以盡可能地將它保留在記憶體中不要回收持續地利用


Elements
特性

  • 就像一個代理人一樣連接兩端(reference),在不可變的 Widget tree 和可變的 RenderObject tree 之間 (immutable & mutable)

功能

  • 用來比較同個位置下的 Widget 和 RenderObject

為什麼要三棵樹不是一棵樹?

效能!!!

當每一次 widget tree 有改變的時候 (可能是某層換不同型態的 widget 或只是 widget 設定的更新), Flutter 用 element tree 來比較新建立的 Widget tree 和已經存在建立的 RenderObject tree

  1. 當該層的Widget和之前的 類別 type 是一樣的,就不需要重新建立相對應昂貴的 RenderObject,而是只需要更新 widget 有變動的設定給現有的 RenderObject 就行了
  2. 反之,該層的 Widget 和之前的類別是不一樣的,就把舊的 widget, element, renderObject 刪掉 (包括其子樹),然後重新創建相對應的 elemnt 和 renderObject

以此類推,迭代這三棵樹裡的所有物件

Widget 是輕量的所以可以被輕易的建造和消除所以適合用來描述目前的狀態和設定,反之 RenderObject 是重量級的所以應審慎的創建和回收

The whole app acts like a huge RecyclerView


BuildContext 和 Element
作為參數傳遞在 build(BuildContext context) 裡面的 BuildContext ,事實上就是包裝 Element 的物件,因此每一個 widget 都有獨一無二的 BuildContext



Conclusion
我們可以得知 Flutter 如此高效是因為有這三棵樹,分別是

  1. widget tree 用來代表要使用的 UI 物件是什麼,再加上記錄一些簡單的狀態設定
  2. element tree 為兩棵樹間的代理人儲存相對應的每一層 widget 和 renderObject 的引用,用來比較彼此的相同或不同
  3. rederobject tree 為真實渲染到螢幕上的龐大昂貴的物件