Material 組件庫提供了豐富多樣的組件,本節(jié)介紹一些常用的組件,其余的讀者可以自行查看文檔或 Flutter Gallery 中 Material 組件部分的示例。
Flutter Gallery 是 Flutter 官方提供的 Flutter Demo,源碼位于 flutter 源碼中的 examples 目錄下,筆者強烈建議用戶將 Flutter Gallery 示例跑起來,它是一個很全面的 Flutter 示例應(yīng)用,是非常好的參考 Demo,也是筆者學(xué)習 Flutter 的第一手資料。
一個完整的路由頁可能會包含導(dǎo)航欄、抽屜菜單(Drawer)以及底部Tab導(dǎo)航菜單等。如果每個路由頁面都需要開發(fā)者自己手動去實現(xiàn)這些,這會是一件非常麻煩且無聊的事。幸運的是,F(xiàn)lutter Material組件庫提供了一些現(xiàn)成的組件來減少我們的開發(fā)任務(wù)。Scaffold
是一個路由頁的骨架,我們使用它可以很容易地拼裝出一個完整的頁面。
我們實現(xiàn)一個頁面,它包含:
最終效果如圖5-18、圖5-19所示:
實現(xiàn)代碼如下:
class ScaffoldRoute extends StatefulWidget {
@override
_ScaffoldRouteState createState() => _ScaffoldRouteState();
}
class _ScaffoldRouteState extends State<ScaffoldRoute> {
int _selectedIndex = 1;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar( //導(dǎo)航欄
title: Text("App Name"),
actions: <Widget>[ //導(dǎo)航欄右側(cè)菜單
IconButton(icon: Icon(Icons.share), onPressed: () {}),
],
),
drawer: new MyDrawer(), //抽屜
bottomNavigationBar: BottomNavigationBar( // 底部導(dǎo)航
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Home')),
BottomNavigationBarItem(icon: Icon(Icons.business), title: Text('Business')),
BottomNavigationBarItem(icon: Icon(Icons.school), title: Text('School')),
],
currentIndex: _selectedIndex,
fixedColor: Colors.blue,
onTap: _onItemTapped,
),
floatingActionButton: FloatingActionButton( //懸浮按鈕
child: Icon(Icons.add),
onPressed:_onAdd
),
);
}
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
void _onAdd(){
}
}
上面代碼中我們用到了如下組件:
組件名稱 | 解釋 |
---|---|
AppBar | 一個導(dǎo)航欄骨架 |
MyDrawer | 抽屜菜單 |
BottomNavigationBar | 底部導(dǎo)航欄 |
FloatingActionButton | 漂浮按鈕 |
下面我們來分別介紹一下它們。
AppBar
是一個 Material 風格的導(dǎo)航欄,通過它可以設(shè)置導(dǎo)航欄標題、導(dǎo)航欄菜單、導(dǎo)航欄底部的Tab標題等。下面我們看看 AppBar 的定義:
AppBar({
Key key,
this.leading, //導(dǎo)航欄最左側(cè)Widget,常見為抽屜菜單按鈕或返回按鈕。
this.automaticallyImplyLeading = true, //如果leading為null,是否自動實現(xiàn)默認的leading按鈕
this.title,// 頁面標題
this.actions, // 導(dǎo)航欄右側(cè)菜單
this.bottom, // 導(dǎo)航欄底部菜單,通常為Tab按鈕組
this.elevation = 4.0, // 導(dǎo)航欄陰影
this.centerTitle, //標題是否居中
this.backgroundColor,
... //其它屬性見源碼注釋
})
如果給Scaffold
添加了抽屜菜單,默認情況下Scaffold
會自動將AppBar
的leading
設(shè)置為菜單按鈕(如上面截圖所示),點擊它便可打開抽屜菜單。如果我們想自定義菜單圖標,可以手動來設(shè)置leading
,如:
Scaffold(
appBar: AppBar(
title: Text("App Name"),
leading: Builder(builder: (context) {
return IconButton(
icon: Icon(Icons.dashboard, color: Colors.white), //自定義圖標
onPressed: () {
// 打開抽屜菜單
Scaffold.of(context).openDrawer();
},
);
}),
...
)
代碼運行效果如圖5-20所示:
可以看到左側(cè)菜單已經(jīng)替換成功。
代碼中打開抽屜菜單的方法在ScaffoldState
中,通過Scaffold.of(context)
可以獲取父級最近的Scaffold
組件的State
對象。
下面我們通過“bottom”屬性來添加一個導(dǎo)航欄底部 Tab 按鈕組,將要實現(xiàn)的效果如圖5-21所示:
Material 組件庫中提供了一個TabBar
組件,它可以快速生成Tab
菜單,下面是上圖對應(yīng)的源碼:
class _ScaffoldRouteState extends State<ScaffoldRoute>
with SingleTickerProviderStateMixin {
TabController _tabController; //需要定義一個Controller
List tabs = ["新聞", "歷史", "圖片"];
@override
void initState() {
super.initState();
// 創(chuàng)建Controller
_tabController = TabController(length: tabs.length, vsync: this);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
... //省略無關(guān)代碼
bottom: TabBar( //生成Tab菜單
controller: _tabController,
tabs: tabs.map((e) => Tab(text: e)).toList()
),
),
... //省略無關(guān)代碼
}
上面代碼首先創(chuàng)建了一個TabController
,它是用于控制/監(jiān)聽Tab
菜單切換的。接下來通過 TabBar 生成了一個底部菜單欄,TabBar
的tabs
屬性接受一個 Widget 數(shù)組,表示每一個 Tab 子菜單,我們可以自定義,也可以像示例中一樣直接使用Tab
組件,它是 Material 組件庫提供的 Material 風格的 Tab 菜單。
Tab
組件有三個可選參數(shù),除了可以指定文字外,還可以指定Tab菜單圖標,或者直接自定義組件樣式。Tab
組件定義如下:
Tab({
Key key,
this.text, // 菜單文本
this.icon, // 菜單圖標
this.child, // 自定義組件樣式
})
開發(fā)者可以根據(jù)實際需求來定制。
通過TabBar
我們只能生成一個靜態(tài)的菜單,真正的 Tab 頁還沒有實現(xiàn)。由于Tab
菜單和 Tab 頁的切換需要同步,我們需要通過TabController
去監(jiān)聽 Tab 菜單的切換去切換 Tab 頁,代碼如:
_tabController.addListener((){
switch(_tabController.index){
case 1: ...;
case 2: ... ;
}
});
如果我們 Tab 頁可以滑動切換的話,還需要在滑動過程中更新 TabBar 指示器的偏移!顯然,要手動處理這些是很麻煩的,為此,Material 庫提供了一個TabBarView
組件,通過它不僅可以輕松的實現(xiàn) Tab 頁,而且可以非常容易的配合 TabBar 來實現(xiàn)同步切換和滑動狀態(tài)同步,示例如下:
Scaffold(
appBar: AppBar(
... //省略無關(guān)代碼
bottom: TabBar(
controller: _tabController,
tabs: tabs.map((e) => Tab(text: e)).toList()),
),
drawer: new MyDrawer(),
body: TabBarView(
controller: _tabController,
children: tabs.map((e) { //創(chuàng)建3個Tab頁
return Container(
alignment: Alignment.center,
child: Text(e, textScaleFactor: 5),
);
}).toList(),
),
... // 省略無關(guān)代碼
)
運行后效果如圖5-22所示:
現(xiàn)在,無論是點擊導(dǎo)航欄 Tab 菜單還是在頁面上左右滑動,Tab 頁面都會切換,并且 Tab 菜單的狀態(tài)和 Tab 頁面始終保持同步!那它們是如何實現(xiàn)同步的呢?細心的讀者可能已經(jīng)發(fā)現(xiàn),上例中TabBar
和TabBarView
的controller
是同一個!正是如此,TabBar
和TabBarView
正是通過同一個controller
來實現(xiàn)菜單切換和滑動狀態(tài)同步的,有關(guān)TabController
的詳細信息,我們不在本書做過多介紹,使用時讀者直接查看 SDK 即可。
另外,Material 組件庫也提供了一個PageView
組件,它和TabBarView
功能相似,讀者可以自行了解一下。
Scaffold
的drawer
和endDrawer
屬性可以分別接受一個 Widget 來作為頁面的左、右抽屜菜單。如果開發(fā)者提供了抽屜菜單,那么當用戶手指從屏幕左(或右)側(cè)向里滑動時便可打開抽屜菜單。本節(jié)開始部分的示例中實現(xiàn)了一個左抽屜菜單MyDrawer
,它的源碼如下:
class MyDrawer extends StatelessWidget {
const MyDrawer({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Drawer(
child: MediaQuery.removePadding(
context: context,
//移除抽屜菜單頂部默認留白
removeTop: true,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 38.0),
child: Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: ClipOval(
child: Image.asset(
"imgs/avatar.png",
width: 80,
),
),
),
Text(
"Wendux",
style: TextStyle(fontWeight: FontWeight.bold),
)
],
),
),
Expanded(
child: ListView(
children: <Widget>[
ListTile(
leading: const Icon(Icons.add),
title: const Text('Add account'),
),
ListTile(
leading: const Icon(Icons.settings),
title: const Text('Manage accounts'),
),
],
),
),
],
),
),
);
}
}
抽屜菜單通常將Drawer
組件作為根節(jié)點,它實現(xiàn)了 Material 風格的菜單面板,MediaQuery.removePadding
可以移除 Drawer 默認的一些留白(比如 Drawer 默認頂部會留和手機狀態(tài)欄等高的留白),讀者可以嘗試傳遞不同的參數(shù)來看看實際效果。抽屜菜單頁由頂部和底部組成,頂部由用戶頭像和昵稱組成,底部是一個菜單列表,用 ListView 實現(xiàn),關(guān)于 ListView 我們將在后面“可滾動組件”一節(jié)詳細介紹。
FloatingActionButton
是 Material 設(shè)計規(guī)范中的一種特殊 Button,通常懸浮在頁面的某一個位置作為某種常用動作的快捷入口,如本節(jié)示例中頁面右下角的"?"號按鈕。我們可以通過Scaffold
的floatingActionButton
屬性來設(shè)置一個FloatingActionButton
,同時通過floatingActionButtonLocation
屬性來指定其在頁面中懸浮的位置,這個比較簡單,不再贅述。
我們可以通過Scaffold
的bottomNavigationBar
屬性來設(shè)置底部導(dǎo)航,如本節(jié)開始示例所示,我們通過 Material 組件庫提供的BottomNavigationBar
和BottomNavigationBarItem
兩種組件來實現(xiàn) Material 風格的底部導(dǎo)航欄??梢钥吹缴厦娴膶崿F(xiàn)代碼非常簡單,所以不再贅述,但是如果我們想實現(xiàn)如圖5-23所示效果的底部導(dǎo)航欄應(yīng)該怎么做呢?
Material組件庫中提供了一個BottomAppBar
組件,它可以和FloatingActionButton
配合實現(xiàn)這種“打洞”效果,源碼如下:
bottomNavigationBar: BottomAppBar(
color: Colors.white,
shape: CircularNotchedRectangle(), // 底部導(dǎo)航欄打一個圓形的洞
child: Row(
children: [
IconButton(icon: Icon(Icons.home)),
SizedBox(), //中間位置空出
IconButton(icon: Icon(Icons.business)),
],
mainAxisAlignment: MainAxisAlignment.spaceAround, //均分底部導(dǎo)航欄橫向空間
),
)
可以看到,上面代碼中沒有控制打洞位置的屬性,實際上,打洞的位置取決于FloatingActionButton
的位置,上面FloatingActionButton
的位置為:
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
所以打洞位置在底部導(dǎo)航欄的正中間。
BottomAppBar
的shape
屬性決定洞的外形,CircularNotchedRectangle
實現(xiàn)了一個圓形的外形,我們也可以自定義外形,比如,F(xiàn)lutter Gallery 示例中就有一個“鉆石”形狀的示例,讀者感興趣可以自行查看。
更多建議: