Flutter 的基礎-- Widget 介紹

前言

上一篇我們簡單地了解了 Dart 語言,接著我們就開始學習 Flutter 的基礎 Widget 吧。

本篇文章的目錄結構:

1. 什么是 Widget

在 Flutter 中要用 Widget 構建 UI,Widget 相當于 Android 里的 View,iOS 里的 UIView。但與 View 不同的是,Widget 具有不同的生命周期:它是不可變的,每當 Widget 或者其狀態發生變化時,Flutter 的框架都會創建一個新的 Widget 實例樹,會先計算從上一個狀態轉換到下一個狀態所需的最小更改,然后再去刷新界面。而 Aandroid 中的 View 會被繪制一次,并且在 invalidate 調用之前不會重繪。

2. Widget 的結構:Widget 樹

Widget 組合的結構是樹,所以叫做 Widget 樹。

Widget 樹結構如下圖:

2.1 父 Widget 和 子 Widget

在 Widget 樹里,Widget 有包含和被包含的關系:

  • 父 Widget:包含其他 Widget 的就叫父 Widget。
  • 子 Widget:被父 Widget 包含的 Widget 就叫子 Widget。

2.2 根 Widget

根 Widget 也叫 RootWidget。
我們之前創建的 Flutter demo 的入口文件 main.dart 里面有一個 main() 方法,是 Flutter 的入口方法:

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

runApp(MyApp()) 里的參數 MyApp() 就是一個 Widget,MyApp 的作用只是封裝了一下,實際使用的 Widget 是 MaterialApp,這里的 MaterialApp 就是 RootWidget,Flutter 默認會把 根Widget 充滿屏幕。

在 Flutter 中,根 Widget 只能是以下三個:

  • WidgetsApp:可以自定義風格的根 Widget。
  • MaterialApp:是在 WidgetsApp 上添加了很多 material-design 的功能,是 Material Design 風格的根 Widget。
  • CupertinoApp:也是基于 WidgetsApp 實現的 iOS 風格的根 Widget。
    這三個中最常用的是 MaterialApp,因為 MaterialApp 的功能最完善。MaterialApp 經常與 Scaffold 一起使用,下一篇文章我們再介紹。

3. Widget 的標識符:Key

因為 Flutter 采用的是 react-style 的框架,每次刷新 UI 的時候,都會重新構建新的 Widget 樹,然后和之前的 Widget 樹進行對比,計算出變化的部分,這個計算過程叫做 diff,在 diff 過程中,如果能提前知道哪些 Widget 沒有變化,無疑會提高 diff 的性能,這時候就需要使用標識符。

在 diff 過程中,如何知道哪些 Widget 沒有變化呢?
給 Widget 添加一個唯一的標識符,然后在 Widget 樹的 diff 過程中,查看刷新前后的 Widget 樹有沒有相同標識符的 Widget,如果標識符相同,則說明 Widget 沒有變化,否則說明 Widget 有變化。

注意:這個標識符在 Flutter 中就是 Key,所有 Widget 都有 Key 這一個屬性。

那么 Flutter 中是如何在 diff 過程中判斷哪些 Widget 沒有變化呢?

  • 默認情況下(Widget 沒有設置 Key)
    當沒有給 Widget 設置 Key 時,Flutter 會根據 Widget 的 runtimeType 和顯示順序是否相同來判斷 Widget 是否有變化。
    runtimeType 是 Widget 的 類型。
  • Widget 有 Key
    當給 Widget 設置了 Key 時,Flutter 是根據 Key 和 runtimeType 是否相同來判斷 Widget 是否有變化。

注意:Key的使用:
一般情況下我們不需要使用 Key,但是當頁面比較復雜時,就需要使用 Key 去提升渲染性能。

4. Widget 大全

