在Flutter框架中,Widget代表最基礎的部分,相當于是應用程序的基石.引用一句話“萬物皆是Widget”,就能看出Widget的重要性不言而喻,所以本篇著重講一下,我在學習Flutter的過程中,對Widget的一些理解和認知.有些基本知識直接摘自Flutter中文網
1.Widget的劃分
import 'package:flutter/material.dart'; //安卓類型的風格庫
void main() {
runApp(
new Center(
child: new Text(
'Hello, world!',
textDirection: TextDirection.ltr,
),
),
);
}
眼熟吧? HelloWorld,程序員標準入門,上面的例子中,將一個widget傳遞給runApp函數就能構成一個最簡單的Flutter程序.
上面的例子中,該runApp函數接收給定的widget并使其成為widget樹的根,這就類似于iOS中,指定window的rootViewController.(我是這樣理解的).
常用的Widget:
1.Text:文本格式的widget
2.Row,Column:類似web開發中的盒模型FlexBox,讓你可以在水平或者垂直方向上靈活布局.
3.Stack:取代線性布局,Stack允許子widget堆疊,你可以使用Positioned來定位他們相對于Stack的上左下右四條邊的位置.
4.Container:它可以讓你創建一個矩形元素.它可以被裝飾為一個BoxDecoration,如background、一個邊框或者一個陰影.它同樣具有margins、padding和應用于其大小的約束constraints.
1.1.StatelessWidget和StatefulWidget
statelessWidget:表示無狀態的widget,內部沒有保存狀態,UI界面一經創建不會發生改變.
statefulWidget:有狀態的widget,內部有保存狀態,當狀態發生改變,調用setState()方法,會觸發更新UI界面的操作.另外對于自定義繼承自StatefulWidget的子類,必須要重寫createState()方法.
StatelessWidget示例
import 'package:flutter/material.dart';
void main() => runApp(MyStatelessWidget(text: "Welcome"));
class MyStatelessWidget extends StatelessWidget {
final String text;
MyStatelessWidget({Key key, this.text}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Text(
text,
textDirection: TextDirection.ltr,
),
);
}
}
在上面的示例中,使用了MyStatelessWidget類的構造函數傳遞標記為final的text。這個類繼承了StatelessWidget,它包含不可變數據。
statelessWidget的build方法通常只會在以下三種情況調用:
- 將widget插入樹中時
- 當widget的父級更改其配置時
- 當它依賴的InheritedWidget發生變化時
StatefulWidget示例
class HomePage extends StatefulWidget {
HomePage({Key key}) : super(key: key);
_HomePageState createState() => _HomePageState();
}
------------------------------------------------------------------------
class _HomePageState extends State<HomePage> {
int count=0;
@override
Widget build(BuildContext context) {
return Container(
child:Column(
children: <Widget>[
Chip(
label: Text("${this.count}")
),
RaisedButton(
child: Text('增加'),
onPressed: (){
setState(() {
this.count++;
});
},
)
],
)
);
}
}
上面的示例中,虛線上面的部分,聲明了一個StatefulWidget,它需要一個createState()方法。此方法創建管理widget狀態的狀態對象_HomePageState 。
2.如何判斷使用statelessWidget還是StatefulWidget的條件?
在Flutter中,widget是有狀態的還是無狀態的 , 取決于是否他們依賴于狀態的變化。
1.如果用戶交互或數據改變導致widget改變,那么它就是有狀態的。
2.如果一個widget是最終的或不可變的,那么它就是無狀態。
首先需要判斷widget的狀態,簡單點,沒有數據驅動,信息不可變的,都是statelessWidget,Flutter本身也告訴你,優先使用StatelessWidget。
還有一個重要的條件,Flutter并沒有實現數據雙向綁定,當你使用StatefulWidget時,你在 State.setState((){}) 中寫什么代碼都不重要,它僅用來標記這個State對象需要重新Build,重新build后根據已變更的數據來創建新的Widget,但是這個build帶來的消耗是巨大的,它會觸發父類底下每個子widget的build方法。
當某個父widget下,只有部分數據發生改變時,它還是會全局重新刷新,所以這對于個別場景是不適用的。對于網上的一些學習資料中提到的方法,我認為是行之有效的。
1.盡量將需要刷新的statefulWidget放在最小的子節點
2.根布局視圖不要使用statefulWidget
3.將會調用到setState((){}) 的代碼盡可能的和要更新的視圖封裝在一個盡可能小的模塊里。
4.如果一個Widget需要reBuild,那么它的子節點、兄弟節點、兄弟節點的子節點應該盡可能少
3.Widget的生命周期
在iOS里的ViewController中,每個視圖控制器都有自己的生命周期,包含loadView,viewDidLoad等方法一樣,Flutter中的widget也有屬于自己生命周期。
方法 | 狀態 |
---|---|
initState | 插入渲染樹時調用,只調用一次 |
didChangeDependencies | state依賴的對象發生變化時調用 |
build | 構建widget時調用 |
setState | 觸發視圖重建 |
didUpdateWidget | 某個組件狀態發生改變時調用,可能會調用多次 |
deactivate | 移除渲染樹時調用 |
dispose | 組件即將銷毀時調用 |
上面的表格中,羅列了widget生命周期的各個階段。
initState:類似于iOS中ViewController的ViewDidLoad方法,
可以在此做一些初始化的設置,比如為某些狀態變量設置默認值。
didChangeDependencies: 用來專門處理 State 對象依賴關系變化,會在 initState() 調用結束后被調用。
build:構建視圖,創建并返回一個widget。
setState:當狀態數據發生變化時,通過調用這個方法通知 Flutter 更新重構 Widget。
didUpdateWidget:當 Widget 的配置發生變化時,比如,父 Widget 觸發重建(即父 Widget 的狀態發生變化時),熱重載時,系統會調用這個函數。
deactivate:當組件的可見狀態發生變化時,deactivate 函數會被調用,這時 State 會被暫時從視圖樹中移除。當頁面切換時,由于 State 對象在視圖樹中的位置發生了變化,需要先暫時移除后再重新添加,重新觸發組件構建,因為這個函數也會被調用。
dispose:從視圖樹中銷毀時調用。
4.Flutter中視圖的層級關系
如上圖所示,Flutter的視圖層級主要包含三個層級:widget,Element和RenderObject。下面我們就按照這三個層級,依次講述其中的知識點。
4.1Widget
首先是widget,按我的理解,它在這三者里應該屬于組織者的角色,通過下面widget的源碼,我們做簡單分析。
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });
final Key key;
/// 創建Widget對應的Element對象,Element對象存儲了Widget的配置信息
@protected
Element createElement();
/// 判斷是否可以更新Widget
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
widget這個class中,主要有以下兩個方法:
createElement:該方法主要是通過widget創建一個對應的Element對象。
canUpdate:該方法主要是判斷widget是否可更新,通過比較新舊widget的runtimeType和key。說到這里就不不提新舊widget之間的重要判斷依據key。
Widget的標識符:Key
Key是所有Widget都擁有的重要屬性,但它并不是默認的,構建某些復雜界面時,我們需要設置widget的key來提升性能。通過Key來比較新舊widget Tree。
4.2 Element
在這三者里,屬于協調者的角色。Element對象會同時持有widget對象和RenderObject對象,相當于是widget和RenderObject之間的橋梁。
它承載了視圖構建的上下文數據,Element與Widget是一對多的關系。由于Element是可變的,所以通過Element將Widget樹的變化做了抽象,可以將真正需要修改的部分同步到RenderObject樹中,最大程度降低對真實渲染視圖的修改,提高渲染效率,而不是銷毀整個渲染視圖樹重建。
我們在代碼中最常看見的BuildContext,其實就是抽象的Element對象。
4.3 RenderObject
RenderObject中包含了所有用來渲染實例Widget的邏輯。它負責layout、painting和hit-testing。它的生成十分耗費性能,所以我們應該盡可能的緩存它。我們與它打交道最多的時候就是在調試布局的時候。
RenderObject | 抽象的Widget |
---|---|
layout | Column和Row |
painting | Text和Image |
hit-testing | GestureDetector |
通過上面的敘述,可以總結出三者的基本關系:
視圖由一個個獨立的Element節點構成。組件最終的Layout、渲染都是通過RenderObejct來完成的,從創建到繪制渲染的大體流程是:根據Widget生成Element,然后創建相應的RenderObejct并關聯到Element.renderObject屬性上,最后再通過RenderObject來完成布局排列和繪制。
Flutter這塊總是寫寫停停的,層級關系這部分還有許多更深的知識點,以后還是要繼續總結學習,回顧舊知識,學習新知識。