Flutter 提供了大量的 Widget,種類繁多,就是布局組件就多達 30 個,我們就不一一介紹,只介紹比較常用的一些。在介紹組件之前,我們先了解一下 Flutter 部件的不同種類,在 Flutter 的官方文檔中,對所有部件的分類比較混亂,不同類型的部件之間有很多交叉現(xiàn)象,例如:Container 既屬于基礎部件又屬于布局類型的部件;Image 部件同時在基礎類型和 Material 類型中,并且還專門為資源類型的部件(Icon、RowImage 等)定義了一個類別。個人感覺它的分類比較混亂,因此我試著重新梳理一下 Flutter 的常用部件類型,以便于學習使用。
- Basics
- Forms and Inputs
- Layout
- Dialogs and Alerts
- Interaction Models
- Animation and Motion
- Painting and effects
StatelessWidget vs StatefulWidget
在介紹各種不同的部件之前,我們先了解一下什么是無狀態(tài)部件
和有狀態(tài)部件
。無狀態(tài)部件,它內部的狀態(tài)是來自父組件,并且使用 final 類型的變量來存儲,當部件被創(chuàng)建的時候,UI 是由這些不可改變的數(shù)據(jù)構建的,他們都是最終態(tài)。那么有狀態(tài)部件剛好和無狀態(tài)部件相反,它內部的狀態(tài)在部件的整個生命周期是可以發(fā)生變化的。
Flutter 詭異的兩個基類,這是在做動靜分離嗎?性能的考慮嗎?個人覺得大可不必,在實際項目中絕大多數(shù) UI 都需要
常用 Widgets
- Basics:
- Text & Image & Icon
- ListView & ListTile
- Buttons
- Scaffold & Appbar & Drawer & PopupMenuButton
- Forms and Inputs:
- Form
- TextFormField & TextField
- Checkbox & Radio & Switch
- Date & Time Pickers
- Layout:
- Container & Padding & Center & Positioned
- Column & Row & Stack & GridView
- Dialogs and Alerts:
- AlertDialog
- SnackBar
- BottomSheet
- Interaction Models
- Animation and Motion
- Painting and effects
常用基礎部件(Basics)
Text & Icon & Image
Text & Image & Icon 是 UI 中最基礎的信息顯示部件,這些部件就不做詳細的介紹,因為作為前端工程師,用的最多最熟悉的也就是這些部件,直接看代碼:
Column(
children: <Widget>[
Text('Text Widget'),
Image.network('https://metaimg.baichanghui.com/appdownload.jpg'),
Icon(Icons.help),
]
)
ListView & ListTile
ListView & ListTile 這類組件,主要是解決大量列表數(shù)據(jù)的展示和交互。ListView 為列表的主體部分,它可以綁定數(shù)據(jù)集合,并且滾動時可以和遠程數(shù)據(jù)進行交互,當然也可以對列表項的數(shù)據(jù)進行不同的布局展示。ListTile 為列表項的布局,它是基于 Material Design 設計出來的一種特殊布局的部件。
List<String> listData = ['a', 'b', 'c', 'd'];
//...
ListView.builder(
itemCount: 4,
itemBuilder: (context, index) {
String item = listData[index];
return ListTile(
title: Text(item),
subtitle: Text(index.toString())
);
}
);
Buttons
在 Flutter 部件中有各種不同風格的按鈕,例如:IconButton、FlatButton、RaisedButton、RawMaterialButton 等等,其實它們僅僅是風格不同,用處沒有太大的差別:
Column(
children: <Widget>[
FlatButton(
color: Colors.black,
textColor: Colors.white,
child: Text('Button'),
onPressed: () { },
),
IconButton(
icon: Icon(Icons.help),
iconSize: 20,
color: Colors.black,
onPressed: () { },
)
]
)
他們在初始化是,有一個必須要初始化的屬性 onPressed
Scaffold & Appbar & Drawer & PopupMenuButton
這一組部件也是基于 Material Design 設計出來的,它們主要解決 App 的導航、輔助功能等問題,當然它也會幫你適配頁面在不同設備下的表現(xiàn)。下面的代碼中,會出現(xiàn)上述所有組件,讓我們分享一下代碼:
Scaffold(
appBar: Appbar(
title: Text('Title'),
actions: <Widget> [
PopupMenuButton(
itemBuilder: (context) {
return [
PopupMenuItem(
value: '',
child: Text('Item1')
)
];
}
)
]
),
drawer: Drawer(
child: Column(),
),
body: Container(),
)
- Scaffold:Material Design 布局結構的基本實現(xiàn)
- appbar: Appbar:引用程序欄
- title:標題
- actions:操作部件
- PopupMenuButton:彈出菜單按鈕
- PopupMenuItem:菜單項
- PopupMenuButton:彈出菜單按鈕
- drawer: Drawer:側邊抽屜導航
- body:應用程序主體
- appbar: Appbar:引用程序欄
表單部件(Forms and Inputs)
我們通過表單來收集用戶填寫的相關信息,和 Web 中的表單類似,F(xiàn)lutter 提供了表單中常用的文本輸入、單選、多選和開關等部件,并且它也提供了基本的表單校驗的功能,以及滿足我們通常的業(yè)務。
Form(
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
validator: (value) {
if(value.isEmpty) {
return 'Please enter some text';
}
},
),
Checkbox(
value: false,
onChanged: (value) { },
),
Radio(
value: false,
groupValue: 0,
onChanged: (value) { },
),
Switch(
value: false,
onChanged: (value) {},
),
RaisedButton(
child: Text('Submit'),
color: Colors.black,
textColor: Colors.white,
onPressed: (){
if(_formKey.currentState.validate()) {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('OK'),
));
}
},
)
],
),
);
布局部件(Layout)
布局部件在 Flutter 中多達 30 個,個人對這種復雜繁多的設計并不是非常滿意(或許它有自己的好處),但是對于開發(fā)人員來說非常不友好。在布局部件中 Flutter 分兩大類:一類是單個子元素的布局部件;另一類是多個子元素的布局部件。我們簡單介紹幾個較為常用的部件:
- Single-child layout widgets
- Container:
- Padding:
- Center:
- Positioned:
- Multi-child layout widgets
- Column:
- Row:
- Stack:
- GridView:
Container
Container 類似于 Web 中的 <div>
,我們可以給它設置 height
、width
、padding
、margin
等屬性:
Container(
child: Text('Container'),
padding: EdgeInsets.all(10),
margin: EdgeInsets.all(30),
height: 250,
width: 200,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.red,
border: Border.all(
width: 3,
color: Colors.blue
),
borderRadius: BorderRadius.all(
Radius.circular(20)
),
image: DecorationImage(
image: NetworkImage('https://metaimg.baichanghui.com/appdownload.jpg')
)
),
transform: Matrix4.rotationZ(-0.1),
)
Padding
Padding 可以用來給部件設置內邊距,搞不懂 Flutter 的設計思想:
Padding(
padding: EdgeInsets.all(20),
child: Container(
child: Text('Padding'),
color: Colors.green,
),
)
Center
What?
Center(
child: Text('Center'),
)
Positioned
Positioned 部件可以對它設置相對位置,讓它位于屏幕的不同地方,不過它需要與 Stack 部件配合使用:
Positioned(
left: 100,
top: 100,
child: Container(
color: Colors.green,
height: 80,
width: 80,
),
)
Column
Column 是縱向布局部件,可以讓你對多個部件進行縱向布局,在它內部的子部件會沿屏幕的縱向一個一個顯示出來:
Column(
children: <Widget>[
Container(
color: Colors.red,
height: 100,
width: 100,
),
Container(
color: Colors.blue,
height: 100,
width: 100,
),
Container(
color: Colors.green,
height: 100,
width: 100,
),
],
)
Row
Row 剛好和 Column 相反,可以讓你對多個部件進行橫向布局,在它內部的子部件會沿屏幕的橫向一個一個顯示出來:
Row(
children: <Widget>[
Container(
color: Colors.red,
height: 100,
width: 100,
),
Container(
color: Colors.blue,
height: 100,
width: 100,
),
Container(
color: Colors.green,
height: 100,
width: 100,
),
],
)
Stack
Stack 堆疊布局,在 Stack 內部的子部件,會堆疊在一起,我們可以配合 Positioned 部件來設置堆疊于 Stack 內部的位置:
Stack(
children: <Widget>[
Container(
color: Colors.red,
height: 250,
width: 250,
),
Container(
color: Colors.blue,
height: 150,
width: 150,
),
Positioned(
left: 100,
top: 100,
child: Container(
color: Colors.green,
height: 80,
width: 80,
),
)
],
)
GridView
GridView 珊格布局,它在 Web 的響應式設計中經常使用,他的好處在于,可以適應不同的屏幕:
GridView.count(
crossAxisCount: 2,
children: List.generate(20, (index) {
return Text(index.toString());
}),
)
提醒彈出框部件(Dialogs and Alerts)
提醒和彈出框部件,App 相比起 Web 更加的豐富多樣,Web 原生提供的也比較少,我們看看 App 中比較常用的幾個:
- AlertDialog
- SnackBar
- BottomSheet
AlertDialog
AlertDialog 類似于瀏覽器中的 alert,只不過它可以自定義一些按鈕來做不同的事情:
FlatButton(
child: Text('Show Dialog'),
onPressed: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Title2'),
content: Text('Content2'),
actions: <Widget>[
FlatButton(
child: Text('OK'),
onPressed: () {
// do something
}
)
]
)
);
},
)
SnackBar
SnackBar 是類似于很多 UI 框架中的提醒框,它屬于非阻斷性的提醒:
RaisedButton(
child: Text('Show SnackBar'),
color: Colors.black,
textColor: Colors.white,
onPressed: (){
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('Hello!'),
));
},
)
BottomSheet
BottomSheet 是從屏幕底部彈出來的頁面,你可以通過它來做一些類似與 Select 部件的功能,當然它內部的內容是可以自定義的:
RaisedButton(
child: Text('Show Bottom Sheet'),
color: Colors.black,
textColor: Colors.white,
onPressed: () {
showBottomSheet(
context: context,
builder: (context){
return Container(
width: MediaQuery.of(context).size.width,
child: Text('BottomSheet'),
);
}
);
},
)
代碼:
總結
Interaction Models、Animation and Motion、Painting and effects 這些都屬于比較高級的部件,我們在未來文章中再做介紹。回顧 Flutter 如此繁多的部件,再加上它的有狀態(tài)部件和無狀態(tài)部件之分,學習成本相對來說比較高,每一種部件還有這不同的功能屬性,這種設計的確講究軟件設計的職責單一,但是在復雜的 UI 場景會導致大量的代碼嵌套,代碼的維護成本也會隨之上升,在 Web 的 Table 布局時代就出現(xiàn)過這樣的災難。作為 Web 工程師,可能對 Flutter 這樣的設計非常抱怨,HTML 和 CSS 將組件和樣式分離開來,從學習的角度上看所有組件有著它自己的功能,而樣式只是組件的附加值,從某種角度上來說這是兩個是解藕的,但是 Flutter 部件的學習過程中需要去了解各個部件自己的長相如何設置,在這一點 Web 學習的復雜成本會降低。另外將樣式分離開來后,代碼嵌套也會隨之減少,同樣是 DIV 我們可以給它賦予布局、邊框、背景、陰影、邊距、剪切等樣式,但是 Flutter 部件要實現(xiàn)這些功能需要嵌套多個部件才能完成。個人認為 Flutter 在這方面需要繼續(xù)改進,已迎合現(xiàn)在互聯(lián)網工程師的口味,這樣才能推廣開來。
〖堅持的一俢〗