Widget總結

Flutter中Widget,State和BuildContext的概念是每個Flutter開發(fā)人員需要完全理解的最重要概念之一。這里先講解一下Widget以及Widget。三者之間的關系會在最后一篇總結一下。

Widget詳解

Widget類在Flutter中是非常重要的,繼承自Widget類的有PreferredSizeWidget、ProxyWidget、RenderObjectWidget、StatefulWidget、StatelessWidget。我們日常使用的絕大部分widget都是繼承自Widget類,查看Widget類源碼,內部實現非常簡單,構造函數如下:

const Widget({ this.key });
final Key key;

在flutter中構建APP是由widget樹構建起來的,所以這個key的作用是用來控制在widget樹中替換widget的時候使用的。其中Key類是Widget、Element以及SemanticsNode的唯一標識符,繼承自Key的還有LocalKey以及GlobalKey。詳細可以去framework.dart文件查看相關源碼及說明。

Widget分類

在Flutter中,我們平時自定義的widget,一般都是繼承自StatefulWidget或StatelessWidget(并不是只有這兩種),這兩種widget也是目前最常用的兩種。如果一個控件自身狀態(tài)不會去改變,創(chuàng)建了就直接顯示,不會有色值、大小或者其他屬性的變化,這種widget一般都是繼承自StatelessWidget,常見的有Container、ScrollView等。如果一個控件需要動態(tài)的去改變或者相應一些狀態(tài),例如點擊態(tài)、色值、內容區(qū)域等,那么一般都是繼承自StatefulWidget,常見的有CheckBox、AppBar、TabBar等。兩者的差別在于是否有狀態(tài)。


圖片1.png
  • StatelessWidget
    一個StatelessWidget可以用多個不同的BuildContext構建,它是由build函數構建的。BuildContext抽象類,它表示一個控件在整個控件樹中的位置句柄,每個控件都有自己的BuildContext實例。某些靜態(tài)函數(例如showDialog、Theme.of等)也有BuildContext實例,以便它們可以代表調用控件或專門針對給定上下文獲取數據。BuildContext對象被傳遞給WidgetBuilder函數,它為創(chuàng)建控件的函數簽名,例如StatelessWidget.build或State.build。無狀態(tài)窗口小部件只能在加載/構建窗口小部件時繪制一次,這意味著無法基于任何事件或用戶操作重繪窗口小部件。


    圖片2.png

對于StatelessWidget,build方法會在如下三種情況下調用:

  1. widget第一次被插入到樹中;
  2. widget的父節(jié)點更改了配置(configuration);
  3. widget依賴的InheritedWidget(InheritedWidget是一個特殊的Widget,您可以將其作為另一個子樹的父級放在Widgets樹中。該子樹的所有小部件都必須能夠與該InheritedWidget公開的數據進行交互,后面會詳細總結)改變了。
import 'package:flutter/material.dart';
import 'package:flutter_apptest/SplashPage.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {

  MyApp ({
        Key key,
        this.parameter,
    }): super(key:key);
  final parameter;

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SplashPage(),//啟動MainPage
    );
  }
}
  • StatefulWidget
    其他一些小部件將處理一些在Widget生命周期內會發(fā)生變化的內部數據。因此,該數據變得動態(tài)。
    此Widget保存的數據集可能會在此Widget的生命周期內發(fā)生變化,稱為State。
    這些窗口小部件稱為有狀態(tài)窗口小部件(Stateful Widget)。
    這樣的Widget的示例可以是用戶可以選擇的復選框列表或者根據條件禁用的Button。
    StatefulWidget創(chuàng)建需要一個State狀態(tài),通過createState函數返回。而一個StatefulWidget會為每個BuildContext創(chuàng)建一個State對象。


    圖片3.png

    在了解這些狀態(tài)的時候,更多的是了解State狀態(tài)

import 'package:flutter/material.dart';

class MinePage extends StatefulWidget{

  //此部分在Widget的生命周期內不會發(fā)生變化,但可能接受可由其相應的State實例使用的參數。請注意,在Widget的第一部分定義的任何變量通常在其生命周期內不會更改。
  MyStatefulWidget({
        Key key,
        this.color,
    }): super(key: key);
    
    final Color color;

  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return new Page();
  }

}

class Page extends State<MinePage>{

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return layout(context);
  }

  Widget layout(BuildContext context) {
    // 改變狀態(tài)加以重新繪制
    setState(() {

    });
    return new Scaffold(
      appBar: buildAppBar(context),
      body: new Text('我的頁面'),
    );
  }

  buildAppBar(BuildContext context) {
    return new AppBar(title: const Text('我的'),);
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
  }

  @override
  void didChangeDependencies() {
    // TODO: implement didChangeDependencies
    super.didChangeDependencies();
  }
  
}

我們在創(chuàng)建State的時候可以看到和StatefulWidget相似的build方法,也就是說我們也可以獲得一個BuildContext,在使用StatefulWidget.createState創(chuàng)建它們之前以及在調用initState之前,框架將State對象與BuildContext關聯起來,該關聯是永久的:State對象永遠不會改變它的BuildContext(但是BuildContext本身可以在控件樹中移動)。后面講解一下這個BuildContext對象在整個程序中什么角色

State

State的作用有兩點:

  1. 在widget構建的時候可以被同步讀??;
  2. 在widget的生命周期中可能會被改變。