Widget 有很多,Flutter官網(https://flutter.dev/docs/development/ui/widgets)上將 Widget 分為14類:

  1. Accessibility:輔助功能Widget。
  2. Animation and Motion:動畫和動作Widget。
  3. Assets, Images, and Icons:資源和圖片Widget。
  4. Async:Flutter應用程序的異步Widget。
  5. Basics:在構建第一個Flutter應用程序之前,需要知道的Basics Widget。
  6. Cupertino (iOS-style widgets):iOS風格的Widget。
  7. Input:除了在Material Components和Cupertino中的輸入Widget外,還可以接受用戶輸入的Widget。
  8. Interaction Models:響應觸摸事件并將用戶路由到不同的視圖中。
  9. Layout:用于布局的Widget。
  10. Material Components:Material Design風格的Widget。
  11. Painting and effects:不改變布局、大小、位置的情況下為子Widget應用視覺效果。
  12. Scrolling:滾動相關的Widget。
  13. Styling:主題、填充相關Widget。
  14. Text:顯示文本和文本樣式。

Widget 幾乎實現了所有的功能,除了 UI、布局之外,還有交互、動畫等,所以掌握 Widget 是很重要的。

5. Widget的分類

因為渲染是很耗性能的,為了提高 Flutter 的幀率,就要盡量減少不必要的 UI 渲染,所以 Flutter 根據 UI 是否有變化,將 Widget 分為:

  • StatefulWidget:是 UI 可以變化的 Widget,創建完后 UI 還可以再更改。
  • StatelessWidget:是 UI 不可以變化的 Widget,創建完后 UI 就不可以再更改。

5.1 StatefulWidget

StatefulWidget 是 UI 可以變化的Widget。

代碼舉例:

import 'package:flutter/material.dart';

void main() => runApp(MyApp("Hello World"));

class MyApp extends StatefulWidget {
  // This widget is the root of your application.

  String content;

  MyApp(this.content);

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

class MyAppState extends State<MyApp> {

  bool isShowText =true;

  void increment(){
    setState(() {
      widget.content += "d";
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: Scaffold(
          appBar: AppBar(title: Text("Widget -- StatefulWidget及State"),),
          body: Center(
              child: GestureDetector(
                child: isShowText? Text(widget.content) :null,
                onTap: increment,
              )
          ),
        )
    );
  }
}

當點擊 Hello World 文本框的時候,內容會變。

實現自定義的 StatefulWidget,需要兩部分:

  1. StatefulWidget:主要功能創建 State。
  2. State:狀態。

其中 StatefulWidget 需要實現以下兩個步驟:

  1. 首先繼承 StatefulWidget
  2. 實現 createState() 的方法,返回一個 State

State:

實現步驟:

  1. 首先繼承 State,State 的泛型類型是上面定義的 Widget 的類型
  2. 實現 build() 的方法,返回一個 Widget
  3. 需要刷新 UI 的話,調用 setState() 方法

State 的功能:

  • build()
  • setState()
  1. build():創建 Widget
    State 的 build() 函數創建 Widget,用于顯示 UI。

  2. setState():刷新UI
    當我們想要刷新UI的時候,在 setState() 方法里更改數據,然后 setState() 會觸發 State 的 build() 方法,引起強制重建 Widget,重建 Widget 的時候會重新綁定數據,而這時數據已經更新了,從而達到刷新 UI 的目的。

    接下來我們看一下 setState() 方法的源碼:
    1117行,執行無參函數 fn(),并把結果轉換成 dynamic 類型,然后賦值給 result。1133行,才是真正的 Widget 創建,感興趣的可以去看 markNeedsBuild() 方法的源碼,這里不過多介紹了。

注意:刷新 UI 的代碼必須在調用 setState() 之前或者在 setState() 函數里面寫,才能刷新UI。

為什么 StatefulWidget 被分成 StatefulWidget 和 State 兩部分?
一方面是為了保存當前的 APP 狀態,另一方面是為了性能。
當 UI 需要更新的時候,假設 Widget 和 State 都重建,由于 State 里保存了 UI 顯示的數據,State 重建就會創建新的實例,UI 之前的狀態就會丟失,導致 UI 顯示異常,所以要分成兩部分StatefulWidget 部分重建,而 State 部分不重建。
Widget 重建的成本比較低,但 State 的重建成本很高,分成兩部分就可以讓 State 不會被頻繁重建,這樣就提高了性能。

生命周期:

StatefulWidget 的生命周期:

  • createState

State的生命周期:
  • moundted is true
    mounted 是 boolean,只有當 mounted 為 true 時,才能調用 setState()
  • initState
    createState 創建 State 對象后調用的,只會調用一次。一旦這個方法完成,State 對象就初始化完成了。
  • didChangeDependencied
    state依賴的對象發生變化時調用,在initState() 方法運行完后調用
  • didUpdateWidget
    Widget狀態改變時候調用,可能會調用多次
  • build
    在 didChangeDependencies()(或者 didUpdateWidget() )之后調用。 這是構建Widget的地方。
  • deactivate
    當 State 從樹中移除時,就會觸發 deactivate。但是如果在這幀結束前,如果其他地方使用到了這個 Widget,就會重新把 Widget 插入到樹里,這就涉及到了 Widget 的重用,Widget 的重用和 Key 有關。
  • dispose
    當 StaefulWidget 從樹中移除時調用 dispose() 方法

5.2 StatelessWidget

StatelessWidget 是沒有 State(狀態)的 Widget,當 Widget 在運行時不需要改變時,就用 StatelessWidget。

代碼舉例:

import 'package:flutter/material.dart';

void main() => runApp(MyApp("Hello World"));

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  final String content;

  MyApp(this.content);
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        body: Center(
          child: Text(content),
        ),
      )
    );
  }
}

上面代碼中的 MyApp 是 StatelessWidget,其實看一下 Text 的源碼,發現它也是 StatelessWidget。

要實現自定義的 StatelessWidget,需要下面兩步:

  1. 首先繼承 StatelessWidget
  2. 必須要實現 build 函數,返回一個 Widget。

生命周期:
StatelessWidget 的生命周期只有一個,即 build 函數。

使用注意事項:
StatelessWidget 只能在它初始化的時候,通過構造函數傳遞一些額外的參數,這些參數在之后的階段都不會變化。因為 StatelessWidget 是 immutable的,只能在加載/構建 Widget 時才繪制一次。

總結

本文我們主要介紹了什么是 Widget、Widget樹結構、Widget標識符、Widget大全和Widget的狀態分類,后面會詳細介紹具體的Widget。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內容