Flutter開發之基礎Widgets

Widgets概念

Flutter裏有一個很是重要的核心理念:一切皆爲組件,Flutter的全部元素都是由控件構成的。 與原生開發中控件所表明的含義不一樣,Flutter中widget的概念更加普遍,它不只能夠表示UI元素,也能夠表示一些功能性的組件,如用於手勢檢測的 GestureDetector widget、用於應用主題數據傳遞的Theme等等。而原生開發中的控件一般只是指UI元素。因爲Flutter主要就是用於構建用戶界面的,因此,在大多數時候,咱們能夠簡單的認爲widget就是一個控件,沒必要糾結於概念。bash

Widget與Element

在正式介紹Flutter的Widget以前,咱們須要理清兩個概念,即什麼是Widget,什麼是Element?框架

Widget的功能是「描述一個UI元素的配置數據,它就是說,Widget其實並非表示最終繪製在設備屏幕上的顯示元素,而只是顯示元素的一個配置數據。實際上,Flutter中真正表明屏幕上顯示元素的類是Element,也就是說Widget只是描述Element的一個配置。而且一個Widget能夠對應多個Element,這是由於同一個Widget對象能夠被添加到UI樹的不一樣部分,而真正渲染時,UI樹的每個Widget節點都會對應一個Element對象。因此,理解Flutter的Widget須要理清兩個概念:less

  • Widget實際上就是Element的配置數據, Widget的功能是描述一個UI元素的一個配置數據, 而真正的UI渲染是由Element構成的。
  • 因爲Element是經過Widget生成,因此它們之間有對應關係,因此在大多數場景,咱們能夠簡單地認爲Widget就是指UI控件或UI渲染。

Widget聲明

首先,咱們先來看一下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類的申明中,咱們能夠獲得以下一些信息:函數

  • Widget類繼承自DiagnosticableTree,主要做用是提供調試信息。
  • Key: 這個key屬性相似於React/Vue中的key,主要的做用是決定是否在下一次build時複用舊的widget,決定的條件在canUpdate()方法中
  • createElement():正如前文所述一個Widget能夠對應多個Element;Flutter Framework在構建UI時,會先調用此方法生成對應節點的Element對象。此方法是Flutter Framework隱式調用的,在咱們開發過程當中基本不會調用到。
  • debugFillProperties 複寫父類的方法,主要是設置DiagnosticableTree的一些特性。
  • canUpdate()是一個靜態方法,它主要用於在Widget樹從新build時複用舊的widget。具體來講,是否使用新的Widget對象去更新舊UI樹上所對應的Element對象的配置;而且經過其源碼咱們能夠知道,只要newWidget與oldWidget的runtimeType和key同時相等時就會用newWidget去更新Element對象的配置,不然就會建立新的Element。

StatelessWidget

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

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實例。

StatelessWidget和StatefulWidget的區別

經過上面的講解,咱們能夠得出以下結論:

  • StatelessWidget是狀態不可變的widget, 初始狀態設置之後就不可再變化, 若是須要變化須要從新建立StatefulWidget,由於StatefulWidget能夠保存本身的狀態。
  • 在Flutter中經過引入State來保存狀態, 當State的狀態改變時,能從新構建本節點以及孩子的Widget樹來進行UI變化。
  • 若是須要主動改變State的狀態,須要經過setState()方法進行觸發,單純改變數據是不會引起UI改變的

Widgets的State

說到組件,就不得不提到Widgets的State。一般,一個StatefulWidget類會對應一個State類,State表示與其對應的StatefulWidget要維護的狀態,State中的保存的狀態信息有以下兩個做用:

  1. 在widget build時能夠被同步讀取。
  2. 在widget生命週期中能夠被改變,當State被改變時,能夠手動調用其setState()方法通知Flutter framework狀態發生改變,Flutter framework在收到消息後,會從新調用其build方法從新構建widget樹,從而達到更新UI的目的。

State有兩個經常使用屬性:widget和context。

  • widget:它表示與該State實例關聯的widget實例,由Flutter framework動態設置。注意,這種關聯並不是永久的,由於在應用聲明週期中,UI樹上的某一個節點的widget實例在從新構建時可能會變化,但State實例只會在第一次插入到樹中時被建立,當在從新構建時,若是widget被修改了,Flutter framework會動態設置State.widget爲新的widget實例。
  • context,它是BuildContext類的一個實例,表示構建widget的上下文,它是操做widget在樹中位置的一個句柄,它包含了一些查找、遍歷當前Widget樹的一些方法。每個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、initState、didChangeDependencies和build。
  • 運行中:在渲染樹中存在,這一階段涉及的生命週期函數主要有didUpdateWidget和build。
  • 銷燬:從渲染樹中移除,此階段涉及的生命週期函數主要有deactivate和dispose。

初始化階段

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提供了不少功能豐富的基礎組件,常見的有以下一些:

  • 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組件

衆所周知,Material是Android應用默認的視覺風格,Cupertino則是iOS應用的默認視覺風格,爲了實現兩種不一樣的視覺風格,Flutter 在基礎組件庫之上Flutter又提供了一套Material風格和一套Cupertino風格的組件庫,以知足兩種不一樣設計風格的開發須要。

Material應用程序以MaterialApp 組件開始, 該組件在應用程序的根部建立了一些必要的組件,好比Theme組件,它用於配置應用的主題。 是否使用MaterialApp徹底是可選的,可是使用它是一個很好的作法。在以前的示例中,咱們已經使用過多個Material 組件了,如:Scaffold、AppBar、FlatButton等。

要使用Material 組件,須要先引入它:

import 'package:flutter/material.dart';
複製代碼

Cupertino組件

Flutter也提供了一套豐富的Cupertino風格的組件,儘管目前尚未Material 組件那麼豐富,可是它仍在不斷的完善中。目前,Flutter提供的Cupertino組件主要有 CupertinoTabBar、 CupertinoActivityIndicator、CupertinoPageScaffold、 CupertinoTabScaffold、 CupertinoTabView 等 。 關於Cupertino組件,你們能夠參考官方的介紹:Cupertino (iOS風格) Widgets

相關文章
相關標籤/搜索