前言
上一篇我們簡單地了解了 Dart 語言盖腿,接著我們就開始學(xué)習(xí) Flutter 的基礎(chǔ) Widget 吧。
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 樹。
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類:
- Accessibility:輔助功能Widget。
- Animation and Motion:動(dòng)畫和動(dòng)作Widget桐绒。
- Assets, Images, and Icons:資源和圖片Widget夺脾。
- Async:Flutter應(yīng)用程序的異步Widget。
- Basics:在構(gòu)建第一個(gè)Flutter應(yīng)用程序之前茉继,需要知道的Basics Widget咧叭。
- Cupertino (iOS-style widgets):iOS風(fēng)格的Widget。
- Input:除了在Material Components和Cupertino中的輸入Widget外烁竭,還可以接受用戶輸入的Widget菲茬。
- Interaction Models:響應(yīng)觸摸事件并將用戶路由到不同的視圖中。
- Layout:用于布局的Widget颖变。
- Material Components:Material Design風(fēng)格的Widget生均。
- Painting and effects:不改變布局、大小腥刹、位置的情況下為子Widget應(yīng)用視覺效果。
- Scrolling:滾動(dòng)相關(guān)的Widget汉买。
- Styling:主題衔峰、填充相關(guān)Widget。
- 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,需要兩部分:
- StatefulWidget:主要功能創(chuàng)建 State说莫。
- State:狀態(tài)杨箭。
其中 StatefulWidget 需要實(shí)現(xiàn)以下兩個(gè)步驟:
- 首先繼承 StatefulWidget
- 實(shí)現(xiàn) createState() 的方法,返回一個(gè) State
State:
實(shí)現(xiàn)步驟:
- 首先繼承 State唬滑,State 的泛型類型是上面定義的 Widget 的類型
- 實(shí)現(xiàn) build() 的方法告唆,返回一個(gè) Widget
- 需要刷新 UI 的話,調(diào)用 setState() 方法
State 的功能:
- build()
- setState()
build():創(chuàng)建 Widget
State 的 build() 函數(shù)創(chuàng)建 Widget晶密,用于顯示 UI擒悬。-
setState():刷新UI
接下來我們看一下 setState() 方法的源碼:
當(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 的目的。
注意:刷新 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
- 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燕偶,需要下面兩步:
- 首先繼承 StatelessWidget
- 必須要實(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闽瓢。