Flutter 的基礎(chǔ)-- Widget 介紹

前言

上一篇我們簡單地了解了 Dart 語言盖腿,接著我們就開始學(xué)習(xí) Flutter 的基礎(chǔ) Widget 吧。

本篇文章的目錄結(jié)構(gòu):

1. 什么是 Widget

在 Flutter 中要用 Widget 構(gòu)建 UI,Widget 相當(dāng)于 Android 里的 View瓶蚂,iOS 里的 UIView强霎。但與 View 不同的是,Widget 具有不同的生命周期:它是不可變的闷旧,每當(dāng) Widget 或者其狀態(tài)發(fā)生變化時(shí)长豁,F(xiàn)lutter 的框架都會(huì)創(chuàng)建一個(gè)新的 Widget 實(shí)例樹,會(huì)先計(jì)算從上一個(gè)狀態(tài)轉(zhuǎn)換到下一個(gè)狀態(tài)所需的最小更改忙灼,然后再去刷新界面匠襟。而 Aandroid 中的 View 會(huì)被繪制一次,并且在 invalidate 調(diào)用之前不會(huì)重繪该园。

2. Widget 的結(jié)構(gòu):Widget 樹

Widget 組合的結(jié)構(gòu)是樹酸舍,所以叫做 Widget 樹。

Widget 樹結(jié)構(gòu)如下圖:

2.1 父 Widget 和 子 Widget

在 Widget 樹里里初,Widget 有包含和被包含的關(guān)系:

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

2.2 根 Widget

根 Widget 也叫 RootWidget青瀑。
我們之前創(chuàng)建的 Flutter demo 的入口文件 main.dart 里面有一個(gè) main() 方法璧亮,是 Flutter 的入口方法:

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

runApp(MyApp()) 里的參數(shù) MyApp() 就是一個(gè) Widget萧诫,MyApp 的作用只是封裝了一下,實(shí)際使用的 Widget 是 MaterialApp枝嘶,這里的 MaterialApp 就是 RootWidget帘饶,F(xiàn)lutter 默認(rèn)會(huì)把 根Widget 充滿屏幕。

在 Flutter 中群扶,根 Widget 只能是以下三個(gè):

  • WidgetsApp:可以自定義風(fēng)格的根 Widget及刻。
  • MaterialApp:是在 WidgetsApp 上添加了很多 material-design 的功能,是 Material Design 風(fēng)格的根 Widget竞阐。
  • CupertinoApp:也是基于 WidgetsApp 實(shí)現(xiàn)的 iOS 風(fēng)格的根 Widget缴饭。
    這三個(gè)中最常用的是 MaterialApp,因?yàn)?MaterialApp 的功能最完善骆莹。MaterialApp 經(jīng)常與 Scaffold 一起使用颗搂,下一篇文章我們再介紹。

3. Widget 的標(biāo)識(shí)符:Key

因?yàn)?Flutter 采用的是 react-style 的框架幕垦,每次刷新 UI 的時(shí)候丢氢,都會(huì)重新構(gòu)建新的 Widget 樹,然后和之前的 Widget 樹進(jìn)行對比先改,計(jì)算出變化的部分疚察,這個(gè)計(jì)算過程叫做 diff,在 diff 過程中仇奶,如果能提前知道哪些 Widget 沒有變化貌嫡,無疑會(huì)提高 diff 的性能,這時(shí)候就需要使用標(biāo)識(shí)符该溯。

在 diff 過程中岛抄,如何知道哪些 Widget 沒有變化呢?
給 Widget 添加一個(gè)唯一的標(biāo)識(shí)符朗伶,然后在 Widget 樹的 diff 過程中弦撩,查看刷新前后的 Widget 樹有沒有相同標(biāo)識(shí)符的 Widget,如果標(biāo)識(shí)符相同论皆,則說明 Widget 沒有變化益楼,否則說明 Widget 有變化。

注意:這個(gè)標(biāo)識(shí)符在 Flutter 中就是 Key点晴,所有 Widget 都有 Key 這一個(gè)屬性感凤。

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

  • 默認(rèn)情況下(Widget 沒有設(shè)置 Key)
    當(dāng)沒有給 Widget 設(shè)置 Key 時(shí)粒督,F(xiàn)lutter 會(huì)根據(jù) Widget 的 runtimeType 和顯示順序是否相同來判斷 Widget 是否有變化陪竿。
    runtimeType 是 Widget 的 類型。
  • Widget 有 Key
    當(dāng)給 Widget 設(shè)置了 Key 時(shí),F(xiàn)lutter 是根據(jù) Key 和 runtimeType 是否相同來判斷 Widget 是否有變化族跛。

注意:Key的使用:
一般情況下我們不需要使用 Key闰挡,但是當(dāng)頁面比較復(fù)雜時(shí),就需要使用 Key 去提升渲染性能礁哄。

4. Widget 大全

Widget 有很多长酗,F(xiàn)lutter官網(wǎng)(https://flutter.dev/docs/development/ui/widgets)上將 Widget 分為14類:

  1. Accessibility:輔助功能Widget。
  2. Animation and Motion:動(dòng)畫和動(dòng)作Widget桐绒。
  3. Assets, Images, and Icons:資源和圖片Widget夺脾。
  4. Async:Flutter應(yīng)用程序的異步Widget。
  5. Basics:在構(gòu)建第一個(gè)Flutter應(yīng)用程序之前茉继,需要知道的Basics Widget咧叭。
  6. Cupertino (iOS-style widgets):iOS風(fēng)格的Widget。
  7. Input:除了在Material Components和Cupertino中的輸入Widget外烁竭,還可以接受用戶輸入的Widget菲茬。
  8. Interaction Models:響應(yīng)觸摸事件并將用戶路由到不同的視圖中。
  9. Layout:用于布局的Widget颖变。
  10. Material Components:Material Design風(fēng)格的Widget生均。
  11. Painting and effects:不改變布局、大小腥刹、位置的情況下為子Widget應(yīng)用視覺效果。
  12. Scrolling:滾動(dòng)相關(guān)的Widget汉买。
  13. Styling:主題衔峰、填充相關(guān)Widget。
  14. Text:顯示文本和文本樣式蛙粘。

Widget 幾乎實(shí)現(xiàn)了所有的功能垫卤,除了 UI、布局之外出牧,還有交互穴肘、動(dòng)畫等,所以掌握 Widget 是很重要的舔痕。

5. Widget的分類

因?yàn)殇秩臼呛芎男阅艿钠栏В瑸榱颂岣?Flutter 的幀率,就要盡量減少不必要的 UI 渲染伯复,所以 Flutter 根據(jù) UI 是否有變化慨代,將 Widget 分為:

  • StatefulWidget:是 UI 可以變化的 Widget,創(chuàng)建完后 UI 還可以再更改啸如。
  • StatelessWidget:是 UI 不可以變化的 Widget侍匙,創(chuàng)建完后 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,
              )
          ),
        )
    );
  }
}

當(dāng)點(diǎn)擊 Hello World 文本框的時(shí)候想暗,內(nèi)容會(huì)變妇汗。

實(shí)現(xiàn)自定義的 StatefulWidget,需要兩部分:

  1. StatefulWidget:主要功能創(chuàng)建 State说莫。
  2. State:狀態(tài)杨箭。

其中 StatefulWidget 需要實(shí)現(xiàn)以下兩個(gè)步驟:

  1. 首先繼承 StatefulWidget
  2. 實(shí)現(xiàn) createState() 的方法,返回一個(gè) State

State:

實(shí)現(xiàn)步驟:

  1. 首先繼承 State唬滑,State 的泛型類型是上面定義的 Widget 的類型
  2. 實(shí)現(xiàn) build() 的方法告唆,返回一個(gè) Widget
  3. 需要刷新 UI 的話,調(diào)用 setState() 方法

State 的功能:

  • build()
  • setState()
  1. build():創(chuàng)建 Widget
    State 的 build() 函數(shù)創(chuàng)建 Widget晶密,用于顯示 UI擒悬。

  2. setState():刷新UI
    當(dāng)我們想要刷新UI的時(shí)候,在 setState() 方法里更改數(shù)據(jù)稻艰,然后 setState() 會(huì)觸發(fā) State 的 build() 方法懂牧,引起強(qiáng)制重建 Widget,重建 Widget 的時(shí)候會(huì)重新綁定數(shù)據(jù)尊勿,而這時(shí)數(shù)據(jù)已經(jīng)更新了僧凤,從而達(dá)到刷新 UI 的目的。

    接下來我們看一下 setState() 方法的源碼:
    1117行元扔,執(zhí)行無參函數(shù) fn()躯保,并把結(jié)果轉(zhuǎn)換成 dynamic 類型,然后賦值給 result澎语。1133行途事,才是真正的 Widget 創(chuàng)建,感興趣的可以去看 markNeedsBuild() 方法的源碼擅羞,這里不過多介紹了尸变。

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

為什么 StatefulWidget 被分成 StatefulWidget 和 State 兩部分召烂?
一方面是為了保存當(dāng)前的 APP 狀態(tài),另一方面是為了性能娃承。
當(dāng) UI 需要更新的時(shí)候奏夫,假設(shè) Widget 和 State 都重建,由于 State 里保存了 UI 顯示的數(shù)據(jù)草慧,State 重建就會(huì)創(chuàng)建新的實(shí)例桶蛔,UI 之前的狀態(tài)就會(huì)丟失,導(dǎo)致 UI 顯示異常漫谷,所以要分成兩部分StatefulWidget 部分重建仔雷,而 State 部分不重建。
Widget 重建的成本比較低,但 State 的重建成本很高碟婆,分成兩部分就可以讓 State 不會(huì)被頻繁重建电抚,這樣就提高了性能。

