Flutter裏有一個很是重要的核心理念:一切皆爲組件,Flutter的全部元素都是由控件構成的。 與原生開發中控件所表明的含義不一樣,Flutter中widget的概念更加普遍,它不只能夠表示UI元素,也能夠表示一些功能性的組件,如用於手勢檢測的 GestureDetector widget、用於應用主題數據傳遞的Theme等等。而原生開發中的控件一般只是指UI元素。因爲Flutter主要就是用於構建用戶界面的,因此,在大多數時候,咱們能夠簡單的認爲widget就是一個控件,沒必要糾結於概念。bash
在正式介紹Flutter的Widget以前,咱們須要理清兩個概念,即什麼是Widget,什麼是Element?框架
Widget的功能是「描述一個UI元素的配置數據,它就是說,Widget其實並非表示最終繪製在設備屏幕上的顯示元素,而只是顯示元素的一個配置數據。實際上,Flutter中真正表明屏幕上顯示元素的類是Element,也就是說Widget只是描述Element的一個配置。而且一個Widget能夠對應多個Element,這是由於同一個Widget對象能夠被添加到UI樹的不一樣部分,而真正渲染時,UI樹的每個Widget節點都會對應一個Element對象。因此,理解Flutter的Widget須要理清兩個概念:less
首先,咱們先來看一下Widget類的聲明:ide
@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;
}
}
複製代碼
從這個Widget類的申明中,咱們能夠獲得以下一些信息:函數
StatelessWidget是Flutter提供的一個不須要狀態更改的widget ,它沒有內部狀態管理功能。StatelessWidget相對比較簡單,它繼承自Widget類,重寫了createElement()方法。佈局
@override
StatelessElement createElement() => new StatelessElement(this);
複製代碼
StatelessElement 間接繼承自Element類,與StatelessWidget相對應。StatelessWidget一般被用於不須要維護狀態的場景,在build方法中經過嵌套其它Widget來構建UI,在構建過程當中會遞歸的構建其嵌套的Widget。例如:ui
class Echo extends StatelessWidget {
const Echo({
Key key,
@required this.text,
this.backgroundColor:Colors.grey,
}):super(key:key);
final String text;
final Color backgroundColor;
@override
Widget build(BuildContext context) {
return Center(
child: Container(
color: backgroundColor,
child: Text(text),
),
);
}
}
複製代碼
按照慣例,widget的構造函數參數應使用命名參數,命名參數中的必要參數要添加@required標註,這樣有利於靜態代碼分析器進行檢查。另外,在繼承widget時,第一個參數一般應該是Key,另外,若是Widget須要接收子Widget,那麼child或children參數一般應被放在參數列表的最後。 而後,咱們能夠經過以下方式來使用Echo widget。this
Widget build(BuildContext context) {
return Echo(text: "hello world");
}
複製代碼
運行後效果以下圖所示: spa
StatefulWidget 是一個可變狀態的widget。 使用setState方法管理StatefulWidget的狀態的改變。調用setState告訴Flutter框架,某個狀態發生了變化,Flutter會從新運行build方法,以便應用程序能夠應用最新狀態。debug
和StatelessWidget同樣,StatefulWidget也是繼承自Widget類,並重寫了createElement()方法,不一樣的是返回的Element 對象並不相同;另外StatefulWidget類中添加了一個新的接口createState()。
下面是StatefulWidget的類定義,以下所示:
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實例。
經過上面的講解,咱們能夠得出以下結論:
說到組件,就不得不提到Widgets的State。一般,一個StatefulWidget類會對應一個State類,State表示與其對應的StatefulWidget要維護的狀態,State中的保存的狀態信息有以下兩個做用:
State有兩個經常使用屬性:widget和context。
和原平生臺的控件同樣,State也有本身的生命週期。爲了加深讀者對State生命週期的印象,本節咱們經過一個實例來演示一下State的生命週期。在接下來的示例中,咱們實現一個計數器widget,點擊它可使計數器加1,因爲要保存計數器的數值狀態,因此咱們應繼承StatefulWidget,代碼以下:
class CounterWidget extends StatefulWidget {
const CounterWidget({
Key key,
this.initValue: 0
});
final int initValue;
@override
_CounterWidgetState createState() => new _CounterWidgetState();
}
複製代碼
CounterWidget接收一個initValue整型參數,它表示計數器的初始值。接下來,咱們看一下_CounterWidgetState的實現:
class _CounterWidgetState extends State<CounterWidget> {
int _counter;
@override
void initState() {
super.initState();
//初始化狀態
_counter=widget.initValue;
print("initState");
}
@override
Widget build(BuildContext context) {
print("build");
return Scaffold(
body: Center(
child: FlatButton(
child: Text('$_counter'),
//點擊後計數器自增
onPressed:()=>setState(()=> ++_counter,
),
),
),
);
}
@override
void didUpdateWidget(CounterWidget oldWidget) {
super.didUpdateWidget(oldWidget);
print("didUpdateWidget");
}
@override
void deactivate() {
super.deactivate();
print("deactive");
}
@override
void dispose() {
super.dispose();
print("dispose");
}
@override
void reassemble() {
super.reassemble();
print("reassemble");
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("didChangeDependencies");
}
}
複製代碼
接下來,咱們建立一個新路由,在新路由中,咱們只顯示一個CounterWidget。
Widget build(BuildContext context) {
return CounterWidget();
}
複製代碼
而後,運行應用並打開該路由頁面,在新路由頁打開後,屏幕中央就會出現一個數字0,而且控制檯日誌輸出以下:
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。 接下來,咱們在widget樹中移除CounterWidget,並將路由build方法改成:
Widget build(BuildContext context) {
//移除計數器
//return CounterWidget();
//隨便返回一個Text()
return Text("xxx");
}
複製代碼
而後執行熱重載操做,日誌以下:
I/flutter ( 5436): reassemble
I/flutter ( 5436): deactive
I/flutter ( 5436): dispose
複製代碼
能夠看到,在CounterWidget從widget樹中移除時,deactive和dispose會依次被調用。
經過上面的示例,咱們將StatefulWidget生命週期整理以下圖:
StatefulWidget的生命週期大體可分爲三個階段:createState:createState必須且僅執行一次,它用來建立state,當建立StatefulWidget時,該放方法就會被執行。
initState:在建立StatefulWidget後,initState是第一個被調用的方法,同createState同樣只被調用一次,此時widget的被添加至渲染樹,mount的值會變爲true,但並無渲染。咱們能夠在該方法內作一些初始化操做。
didChangeDependencies:當widget第一次被建立時,didChangeDependencies緊跟着initState函數以後調用,在widget刷新時,該方法不會被調用。它會在「依賴」發生變化時被Flutter Framework調用,這個依賴是指widget是否使用父widget中InheritedWidget的數據。也便是隻有在widget依賴的InheritedWidget發生變化以後,didChangeDependencies纔會調用。 這種機制可使子組件在所依賴的InheritedWidget變化時來更新自身!好比當主題、locale(語言)等發生變化時,依賴其的子widget的didChangeDependencies方法將會被調用。
build:build會在widget第一次建立時緊跟着didChangeDependencies方法以後和UI從新渲染時被調用。build只作widget的建立操做,若是在build裏作其餘操做,會影響UI的渲染效果。
StatefulWidget運行中只會調用兩個函數,即didUpdateWidget和build。 didUpdateWidget:當組件的狀態改變的時候就會調用didUpdateWidget,好比調用了setState。
deactivate:當State對象從樹中被移除時,會調用此回調函數,這標誌着 StatefulWidget將要執行銷燬操做。頁面切換時,也會調用它,由於此時State在視圖樹中的位置發生了變化可是State不會被銷燬,而是從新插入到渲染樹中。 重寫的時候必需要調用 super.deactivate()。
dispose:從渲染樹中移除時調用,State會永久的從渲染樹中移除,和initState正好相反mount值變味false。這時候就能夠在dispose裏作一些取消監聽操做。
爲了方便讀者理解,咱們看一下StatefulWidget的生命週期函數調用狀況。
生命週期 | 調用次數 | 調用時間 |
---|---|---|
createState | 1 | 組件建立時 |
initState | 1 | 組件建立時 |
didChangeDependencies | n | 組件建立或狀態發生變化 |
build | n | 組件建立或UI從新渲染 |
didUpdateWidget | n | 組件建立或UI從新渲染 |
deactivate | n | State對象將要移除時 |
dispose | 1 | state對象被銷燬 |
Flutter SDK提供了一套豐富、強大的基礎組件,在基礎組件庫之上Flutter又提供了一套Material風格(Android默認的視覺風格)和一套Cupertino風格(iOS視覺風格)的組件庫。使用前只須要導入便可使用:
import 'package:flutter/widgets.dart';
複製代碼
Flutter SDK提供了不少功能豐富的基礎組件,常見的有以下一些:
衆所周知,Material是Android應用默認的視覺風格,Cupertino則是iOS應用的默認視覺風格,爲了實現兩種不一樣的視覺風格,Flutter 在基礎組件庫之上Flutter又提供了一套Material風格和一套Cupertino風格的組件庫,以知足兩種不一樣設計風格的開發須要。
Material應用程序以MaterialApp 組件開始, 該組件在應用程序的根部建立了一些必要的組件,好比Theme組件,它用於配置應用的主題。 是否使用MaterialApp徹底是可選的,可是使用它是一個很好的作法。在以前的示例中,咱們已經使用過多個Material 組件了,如:Scaffold、AppBar、FlatButton等。
要使用Material 組件,須要先引入它:
import 'package:flutter/material.dart';
複製代碼
Flutter也提供了一套豐富的Cupertino風格的組件,儘管目前尚未Material 組件那麼豐富,可是它仍在不斷的完善中。目前,Flutter提供的Cupertino組件主要有 CupertinoTabBar、 CupertinoActivityIndicator、CupertinoPageScaffold、 CupertinoTabScaffold、 CupertinoTabView 等 。 關於Cupertino組件,你們能夠參考官方的介紹:Cupertino (iOS風格) Widgets