在本欄的前面章節的學習中,我們基本上把Flutter中所有的常用佈局、元件包括多頁面跳轉路由都介紹過了,細心的讀者可能會發現在前面的課程中我們每次新建一個
Flutter Page
的時候都會在根佈局的build
方法中直接return一個Scaffold
然後,再通過配置Scaffold中的相關屬性來快速的渲染頁面佈局:沒錯Scaffold的出現就是為了幫助我們實現基本的 Material Design 佈局結構,簡化我們頁面開發工作,我們可以形象的把Scaffold
理解為頁面搭建的腳手架
課程學習目標
瞭解並掌握Scaffold中提供的快速搭建頁面的腳手架方法
appBar
: 顯示在介面頂部的一個選單導航欄body
:頁面中顯示主要內容的區域,可任意指定WidgetfloatingActionButton
: 懸浮按鈕(類似原生Android中的floatingActionButton)drawer、endDrawer
:分別用於在左右兩側邊欄展示抽屜選單bottomNavigationBar
:顯示在底部的導航欄按鈕欄
在Flutter腳手架中給我們提供了上述用於快速構建頁面的常用屬性,開發者可根據自己的頁面需求,選擇性的引入不同屬性達到定製出不同UI呈現的目的,關於Scaffold中的其他屬性,我就不逐個講解了,下面我結合程式碼跟大家一塊測試下上述常用方法。
Scaffold課程完整效果圖

1. appBar
在前面的每一節課程中我們都可以找到appBar的身影,但是由於之前的課程重點不在appBar上,所以我們並沒有對appBar展開過多的介紹,就僅僅是作為展示頁面標題使用。
我們先來看看Scaffold原始碼中對appBar的解釋說明:一個展示在Scaffold頂部的APP Bar
/// An app bar to display at the top of the scaffold.
final PreferredSizeWidget appBar;
複製程式碼
通常我們會給根據不同的業務場景設定不同的AppBar,關於AppBar的構造方法大部分屬性讀者都可以根據屬性名自解釋,我就不貼出來贅述了,下面我舉幾個常用的例子
1.1 設定Title
appBar: AppBar(
title: Text("Scaffold 腳手架"),
),
複製程式碼
標題居中:
appBar: AppBar(
title: Text("Scaffold 腳手架"),
centerTitle: true,
),
複製程式碼


1.2設定左上角返回按鈕
從上面的程式碼以及圖片中我們看到Scaffold中預設為我們在appBar上指定了一個返回的箭頭,點選箭頭返回上一頁,當然我們可以通過leading
屬性自定義左上角的圖示,但是當我們指定了leading
點選事件就需要我們自己單獨處理了,也就是說,指定了leading
之後,我們就沒辦法在點選箭頭的時候結束當前頁,但是我們可以自己實現這一操作。
appBar: AppBar(
leading: GestureDetector(child: Icon(Icons.print),onTap: (){
Navigator.of(context).pop();
}), //新增leading之後需要重寫點選事件喚
title: Text("Scaffold 腳手架"),
centerTitle: true,
),
複製程式碼

1.3 右側溢位選單
在Flutter中我們通過Appbar的 actions屬性設定選單項,一般重要的選單選項我們會直接放在右邊bar上顯示,非重要功能選項我們會通過PopupMenuButton以三個小點的形式放進摺疊選單裡,下面我們結合原始碼看下效果圖,讀者一看便知。
appBar: AppBar(
leading: GestureDetector(child: Icon(Icons.print),onTap: (){
Navigator.of(context).pop();
}), //新增leading之後需要重寫點選事件喚起抽屜選單
title: Text("Scaffold 腳手架"),
actions: <Widget>[
IconButton(icon: Icon(Icons.message), onPressed: () {}),
IconButton(icon: Icon(Icons.access_alarm), onPressed: () {}),
PopupMenuButton(
onSelected: (String value) {
print('-----------------$value');
},
itemBuilder: (BuildContext context) => [
new PopupMenuItem(value: "選項一的內容", child: new Text("選項一")),
new PopupMenuItem(value: "選項二的內容", child: new Text("選項二")),
new PopupMenuItem(value: "選項三的內容", child: new Text("選項三")),
])
],
),
複製程式碼

1.4 標題欄底部TabBar
在原生Android中我們很熟悉的就是利用TabLayout設定標題欄下方的tab切換效果,在Flutter中我們通過給AppBar的bottom屬性設定TabBar來完成這一效果。
bottom: TabBar(
controller: _tabController,
tabs: topTabLists
.map((element) => Tab(
text: element,
))
.toList(),
// onTap: (index) => {},
)),
複製程式碼
使用TabBar必須傳入controller屬性,我們通過initState()方法初始化_tabController
@override
void initState() {
super.initState();
//初始化頂部TabController
_tabController = TabController(length: topTabLists.length, vsync: this);
}
複製程式碼

bottom: TabBar(
controller: _tabController,
tabs: topTabLists
.map((element) => Tab(
text: element,
icon: Icon(Icons.print),
))
.toList(),
// onTap: (index) => {},
)),
複製程式碼