生命周期:

StatefulWidget 的生命周期:

  • createState

State的生命周期:
  • moundted is true
    mounted 是 boolean竖共,只有當(dāng) mounted 為 true 時(shí)蝙叛,才能調(diào)用 setState()
  • initState
    createState 創(chuàng)建 State 對象后調(diào)用的,只會(huì)調(diào)用一次公给。一旦這個(gè)方法完成借帘,State 對象就初始化完成了。
  • didChangeDependencied
    state依賴的對象發(fā)生變化時(shí)調(diào)用淌铐,在initState() 方法運(yùn)行完后調(diào)用
  • didUpdateWidget
    Widget狀態(tài)改變時(shí)候調(diào)用肺然,可能會(huì)調(diào)用多次
  • build
    在 didChangeDependencies()(或者 didUpdateWidget() )之后調(diào)用。 這是構(gòu)建Widget的地方腿准。
  • deactivate
    當(dāng) State 從樹中移除時(shí)际起,就會(huì)觸發(fā) deactivate。但是如果在這幀結(jié)束前吐葱,如果其他地方使用到了這個(gè) Widget街望,就會(huì)重新把 Widget 插入到樹里,這就涉及到了 Widget 的重用弟跑,Widget 的重用和 Key 有關(guān)灾前。
  • dispose
    當(dāng) StaefulWidget 從樹中移除時(shí)調(diào)用 dispose() 方法

5.2 StatelessWidget

StatelessWidget 是沒有 State(狀態(tài))的 Widget,當(dāng) Widget 在運(yùn)行時(shí)不需要改變時(shí)孟辑,就用 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,其實(shí)看一下 Text 的源碼扑浸,發(fā)現(xiàn)它也是 StatelessWidget。

要實(shí)現(xiàn)自定義的 StatelessWidget燕偶,需要下面兩步:

  1. 首先繼承 StatelessWidget
  2. 必須要實(shí)現(xiàn) build 函數(shù)喝噪,返回一個(gè) Widget。

生命周期:
StatelessWidget 的生命周期只有一個(gè)指么,即 build 函數(shù)酝惧。

使用注意事項(xiàng):
StatelessWidget 只能在它初始化的時(shí)候,通過構(gòu)造函數(shù)傳遞一些額外的參數(shù)伯诬,這些參數(shù)在之后的階段都不會(huì)變化晚唇。因?yàn)?StatelessWidget 是 immutable的,只能在加載/構(gòu)建 Widget 時(shí)才繪制一次盗似。

總結(jié)

本文我們主要介紹了什么是 Widget哩陕、Widget樹結(jié)構(gòu)、Widget標(biāo)識(shí)符、Widget大全和Widget的狀態(tài)分類悍及,后面會(huì)詳細(xì)介紹具體的Widget闽瓢。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市心赶,隨后出現(xiàn)的幾起案子扣讼,更是在濱河造成了極大的恐慌,老刑警劉巖缨叫,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件椭符,死亡現(xiàn)場離奇詭異,居然都是意外死亡耻姥,警方通過查閱死者的電腦和手機(jī)销钝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來咏闪,“玉大人曙搬,你說我怎么就攤上這事「肷” “怎么了纵装?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長据某。 經(jīng)常有香客問我橡娄,道長,這世上最難降的妖魔是什么癣籽? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任挽唉,我火速辦了婚禮,結(jié)果婚禮上筷狼,老公的妹妹穿的比我還像新娘瓶籽。我一直安慰自己,他們只是感情好埂材,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布塑顺。 她就那樣靜靜地躺著,像睡著了一般俏险。 火紅的嫁衣襯著肌膚如雪严拒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天竖独,我揣著相機(jī)與錄音裤唠,去河邊找鬼。 笑死莹痢,一個(gè)胖子當(dāng)著我的面吹牛种蘸,可吹牛的內(nèi)容都是我干的墓赴。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼劈彪,長吁一口氣:“原來是場噩夢啊……” “哼竣蹦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起沧奴,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對情侶失蹤痘括,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后滔吠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纲菌,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年疮绷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了翰舌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡冬骚,死狀恐怖椅贱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情只冻,我是刑警寧澤庇麦,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站喜德,受9級(jí)特大地震影響山橄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜舍悯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一航棱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧萌衬,春花似錦饮醇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至馁蒂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蜘腌,已是汗流浹背沫屡。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留撮珠,地道東北人沮脖。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓金矛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親勺届。 傳聞我的和親對象是個(gè)殘疾皇子驶俊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容