前言
上一篇我們簡單地了解了 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 樹。
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類:
- Accessibility:輔助功能Widget。
- Animation and Motion:動畫和動作Widget。
- Assets, Images, and Icons:資源和圖片Widget。
- Async:Flutter應用程序的異步Widget。
- Basics:在構建第一個Flutter應用程序之前,需要知道的Basics Widget。
- Cupertino (iOS-style widgets):iOS風格的Widget。
- Input:除了在Material Components和Cupertino中的輸入Widget外,還可以接受用戶輸入的Widget。
- Interaction Models:響應觸摸事件并將用戶路由到不同的視圖中。
- Layout:用于布局的Widget。
- Material Components:Material Design風格的Widget。
- Painting and effects:不改變布局、大小、位置的情況下為子Widget應用視覺效果。
- Scrolling:滾動相關的Widget。
- Styling:主題、填充相關Widget。
- 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,需要兩部分:
- StatefulWidget:主要功能創建 State。
- State:狀態。
其中 StatefulWidget 需要實現以下兩個步驟:
- 首先繼承 StatefulWidget
- 實現 createState() 的方法,返回一個 State
State:
實現步驟:
- 首先繼承 State,State 的泛型類型是上面定義的 Widget 的類型
- 實現 build() 的方法,返回一個 Widget
- 需要刷新 UI 的話,調用 setState() 方法
State 的功能:
- build()
- setState()
build():創建 Widget
State 的 build() 函數創建 Widget,用于顯示 UI。-
setState():刷新UI
接下來我們看一下 setState() 方法的源碼:
當我們想要刷新UI的時候,在 setState() 方法里更改數據,然后 setState() 會觸發 State 的 build() 方法,引起強制重建 Widget,重建 Widget 的時候會重新綁定數據,而這時數據已經更新了,從而達到刷新 UI 的目的。
注意:刷新 UI 的代碼必須在調用 setState() 之前或者在 setState() 函數里面寫,才能刷新UI。
為什么 StatefulWidget 被分成 StatefulWidget 和 State 兩部分?
一方面是為了保存當前的 APP 狀態,另一方面是為了性能。
當 UI 需要更新的時候,假設 Widget 和 State 都重建,由于 State 里保存了 UI 顯示的數據,State 重建就會創建新的實例,UI 之前的狀態就會丟失,導致 UI 顯示異常,所以要分成兩部分StatefulWidget 部分重建,而 State 部分不重建。
Widget 重建的成本比較低,但 State 的重建成本很高,分成兩部分就可以讓 State 不會被頻繁重建,這樣就提高了性能。
生命周期:
StatefulWidget 的生命周期:
- createState
- 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,需要下面兩步:
- 首先繼承 StatelessWidget
- 必須要實現 build 函數,返回一個 Widget。
生命周期:
StatelessWidget 的生命周期只有一個,即 build 函數。
使用注意事項:
StatelessWidget 只能在它初始化的時候,通過構造函數傳遞一些額外的參數,這些參數在之后的階段都不會變化。因為 StatelessWidget 是 immutable的,只能在加載/構建 Widget 時才繪制一次。
總結
本文我們主要介紹了什么是 Widget、Widget樹結構、Widget標識符、Widget大全和Widget的狀態分類,后面會詳細介紹具體的Widget。