Flutter UI系統
它提供了一套Dart API,然後在底層通過OpenGL這種跨平臺的繪製庫(內部會呼叫作業系統API)實現了一套程式碼跨多端。由於Dart API也是呼叫作業系統API,所以它的效能接近原生。
Flutter中,一切都是Widget,當UI要發生變化時,我們不去直接修改DOM,而是通過更新狀態,讓Flutter UI系統來根據新的狀態來重新構建UI。
Widget與Element
Widget只是UI元素的一個配置資料,並且一個Widget可以對應多個Element
@immutable
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });
final Key key;
@protected
Element createElement();
@override
String toStringShort() {
return key == null ? '$runtimeType' : '$runtimeType-$key';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
}
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
複製程式碼
canUpdate(...)
是一個靜態方法,它主要用於在Widget樹重新build
時複用舊的widget,其實具體來說,應該是:是否用新的Widget物件去更新舊UI樹上所對應的Element
物件的配置;通過其原始碼我們可以看到,只要newWidget
與oldWidget
的runtimeType
和key
同時相等時就會用newWidget
去更新Element
物件的配置,否則就會建立新的Element
。
另外Widget
類本身是一個抽象類,其中最核心的就是定義了createElement()
介面,在Flutter開發中,我們一般都不用直接繼承Widget
類來實現一個新元件,相反,我們通常會通過繼承StatelessWidget
或StatefulWidget
來間接繼承Widget
類來實現。StatelessWidget
和StatefulWidget
都是直接繼承自Widget
類,而這兩個類也正是Flutter中非常重要的兩個抽象類,它們引入了兩種Widget模型,接下來我們將重點介紹一下這兩個類。
3.1.4 StatelessWidget
StatelessWidget
用於不需要維護狀態的場景,它通常在build
方法中通過巢狀其它Widget來構建UI,在構建過程中會遞迴的構建其巢狀的Widget。我們看一個簡單的例子:
Context
build
方法有一個context
引數,它是BuildContext
類的一個例項,表示當前widget在widget樹中的上下文,每一個widget都會對應一個context物件(因為每一個widget都是widget樹上的一個節點)。實際上,context
是當前widget在widget樹中位置中執行”相關操作“的一個控制程式碼,比如它提供了從當前widget開始向上遍歷widget樹以及按照widget型別查詢父級widget的方法。下面是在子樹中獲取父級widget的一個示例:
lass ContextRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Context測試"),
),
body: Container(
child: Builder(builder: (context) {
// 在Widget樹中向上查詢最近的父級`Scaffold` widget
Scaffold scaffold = context.findAncestorWidgetOfExactType<Scaffold>();
// 直接返回 AppBar的title, 此處實際上是Text("Context測試")
return (scaffold.appBar as AppBar).title;
}),
),
);
}
}
複製程式碼
3.1.5 StatefulWidget
和StatelessWidget
一樣,StatefulWidget
也是繼承自Widget
類,並重寫了createElement()
方法,不同的是返回的Element
物件並不相同;另外StatefulWidget
類中新增了一個新的介面createState()
。
abstract class StatefulWidget extends Widget {
const StatefulWidget({ Key key }) : super(key: key);
@override
StatefulElement createElement() => new StatefulElement(this);
@protected
State createState();
}
複製程式碼
StatefulElement
間接繼承自Element
類,與StatefulWidget相對應(作為其配置資料)。StatefulElement
中可能會多次呼叫createState()
來建立狀態(State)物件。createState()
用於建立和Stateful widget相關的狀態,它在Stateful widget的生命週期中可能會被多次呼叫。例如,當一個Stateful widget同時插入到widget樹的多個位置時,Flutter framework就會呼叫該方法為每一個位置生成一個獨立的State例項,其實,本質上就是一個StatefulElement
對應一個State例項。
3.1.6 State
在widget生命週期中可以被改變,當State被改變時,可以手動呼叫其setState()
方法通知Flutter framework狀態發生改變,Flutter framework在收到訊息後,會重新呼叫其build
方法重新構建widget樹,從而達到更新UI的目的。
State生命週期
void initState()
Widget build(BuildContext context)
void didUpdateWidget(CounterWidget oldWidget)
void deactivate()
void dispose()
void didChangeDependencies()
複製程式碼
我們執行應用並開啟該路由頁面,在新路由頁開啟後
I/flutter ( 5436): initState
I/flutter ( 5436): didChangeDependencies
I/flutter ( 5436): build
複製程式碼
可以看到,在StatefulWidget插入到Widget樹時首先initState
方法會被呼叫。
然後我們點選⚡️按鈕熱過載,控制檯輸出日誌如下:
I/flutter ( 5436): reassemble
I/flutter ( 5436): didUpdateWidget
I/flutter ( 5436): build
複製程式碼
可以看到此時initState
和didChangeDependencies
都沒有被呼叫,而此時didUpdateWidget
被呼叫。
initState
:當Widget第一次插入到Widget樹時會被呼叫,對於每一個State物件,Flutter framework只會呼叫一次該回撥,所以,通常在該回撥中做一些一次性的操作,如狀態初始化、訂閱子樹的事件通知等。不能在該回撥中呼叫BuildContext.dependOnInheritedWidgetOfExactType
(該方法用於在Widget樹上獲取離當前widget最近的一個父級InheritFromWidget
,關於InheritedWidget
我們將在後面章節介紹),原因是在初始化完成後,Widget樹中的InheritFromWidget
也可能會發生變化,所以正確的做法應該在在build()
方法或didChangeDependencies()
中呼叫它。didChangeDependencies()
:當State物件的依賴發生變化時會被呼叫;例如:在之前build()
中包含了一個InheritedWidget
,然後在之後的build()
中InheritedWidget
發生了變化,那麼此時InheritedWidget
的子widget的didChangeDependencies()
回撥都會被呼叫。典型的場景是當系統語言Locale或應用主題改變時,Flutter framework會通知widget呼叫此回撥。build()
:此回撥讀者現在應該已經相當熟悉了,它主要是用於構建Widget子樹的,會在如下場景被呼叫:- 在呼叫
initState()
之後。 - 在呼叫
didUpdateWidget()
之後。 - 在呼叫
setState()
之後。 - 在呼叫
didChangeDependencies()
之後。 - 在State物件從樹中一個位置移除後(會呼叫deactivate)又重新插入到樹的其它位置之後。
- 在呼叫
reassemble()
:此回撥是專門為了開發除錯而提供的,在熱過載(hot reload)時會被呼叫,此回撥在Release模式下永遠不會被呼叫。didUpdateWidget()
:在widget重新構建時,Flutter framework會呼叫Widget.canUpdate
來檢測Widget樹中同一位置的新舊節點,然後決定是否需要更新,如果Widget.canUpdate
返回true
則會呼叫此回撥。正如之前所述,Widget.canUpdate
會在新舊widget的key和runtimeType同時相等時會返回true,也就是說在在新舊widget的key和runtimeType同時相等時didUpdateWidget()
就會被呼叫。deactivate()
:當State物件從樹中被移除時,會呼叫此回撥。在一些場景下,Flutter framework會將State物件重新插到樹中,如包含此State物件的子樹在樹的一個位置移動到另一個位置時(可以通過GlobalKey來實現)。如果移除後沒有重新插入到樹中則緊接著會呼叫dispose()
方法。dispose()
:當State物件從樹中被永久移除時呼叫;通常在此回撥中釋放資源。
StatefulWidget生命週期如圖3-2所示:
3.1.8 Flutter SDK內建元件庫介紹
Flutter提供了一套豐富、強大的基礎元件,在基礎元件庫之上Flutter又提供了一套Material風格(Android預設的視覺風格)和一套Cupertino風格(iOS視覺風格)的元件庫。要使用基礎元件庫,需要先匯入:
import 'package:flutter/widgets.dart';
複製程式碼
下面我們介紹一下常用的元件。
基礎元件
Text
:該元件可讓您建立一個帶格式的文字。Row
、Column
: 這些具有彈性空間的佈局類Widget可讓您在水平(Row)和垂直(Column)方向上建立靈活的佈局。其設計是基於Web開發中的Flexbox佈局模型。Stack
: 取代線性佈局 (譯者語:和Android中的FrameLayout
相似),Stack
允許子 widget 堆疊, 你可以使用Positioned
來定位他們相對於Stack
的上下左右四條邊的位置。Stacks是基於Web開發中的絕對定位(absolute positioning )佈局模型設計的。Container
:Container
可讓您建立矩形視覺元素。container 可以裝飾一個BoxDecoration
, 如 background、一個邊框、或者一個陰影。Container
也可以具有邊距(margins)、填充(padding)和應用於其大小的約束(constraints)。另外,Container
可以使用矩陣在三維空間中對其進行變換。
Material元件
Flutter提供了一套豐富的Material元件,它可以幫助我們構建遵循Material Design設計規範的應用程式。Material應用程式以MaterialApp
元件開始, 該元件在應用程式的根部建立了一些必要的元件,比如Theme
元件,它用於配置應用的主題。 是否使用MaterialApp
完全是可選的,但是使用它是一個很好的做法。在之前的示例中,我們已經使用過多個Material 元件了,如:Scaffold
、AppBar
、FlatButton
等。要使用Material 元件,需要先引入它:
import 'package:flutter/material.dart';
複製程式碼
Cupertino元件
Flutter也提供了一套豐富的Cupertino風格的元件,儘管目前還沒有Material 元件那麼豐富,但是它仍在不斷的完善中。值得一提的是在Material 元件庫中有一些元件可以根據實際執行平臺來切換表現風格,比如MaterialPageRoute
,在路由切換時,如果是Android系統,它將會使用Android系統預設的頁面切換動畫(從底向上);如果是iOS系統,它會使用iOS系統預設的頁面切換動畫(從右向左)。由於在前面的示例中還沒有Cupertino元件的示例,下面我們實現一個簡單的Cupertino元件風格的頁面:
//匯入cupertino widget庫
import 'package:flutter/cupertino.dart';
class CupertinoTestRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text("Cupertino Demo"),
),
child: Center(
child: CupertinoButton(
color: CupertinoColors.activeBlue,
child: Text("Press"),
onPressed: () {}
),
),
);
}
}
複製程式碼
其他
-
深入理解StatelessWidget
-
abstract class StatelessWidget extends Widget Containing class: StatelessWidget
A widget that does not require mutable state. A stateless widget is a widget that describes part of the user interface by building a constellation of other widgets that describe the user interface more concretely. The building process continues recursively until the description of the user interface is fully concrete (e.g., consists entirely of RenderObjectWidgets, which describe concrete RenderObjects). www.youtube.com/watch?v=wE7khGHVkYY Stateless widget are useful when the part of the user interface you are describing does not depend on anything other than the configuration information in the object itself and the BuildContext in which the widget is inflated. For compositions that can change dynamically, e.g. due to having an internal clock-driven state, or depending on some system state, consider using StatefulWidget. Performance considerations The build method of a stateless widget is typically only called in three situations: the first time the widget is inserted in the tree, when the widget's parent changes its configuration, and when an InheritedWidget it depends on changes. If a widget's parent will regularly change the widget's configuration, or if it depends on inherited widgets that frequently change, then it is important to optimize the performance of the build method to maintain a fluid rendering performance. There are several techniques one can use to minimize the impact of rebuilding a stateless widget: Minimize the number of nodes transitively created by the build method and any widgets it creates. For example, instead of an elaborate arrangement of Rows, Columns, Paddings, and SizedBoxes to position a single child in a particularly fancy manner, consider using just an Align or a CustomSingleChildLayout. Instead of an intricate layering of multiple Containers and with Decorations to draw just the right graphical effect, consider a single CustomPaint widget. Useconst
widgets where possible, and provide aconst
constructor for the widget so that users of the widget can also do so. Consider refactoring the stateless widget into a stateful widget so that it can use some of the techniques described at StatefulWidget, such as caching common parts of subtrees and using GlobalKeys when changing the tree structure. If the widget is likely to get rebuilt frequently due to the use of InheritedWidgets, consider refactoring the stateless widget into multiple widgets, with the parts of the tree that change being pushed to the leaves. For example instead of building a tree with four widgets, the inner-most widget depending on the Theme, consider factoring out the part of the build function that builds the inner-most widget into its own widget, so that only the inner-most widget needs to be rebuilt when the theme changes. {@tool snippet} The following is a skeleton of a stateless widget subclass called GreenFrog. Normally, widgets have more constructor arguments, each of which corresponds to a final property. class GreenFrog extends StatelessWidget { const GreenFrog({ Key key }) : super(key: key);@override Widget build(BuildContext context) { return Container(color: const Color(0xFF2DBD3A)); } } {@end-tool} {@tool snippet} This next example shows the more generic widget Frog which can be given a color and a child: class Frog extends StatelessWidget { const Frog({ Key key, this.color = const Color(0xFF2DBD3A), this.child, }) : super(key: key);
final Color color; final Widget child;
@override Widget build(BuildContext context) { return Container(color: color, child: child); } } {@end-tool} By convention, widget constructors only use named arguments. Named arguments can be marked as required using @required. Also by convention, the first argument is key, and the last argument is child, children, or the equivalent. See also: StatefulWidget and State, for widgets that can build differently several times over their lifetime. InheritedWidget, for widgets that introduce ambient state that can be read by descendant widgets.
- 不需要可變狀態的小部件。
- 非常具體描述某一個事情的時候使用
- 參考: www.youtube.com/watch?v=wE7khGHVkYY
- 當您所描述的使用者介面部分不依賴於物件本身中的配置資訊和小部件膨脹的構建上下文之外的任何東西時,無狀態小部件非常有用。對於可以動態更改的組合,例如,由於具有內部時鐘驅動狀態,或取決於某些系統狀態,請考慮使用StatefulWidget。
- 效能注意事項
- 無狀態小部件的構建方法通常只在三種情況下呼叫:
- 第一次將小部件插入樹中時,
- 當小部件的父級更改其配置時,
- 以及當繼承的小部件依賴於更改時。
- 如果小部件的父級將定期更改小部件的配置,或者如果它依賴於頻繁更改的繼承小部件,那麼優化構建方法的效能以保持流體渲染效能非常重要。
- 最小化由build方法及其建立的任何小部件可傳遞地建立的節點數。例如,為了以一種特別奇特的方式定位單個子級,而不是對行、列、填充和大小框進行精心安排,而是考慮使用Align或CustomSingleChildLayout。考慮一個單獨的CustomPaint小部件,而不是用多個容器和裝飾來繪製正確的圖形效果的複雜分層。
- 儘可能使用const widgets,併為小部件提供一個const建構函式,這樣小部件的使用者也可以這樣做。
- 考慮將無狀態小部件重構為有狀態小部件,以便它可以使用StatefulWidget中描述的一些技術,例如快取子樹的公共部分,以及在更改樹結構時使用globalkey。
- 如果小部件可能由於使用繼承的小部件而頻繁地重新構建,那麼考慮將無狀態的小部件重構為多個小部件,並將樹中更改的部分推到葉子上。例如,與其構建一個包含四個小部件(最內部的小部件取決於主題)的樹,不如考慮將構建最內部小部件的構建函式部分分解到自己的小部件中,這樣當主題發生變化時,只需要重新構建最內部的小部件。
-
StatelessWidget
-
InheritedWidget 資料共享元件
-
提供了一種資料在widget樹中從上到下傳遞、共享的方式,比如我們在應用的根widget中通過
InheritedWidget
共享了一個資料,那麼我們便可以在任意子widget中來獲取該共享的資料!如Flutter SDK中正是通過InheritedWidget來共享應用主題(Theme
)和Locale (當前語言環境)資訊的。 -
特性 didChangeDependencies
-
state中didChangeDependencies的回撥方法,再依賴發生變化的時候會進行被呼叫;依賴的行為只指子的widget是否使用了父widget中InheritedWidget 的資料;如果使用了就代表依賴了,依賴其的子widget的
didChangeDependencies
方法將會被呼叫。 -
context.getElementForInheritedWidgetOfExactType<ShareDataWidget>().widget 複製程式碼
-
如果是通過 'getElementForInheritedWidgetOfExactType'
-
什麼時候使用
- 子元件共享獲取全域性共享資料時候,比如主題變化
-