State的生命周期有四種狀態(tài):

  • created:當State對象被創(chuàng)建時候,State.initState方法會被調用;
  • initialized:當State對象被創(chuàng)建,但還沒有準備構建時,State.didChangeDependencies在這個時候會被調用;
  • ready:State對象已經準備好了構建,State.dispose沒有被調用的時候;
  • defunct:State.dispose被調用后,State對象不能夠被構建。
圖片4.png

完整生命周期如下:

  • 創(chuàng)建一個State對象時,會調用StatefulWidget.createState;
  • 和一個BuildContext相關聯,可以認為被加載了(mounted);
  • 調用initState;
  • 調用didChangeDependencies;
  • 經過上述步驟,State對象被完全的初始化了,調用build;
  • 如果有需要,會調用didUpdateWidget;
  • 如果處在開發(fā)模式,熱加載會調用reassemble;
  • 如果它的子樹(subtree)包含需要被移除的State對象,會調用deactivate;
  • 調用dispose,State對象以后都不會被構建;
  • 當調用了dispose,State對象處于未加載(unmounted),已經被dispose的State 對象沒有辦法被重新加載(remount)。

當控件的配置被更改時會調用State.didUpdateWidget方法,此時框架會重新繪制控件。你也可以使用State.setState方法在狀態(tài)發(fā)生變化時通知框架,告訴框架該對象的內部狀態(tài)已經改變,框架接到通知后也會重新繪制控件。

State常用方法
  • initState()
    initState()方法是創(chuàng)建State對象后要調用的第一個方法(在構造函數之后)。
    需要執(zhí)行其他初始化時,將覆蓋重寫此方法。典型的初始化與動畫,控制器有關…
    如果重寫此方法,則需要在第一個位置調用super.initState()方法。
    在這個方法中,上下文context可用,但你還不能真正使用它,因為框架還沒有完全將狀態(tài)與它相關聯。
    initState()方法完成后,State對象現在已初始化,上下文可用。在此State對象的生命周期內不再調用此方法。

  • didChangeDependencies()
    didChangeDependencies()方法是要調用的第二個方法。
    在此階段,由于上下文可用,您可以使用它。
    如果您的Widget鏈接到InheritedWidget和/或您需要初始化一些偵聽器(基于BuildContext),則通常會覆蓋此方法。請注意,如果您的窗口小部件鏈接到InheritedWidget,則每次重建此窗口小部件時都會調用此方法。

  • build()
    build(BuildContext context)方法在didChangeDependencies()(和didUpdateWidget)之后調用。
    這是您構建窗口小部件(可能還有任何子樹)的位置。
    每次State對象更改時(或者當InheritedWidget需要通知“已注冊”的小部件時)都會調用此方法!
    為了強制重建,您可以調用setState((){...})方法。

  • dispose()
    放棄窗口小部件時調用dispose()方法。
    如果你需要執(zhí)行一些清理(例如監(jiān)聽器,控制器…),然后立即調用super.dispose(),則覆蓋此方法。

State.setState

State中比較重要的一個方法是setState,當修改狀態(tài)時,widget會被更新。比方說點擊CheckBox,會出現選中和非選中狀態(tài)之間的切換,就是通過修改狀態(tài)來達到的。查看setState源碼,在一些異常的情況下將會拋出異常:

  • 傳入的為null;
  • 處在defunct階段;
  • created階段還沒有被加載(mounted);
  • 參數返回一個Future對象。
    檢查完一系列異常后,最后調用代碼如下:
_element.markNeedsBuild();

markNeedsBuild內部,則是通過標記element為diry,在下一幀的時候重建(rebuild)??梢钥闯鰏etState并不是立即生效,它只是將widget進行了標記,真正的rebuild操作,則是等到下一幀的時候才會去進行。

StatefulWidget的兩個主要類別:

  1. 在initState中創(chuàng)建資源,在dispose中銷毀,但是不依賴于InheritedWidget或者調用setState方法,這類widget基本上用在一個應用或者頁面的root;
  2. 使用setState或者依賴于InheritedWidget,這種在營業(yè)生命周期中會被重建(rebuild)很多次。
    總結一句話就是在每一幀的繪制過程,StatefulWidget會根據State或者配置變了來rebuild下一幀,也就是上面圖所說的弄臟了和下一幀又干凈了。
選擇無狀態(tài)還是有狀態(tài)小部件?

在我的小部件的生命周期中,我是否需要考慮一個將要更改的變量,何時更改,將強制重建小部件?
如果問題的答案是肯定的,那么您需要一個有狀態(tài)的小部件,否則,您需要一個無狀態(tài)小部件。
比如:

  • 用于顯示復選框列表的小組件。要顯示復選框,您需要考慮一系列項目。每個項目都是一個具有標題和狀態(tài)的對象。如果單擊復選框,則切換相應的item.status;在這種情況下,您需要使用有狀態(tài)窗口小部件來記住項目的狀態(tài),以便能夠重繪復選框。
  • 帶有表格的屏幕。該屏幕允許用戶填寫表單的窗口小部件并將表單發(fā)送到服務器。在這種情況下,在這種情況下,除非您在提交表單之前需要驗證表單或執(zhí)行任何其他操作,否則無狀態(tài)窗口小部件可能就足夠了。
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容