2.body部分
根據Scaffold頁面佈局的上下順序,下面我們講解第二部分body
部分。關於body部分在前面的課程中,我們使用了很多次了,也沒什麼需要特別說的地方,這裡我就不展開介紹了,body作為一個頁面的主要檢視部分,可以傳入任意指定的Widget,可現實在螢幕中央,這裡我就不過多贅述了。
body: Container(
child: Text("內容區域"),
),
複製程式碼
3.抽屜選單
在文章開題的時候,我提到了抽屜選單可以通過drawer、endDrawer指定左右兩側開啟的抽屜選單。下面我們就來看下效果。
左側抽屜
drawer: MyDrawer(), //MyDrawer詳細程式碼在下面
複製程式碼
右側抽屜
endDrawer: MyDrawer(), //MyDrawer詳細程式碼在下面
複製程式碼


MyDrawer
程式碼如下
class MyDrawer extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Drawer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 88.0, bottom: 30.0),
child: Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: ClipOval(
child: Image.network(
"https://avatar.csdn.net/6/0/6/3_xieluoxixi.jpg",
width: 60,
),
),
),
Text(
"謝棟",
style: TextStyle(fontWeight: FontWeight.bold),
)
],
),
),
Expanded(
child: ListView(
children: <Widget>[
ListTile(
leading: const Icon(Icons.settings),
title: const Text('個人設定'),
),
ListTile(
leading: const Icon(Icons.live_help),
title: const Text('幫助說明'),
),
ListTile(
leading: const Icon(Icons.settings),
title: const Text('個人設定'),
),
ListTile(
leading: const Icon(Icons.live_help),
title: const Text('幫助說明'),
),
],
),
)
],
));
}
}
複製程式碼
4.floatingActionButton
floatingActionButton在前面講解Button章節中我們講解過,知識點也比較簡單,就是在頁面佈局快速構建出一個懸浮按鈕。
floatingActionButton: FloatingActionButton(
onPressed: (){
print("---------");
},
child: Icon(Icons.add),
),
複製程式碼


floatingActionButtonLocation:
FloatingActionButtonLocation.centerDocked, //設定FloatingActionButton的位置
);
複製程式碼

5.底部導航欄 bottomNavigationBar
bottomNavigationBar的使用場景還比較多,一般我們的多頁面app都會通過底部的Tab來切換App首頁展示的不同內容,在Flutter的Scaffold中為我們提供了快捷用於構建底部Tab的方法,我們通過給BottomNavigationBar的Items
屬性設定需要展示的BottomNavigationBarItem陣列即可。
bottomNavigationBar: BottomNavigationBar(
//不設定該屬性多於三個不顯示顏色
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), title: Text("首頁")),
BottomNavigationBarItem(icon: Icon(Icons.message), title: Text("訊息")),
BottomNavigationBarItem(
icon: Icon(Icons.add_a_photo), title: Text("動態")),
BottomNavigationBarItem(icon: Icon(Icons.person), title: Text("我的"))
],
currentIndex: _currentBottomIndex,
fixedColor: Colors.blue,
onTap: (index) => _onBottomTabChange(index),
),
複製程式碼

多於三個就變成白色了,不能正常顯示
。


這裡我從原始碼角度上給讀者解讀一下。
/// If [fixedColor] is null then the theme's primary color,
/// [ThemeData.primaryColor], is used. However if [BottomNavigationBar.type] is
/// [BottomNavigationBarType.shifting] then [fixedColor] is ignored.
BottomNavigationBar({
Key key,
@required this.items,
this.onTap,
this.currentIndex = 0,
BottomNavigationBarType type,
this.fixedColor,
this.iconSize = 24.0,
}) : assert(items != null),
assert(items.length >= 2),
assert(
items.every((BottomNavigationBarItem item) => item.title != null) == true,
'Every item must have a non-null title',
),
assert(0 <= currentIndex && currentIndex < items.length),
assert(iconSize != null),
type = type ?? (items.length <= 3 ? BottomNavigationBarType.fixed : BottomNavigationBarType.shifting),
super(key: key);
複製程式碼

BottomNavigationBar中的items數量小於等於3時,type為BottomNavigationBarType.fixed,大於3則為BottomNavigationBarType.shifting
,所以我們只需在程式碼中過載type屬性,大於3個的時候設定type值為BottomNavigationBarType.fixed
即可。我在一開始的程式碼註釋中也解釋了這個問題。
UI小特效 在實現底部導航欄時,Flutter還為我們提供了一個Material元件中的類似‘"鑲嵌"效果,使用BottomAppBar配合FloatingActionButton完成,文字描述可能雲裡霧裡的。
一圖勝千言

//與FloatingActionButton配合實現"打洞"效果
bottomNavigationBar: BottomAppBar(
color: Colors.white,
shape: CircularNotchedRectangle(), // 底部導航欄打一個圓形的洞
child: Row(
children: [
Tab(text: "首頁", icon: Icon(Icons.home)),
Tab(text: "訊息", icon: Icon(Icons.message)),
Tab(text: "動態", icon: Icon(Icons.add_a_photo)),
Tab(text: "我的", icon: Icon(Icons.person)),
],
mainAxisAlignment: MainAxisAlignment.spaceAround, //均分底部導航欄橫向空間
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => _onFabClick,
child: Icon(Icons.add),
),
floatingActionButtonLocation:
FloatingActionButtonLocation.centerDocked, //設定FloatingActionButton的位置
);
複製程式碼
雖然我們在上面藉助BottomNavigationBar
也實現了類似的效果,但是前者是直接壓在導航欄上面的,而後者是嵌入進去的,效果更逼真。讀者可對比下方的效果圖。

