Widget與Element
Widget主要接口
Stateless Widget
Stateful Widget
State
State生命周期
狀態(tài)管理
Widget管理自身狀態(tài)
父widget管理子widget的State
混合管理
Flutter widget庫介紹
Material widget
Cupertino widget
總結(jié)
概念
在Flutter中,幾乎所有的對象都是一個Widget,與原生開發(fā)中的“控件”不同的是,F(xiàn)lutter中的widget的概念更廣泛,它不僅可以表示UI元素(例如class Text extends StatelessWidget 而class StatelessWidget extends Widget),也可以表示一些功能性的組件如:用于手勢檢測的 GestureDetector widget(class GestureDetector extends StatelessWidget )、用于應用主題數(shù)據(jù)傳遞的Theme(class Theme extends StatelessWidget)等等。
Widget與Element
在Flutter中,Widget的功能是“描述一個UI元素的配置數(shù)據(jù)”,它就是說,Widget其實并不是表示最終繪制在設備屏幕上的顯示元素,而只是顯示元素的一個配置數(shù)據(jù)。實際上,Flutter中真正代表屏幕上顯示元素的類是Element,也就是說Widget只是描述Element的一個配置,有關Element的詳細介紹我們將在本書后面的高級部分深入介紹,讀者現(xiàn)在只需要知道,Widget只是UI元素的一個配置數(shù)據(jù),并且一個Widget可以對應多個Element,這是因為同一個Widget對象可以被添加到UI樹的不同部分,而真正渲染時,UI樹的每一個節(jié)點都會對應一個Element對象??偨Y(jié)一下:
- Widget實際上就是Element的配置數(shù)據(jù),Widget樹實際上是一個配置樹,而真正的UI渲染樹是由Element構成;不過,由于Element是通過Widget生成,所以它們之間有對應關系,所以在表述上,我們可以寬泛的認為Widget樹就是指UI控件樹或UI渲染樹。
- 一個Widget對象可以對應多個Element對象。
主要接口
1 構造器
2 布局Element
3 診斷樹
4 復用機制
abstract class Widget extends DiagnosticableTree {
/// 構造器
const Widget({ this.key });
final Key key;
@protected
Element createElement();
/// A short, textual description of this widget.
@override
String toStringShort() {
return key == null ? '$runtimeType' : '$runtimeType-$key';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
}
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
-
Widget類繼承自DiagnosticableTree,DiagnosticableTree即“診斷樹”,主要作用是提供調(diào)試信息. -
Key: 這個key屬性類似于React/Vue中的key,主要的作用是決定是否在下一次build時復用舊的widget,決定的條件在canUpdate()方法中。 -
createElement():正如前文所述“一個Widget可以對應多個Element”;Flutter Framework在構建UI樹時,會先調(diào)用此方法生成對應節(jié)點的Element對象。此方法是Flutter Framework隱式調(diào)用的,在我們開發(fā)過程中基本不會調(diào)用到。 -
debugFillProperties(...)復寫父類的方法,主要是設置診斷樹的一些特性。 -
canUpdate(...)是一個靜態(tài)方法,它主要用于在Widget樹重新build即創(chuàng)建時復用舊的widget,其實具體來說,應該是:是否用新的Widget對象去更新舊UI樹上所對應的Element對象的配置;通過其源碼我們可以看到,只要newWidget與oldWidget的runtimeType和key同時相等時就會用newWidget去更新Element對象的配置,否則就會創(chuàng)建新的Element
另外Widget類本身是一個抽象類,其中最核心的就是定義了createElement()接口,在Flutter開發(fā)中,我們一般都不用直接繼承Widget類來實現(xiàn)Widget,相反,我們通常會通過繼承
StatelessWidget和StatefulWidget來間接繼承Widget類來實現(xiàn),而StatelessWidget和StatefulWidget都是直接繼承自Widget類,而這兩個類也正是Flutter中非常重要的兩個抽象類,它們引入了兩種Widget模型,接下來我們將重點介紹一下這兩個類。
Stateless Widget
在之前的章節(jié)中,我們已經(jīng)簡單介紹過StatelessWidget,StatelessWidget相對比較簡單,它繼承自Widget,重寫了createElement()方法:
abstract class StatelessWidget extends Widget {
/// Initializes [key] for subclasses.
const StatelessWidget({ Key key }) : super(key: key);
@override
StatelessElement createElement() => StatelessElement(this);
@protected
Widget build(BuildContext context);
}
而StatelessElement 集成關系如下所示:
class StatelessElement extends ComponentElement
abstract class ComponentElement extends Element
abstract class Element extends DiagnosticableTree implements BuildContext
abstract class Diagnosticable
abstract class BuildContext
StatelessWidget用于不需要維護狀態(tài)的場景,它通常在build方法中通過嵌套其它Widget來構建UI,在構建過程中會遞歸構建 其嵌套的Widget。我們看一個簡單的例子:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context){
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.purple,
),
home:new Echo(text:'我就是一段文字')
);
}
}
class Echo extends StatelessWidget{
final String text;
final Color bgColor;
/*
按照慣例,widget的構造函數(shù)應使用命名參數(shù)(例如:Echo),
命名參數(shù)中的必要參數(shù)要添加@required標注,這樣有利于靜態(tài)代碼分析器進行檢查,
另外,在繼承widget時,第一個參數(shù)通常應該是Key,
如果接受子widget的child參數(shù),那么通常應該將它放在參數(shù)列表的最后。
同樣是按照慣例,widget的屬性應被聲明為final,防止被意外改變。
*/
const Echo({
Key key,
@required this.text,
this.bgColor:Colors.blue,
}):super(key:key);
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(
title:Text('我就是個導航'),
backgroundColor:bgColor,
),
body: Center(
child: Column(
children: <Widget>[
Text(
text,
)
],
),
),
);
}
}
運行效果:

Stateful Widget
和StatelessWidget一樣,StatefulWidget也是繼承自widget類,并重寫了createElement()方法,不同的是返回的Element 對象并不相同;另外StatefulWidget類中添加了一個新的接口createState(),下面我們看看StatefulWidget的類定義:
abstract class StatefulWidget extends Widget {
/// 構造器
const StatefulWidget({ Key key }) : super(key: key);
StatefulElement createElement() => StatefulElement(this);
///比Stateless Widget新增了一個接口
@protected
State createState();
}
-
StatefulElement間接繼承自Element類,與StatefulWidget相對應(作為其配置數(shù)據(jù))。StatefulElement中可能會多次調(diào)用createState()來創(chuàng)建狀態(tài)(State)對象 -
createState()用于創(chuàng)建和Stateful widget相關的狀態(tài),它在Stateful widget的生命周期中可能會被多次調(diào)用。例如,當一個Stateful widget同時插入到widget樹的多個位置時(例如:列表中子Widget),F(xiàn)lutter framework就會調(diào)用該方法為每一個位置生成一個獨立的State實例,其實,本質(zhì)上就是一個StatefulElement對應一個State實例。
State
一個StatefulWidget類會對應一個State類,State表示與其對應的StatefulWidget要維護的狀態(tài),State中的保存的狀態(tài)信息可以:
- 1 在widget build時可以被同步讀取。
- 2 在widget生命周期中可以被改變,當State被改變時,可以手動
調(diào)用其setState()方法通知Flutter framework狀態(tài)發(fā)生改變,F(xiàn)lutter framework在收到消息后,會重新調(diào)用其build方法重新構建widget樹,從而達到更新UI的目的。
State中有兩個常用屬性:
- 1 widget,它表示與該State實例關聯(lián)的widget實例,由Flutter framework動態(tài)設置。注意,這種關聯(lián)并非永久的,因為在應用聲明周期中,UI樹上的某一個節(jié)點的widget實例在重新構建時可能會變化,但State實例只會在第一次插入到樹中時被創(chuàng)建,當在重新構建時,如果widget被修改了,F(xiàn)lutter framework會動態(tài)設置State.widget為新的widget實例。
T get widget => _widget;
T _widget;
- 2 context,它是BuildContext類的一個實例,表示構建widget的上下文,它是操作widget在樹中位置的一個句柄,它包含了一些查找、遍歷當前Widget樹的一些方法。每一個widget都有一個自己的context對象。
State生命周期
理解State的生命周期對flutter開發(fā)非常重要,為了加深讀者印象,本節(jié)我們通過一個實例來演示一下State的生命周期。在接下來的示例中,我們實現(xiàn)一個計數(shù)器widget,點擊它可以使計數(shù)器加1,由于要保存計數(shù)器的數(shù)值狀態(tài),所以我們應繼承StatefulWidget,代碼如下:
class CounterWidget extends StatefulWidget{
const CounterWidget({
Key key,
this.initValue:0,
}):super(key:key);
final int initValue;
@override
_CounterWidgetState createState() => new _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget>{
int _count;
@override
void initState(){
super.initState();
_count = widget.initValue;
print("initState");
}
@override
Widget build(BuildContext context){
print('build');
return Center(
child: FlatButton(
child: Text('$_count'),
onPressed: () => setState(() => ++_count),
),
);
}
@override
void didUpdateWidget(CounterWidget oldWidget) {
super.didUpdateWidget(oldWidget);
print("didUpdateWidget");
}
//無效
@override
void deactivate(){
super.deactivate();
print("deactivate");
}
@override
void dispose() {
super.dispose();
print("dispose");
}
@override
void reassemble() {
super.reassemble();
print("reassemble");
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("didChangeDependencies");
}
}
接下來,我們創(chuàng)建一個新路由,在新路由中,我們只顯示一個CounterWidget:
Widget build(BuildContext context) {
return CounterWidget();
}
我們運行應用并打開該路由頁面,在新路由頁打開后,屏幕中央就會出現(xiàn)一個數(shù)字0,然后控制臺日志輸出
Launching lib/main.dart on iPhone X in debug mode...
Xcode build done. 17.0s
flutter: initState
flutter: didChangeDependencies
flutter: build
可以看到,在StatefulWidget插入到Widget樹時首先initState方法會被調(diào)用。
然后我們點擊??按鈕熱重載,控制臺輸出日志如下:
flutter: reassemble
flutter: didUpdateWidget
flutter: build
Reloaded 0 of 419 libraries in 1,082ms.
可以看到此時initState 和didChangeDependencies都沒有被調(diào)用,而此時didUpdateWidget被調(diào)用。
接下來,我們在widget樹中移除CounterWidget,將路由build方法改為:
Widget build(BuildContext context) {
//移除計數(shù)器
//return CounterWidget();
//隨便返回一個Text()
return Text("xxx");
}
然后熱重載,日志如下:
flutter: reassemble
flutter: deactivate
flutter: dispose
Reloaded 1 of 419 libraries in 1,085ms.
我們可以看到,在CounterWidget從widget樹中移除時,deactive和dispose會依次被調(diào)用。
下面我們來看看各個回調(diào)函數(shù):
initState:當Widget第一次插入到Widget樹時會被調(diào)用,對于每一個State對象,F(xiàn)lutter framework只會調(diào)用一次該回調(diào),所以,通常在該回調(diào)中做一些一次性的操作,如狀態(tài)初始化、訂閱子樹的事件通知等。不能在該回調(diào)中調(diào)用
BuildContext.inheritFromWidgetOfExactType(該方法用于在Widget樹上獲取離當前widget最近的一個父級InheritFromWidget,關于InheritedWidget我們將在后面章節(jié)介紹),原因是在初始化完成后,Widget樹中的InheritFromWidget也可能會發(fā)生變化,所以正確的做法應該在在build()方法或didChangeDependencies()中調(diào)用它。didChangeDependencies():當State對象的依賴發(fā)生變化時會被調(diào)用;例如:在之前build() 中包含了一個InheritedWidget,然后在之后的build() 中InheritedWidget發(fā)生了變化,那么此時InheritedWidget的子widget的didChangeDependencies()回調(diào)都會被調(diào)用。典型的場景是當系統(tǒng)語言Locale或應用主題改變時,F(xiàn)lutter framework會通知widget調(diào)用此回調(diào)。build():此回調(diào)讀者現(xiàn)在應該已經(jīng)相當熟悉了,它主要是用于構建Widget子樹的,會在如下場景被調(diào)用:
1 在調(diào)用initState()之后。
2 在調(diào)用didUpdateWidget()之后。
3 在調(diào)用setState()之后。
4 在調(diào)用didChangeDependencies()之后。
5 在State對象從樹中一個位置移除后(會調(diào)用deactivate)又重新插入到樹的其它位置之后。
-
reassemble():此回調(diào)是專門為了開發(fā)調(diào)試而提供的,在熱重載(hot reload)時會被調(diào)用,此回調(diào)在Release模式下永遠不會被調(diào)用。 -
didUpdateWidget():在widget重新構建時,F(xiàn)lutter framework會調(diào)用Widget.canUpdate來檢測Widget樹中同一位置的新舊節(jié)點,然后決定是否需要更新,如果Widget.canUpdate返回true則會調(diào)用此回調(diào)。正如之前所述,Widget.canUpdate會在新舊widget的key和runtimeType同時相等時會返回true,也就是說在在新舊widget的key和runtimeType同時相等時didUpdateWidget()就會被調(diào)用。 -
deactivate():當State對象從樹中被移除時,會調(diào)用此回調(diào)。在一些場景下,F(xiàn)lutter framework會將State對象重新插到樹中,如包含此State對象的子樹在樹的一個位置移動到另一個位置時(可以通過GlobalKey來實現(xiàn))。如果移除后沒有重新插入到樹中則緊接著會調(diào)用dispose()方法。 -
dispose():當State對象從樹中被永久移除時調(diào)用;通常在此回調(diào)中釋放資源。
狀態(tài)管理
響應式的編程框架中都會有一個永恒的主題——“狀態(tài)管理”,無論是在React/Vue(兩者都是支持響應式編程的web開發(fā)框架)還是Flutter,他們討論的問題和解決的思想都是一致的。所以,如果你對React/Vue的狀態(tài)管理有了解,可以跳過本節(jié)。言歸正傳,我們想一個問題,stateful widget的狀態(tài)應該被誰管理?widget本身?父widget?都會?還是另一個對象?答案是取決于實際情況!以下是管理狀態(tài)的最常見的方法:
- Widget管理自己的state。
- 父widget管理子widget狀態(tài)。
- 混合管理(父widget和子widget都管理狀態(tài))。
如何決定使用哪種管理方法?以下原則可以幫助你決定:
- 如果狀態(tài)是用戶數(shù)據(jù),如復選框的選中狀態(tài)、滑塊的位置,則該狀態(tài)最好由父widget管理。
- 如果狀態(tài)是有關界面外觀效果的,例如顏色、動畫,那么狀態(tài)最好由widget本身來管理。
- 如果某一個狀態(tài)是不同widget共享的則最好由它們共同的父widget管理。
接下來,我們將通過創(chuàng)建三個簡單示例TapboxA、TapboxB和TapboxC來說明管理狀態(tài)的不同方式。 這些例子功能是相似的 ——創(chuàng)建一個盒子,當點擊它時,盒子背景會在綠色與灰色之間切換。狀態(tài) _active確定顏色:綠色為true ,灰色為false。

Widget管理自身狀態(tài)
_TapboxAState 類:
- 管理TapboxA的狀態(tài)。
- 定義_active:確定盒子的當前顏色的布爾值。
- 定義_handleTap()函數(shù),該函數(shù)在點擊該盒子時更新_active,并調(diào)用setState()更新UI。
- 實現(xiàn)widget的所有交互式行為。
class TapBoxA extends StatefulWidget{
@override
_TapBoxAState createState() => _TapBoxAState();
}
class _TapBoxAState extends State<TapBoxA>{
bool _active = false;
void _handleTap(){
setState(() {
_active = !_active;
});
}
@override
Widget build(BuildContext context){
return new GestureDetector(
onTap: _handleTap,
child: new Container(
width: 200,
height: 200,
decoration: new BoxDecoration(
color: _active?Colors.lightGreen[700]:Colors.green[600],
),
child: new Center(
child: new Text(
_active ?"Active":"Inactive",
style: new TextStyle(
fontSize: 32,
color: Colors.white,
),
),
),
),
);
}
}
父widget管理子widget的State
對于父widget來說,管理狀態(tài)并告訴其子widget何時更新通常是比較好的方式。 例如,IconButton是一個圖片按鈕,但它是一個無狀態(tài)的widget,因為我們認為父widget需要知道該按鈕是否被點擊來采取相應的處理。
在以下示例中,TapboxB通過回調(diào)將其狀態(tài)導出到其父項。由于TapboxB不管理任何狀態(tài),因此它的父類為StatelessWidget。
ParentWidgetState 類:
- 為TapboxB 管理_active狀態(tài).
- 實現(xiàn)_handleTapboxChanged(),當盒子被點擊時調(diào)用的方法.
- 當狀態(tài)改變時,調(diào)用setState()更新UI.
TapboxB 類:
- 繼承StatelessWidget類,因為所有狀態(tài)都由其父widget處理。
- 當檢測到點擊時,它會通知父widget。
//父widget管理子widget的state
class ParentWidget extends StatefulWidget{
@override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget>{
bool _active = false;
void _handleTapBoxChanged(bool newActive){
setState(() {
_active = newActive;
});
}
@override
Widget build(BuildContext context) {
return new Container(
child: new TapBoxB(
active: _active,
onChanged: _handleTapBoxChanged,
),
);
}
}
class TapBoxB extends StatelessWidget{
const TapBoxB({
Key key,
this.active:false,
@required this.onChanged,
}):super(key:key);
final bool active;
final ValueChanged<bool> onChanged;
void _handleTap() {
onChanged(!active);
}
Widget build(BuildContext context){
return new GestureDetector(
onTap: _handleTap,
child: new Container(
width: 200,
height: 200,
decoration: new BoxDecoration(
color: active?Colors.lightGreen[700]:Colors.red[600],
),
child: new Center(
child: new Text(
active?"Active":"Inactive",
style: new TextStyle(
fontSize: 32,
color: Colors.red
),
),
),
),
);
}
}
混合管理
注意頁面
build都是放在管理State中的
對于一些widget來說,混和管理的方式非常有用。在這種情況下,widget自身管理一些內(nèi)部狀態(tài),而父widget管理一些其他外部狀態(tài)。
在下面TapboxC示例中,點擊時,盒子的周圍會出現(xiàn)一個深綠色的邊框。點擊時,邊框消失,盒子的顏色改變。 TapboxC將其_active狀態(tài)導出到其父widget中,但在內(nèi)部管理其_highlight狀態(tài)。這個例子有兩個狀態(tài)對象_ParentWidgetState和_TapboxCState。
_ParentWidgetStateC 對象:
- 管理_active 狀態(tài)。
- 實現(xiàn) _handleTapboxChanged() ,當盒子被點擊時調(diào)用。
- 當點擊盒子并且_active狀態(tài)改變時調(diào)用setState()更新UI。
_TapboxCState 對象:
管理_highlight state。
- GestureDetector監(jiān)聽所有tap事件。當用戶點下時,它添加高亮(深綠色邊框);當用戶釋放時,會移除高亮。
- 當按下、抬起、或者取消點擊時更新_highlight狀態(tài),調(diào)用setState()更新UI。
- 當點擊時,將狀態(tài)的改變傳遞給父widget.
class ParentWidgetC extends StatefulWidget{
@override
_ParentWidgetCState createState() => _ParentWidgetCState();
}
class _ParentWidgetCState extends State<ParentWidgetC>{
bool _active = false;
void _handleTapBoxChanged(bool newActive){
setState(() {
_active = newActive;
});
}
@override
Widget build(BuildContext context) {
return new Container(
child: new TapBoxC(
active: _active,
onChanged: _handleTapBoxChanged,
),
);
}
}
class TapBoxC extends StatefulWidget{
const TapBoxC({
Key key,
this.active:false,
@required this.onChanged,
}):super(key:key);
final bool active;
final ValueChanged<bool> onChanged;
@override
_TapBoxCState createState() => _TapBoxCState();
}
class _TapBoxCState extends State<TapBoxC>{
bool _hightlight = false;
void _handleTapDown(TapDownDetails details) {
setState(() {
_hightlight = true;
});
}
void _handleTapUp(TapUpDetails details) {
setState(() {
_hightlight = false;
});
}
void _handleTapCancel() {
setState(() {
_hightlight = false;
});
}
void _handleTap() {
widget.onChanged(!widget.active);
}
Widget build(BuildContext context){
return new GestureDetector(
onTap: _handleTap,
onTapDown: _handleTapDown,
onTapUp: _handleTapUp,
onTapCancel: _handleTapCancel,
child: new Container(
width: 200,
height: 200,
decoration: new BoxDecoration(
color: widget.active?Colors.lightGreen[700]:Colors.red[600],
border: _hightlight
? new Border.all(
color: Colors.teal[700],
width: 10
)
: null
),
child: new Center(
child: new Text(
widget.active?"Active":"Inactive",
style: new TextStyle(
fontSize: 32,
color: Colors.red
),
),
),
),
);
}
}
全局狀態(tài)管理
當應用中包括一些跨widget(甚至跨路由)的狀態(tài)需要同步時,上面介紹的方法很難勝任了。比如,我們有一個設置頁,里面可以設置應用語言,但是我們?yōu)榱俗屧O置實時生效,我們期望在語言狀態(tài)發(fā)生改變時,我們的APP Widget能夠重新build一下,但我們的APP Widget和設置頁并不在一起。正確的做法是通過一個全局狀態(tài)管理器來處理這種“相距較遠”的widget之間的通信。目前主要有兩種辦法:
- 實現(xiàn)一個全局的事件總線,將語言狀態(tài)改變對應為一個事件,然后在APP Widget所在的父widgetinitState 方法中訂閱語言改變的事件,當用戶在設置頁切換語言后,我們觸發(fā)語言改變事件,然后APP Widget那邊就會收到通知,然后重新build一下即可。
- 使用redux這樣的全局狀態(tài)包,讀者可以在pub上查看其詳細信息。
Flutter widget庫介紹
Flutter提供了一套豐富、強大的基礎widget,在基礎widget庫之上Flutter又提供了一套Material風格(Android默認的視覺風格)和一套Cupertino風格(iOS視覺風格)的widget庫。要使用基礎widget庫,需要先導入:
import 'package:flutter/material.dart';
Material widget
Flutter提供了一套豐富的Material widget,可幫助您構建遵循Material Design的應用程序。Material應用程序以MaterialApp widget開始, 該widget在應用程序的根部創(chuàng)建了一些有用的widget,比如一個Theme,它配置了應用的主題。 是否使用MaterialApp完全是可選的,但是使用它是一個很好的做法。在之前的示例中,我們已經(jīng)使用過多個Material widget了,如:Scaffold、AppBar、FlatButton等。要使用Material widget,需要先引入它:
import 'package:flutter/material.dart';
Cupertino widget
Flutter也提供了一套豐富的Cupertino風格的widget,盡管目前還沒有Material widget那么豐富,但也在不斷的完善中。值得一提的是在Material widget庫中,有一些widget可以根據(jù)實際運行平臺來切換表現(xiàn)風格,比如MaterialPageRoute,在路由切換時,如果是Android系統(tǒng),它將會使用Android系統(tǒng)默認的頁面切換動畫(從底向上),如果是iOS系統(tǒng)時,它會使用iOS系統(tǒng)默認的頁面切換動畫(從右向左)。由于在前面的示例中還沒有Cupertino widget的示例,我們實現(xiàn)一個簡單的Cupertino頁面:
//導入cupertino widget庫
import 'package:flutter/cupertino.dart';
class CupertinoTestRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text("Cupertino Demo"),
),
child: Center(
child: CupertinoButton(
color: CupertinoColors.activeBlue,
child: Text("Press"),
onPressed: () {}
),
),
);
}
}
總結(jié)
Flutter提供了豐富的widget,在實際的開發(fā)中你可以隨意使用它們,不要怕引入過多widget庫會讓你的應用安裝包變大,這不是web開發(fā),dart在編譯時只會編譯你使用了的代碼。由于Material和Cupertino都是在基礎widget庫之上的,所以如果你的應用中引入了這兩者之一,則不需要再引入flutter/widgets.dart了,因為它們內(nèi)部已經(jīng)引入過了。