好久不見了,這陣子在忙公司的專案,加班比較嚴重,這周終於抽了點時間來幫國外一家公司做一款跨平臺的 App。由於去年九月份在上海參加過 Google 舉辦的 Google develop days, 受益頗多,特別在其目前正在大力熱推的 Flutter 框架。相比於目前熱門的跨平臺框架 React Native,Flutter在 UI 繪製以及效能方便不遑多讓。因此,這款 app 打算基於 Dart 語言,並採用 Flutter 框架來完成。
花了大概幾天時間熟悉了下 Dart 語法和 Flutter 基本組成控制元件,就開始摸索著做一個練手專案。AppBar 想必大家都用過,當其處於啟動頁時是隱藏的,而處於其他頁面時左邊預設攜帶來返回按鈕。目前,我們的需求是:首頁的 AppBar 最左邊為使用者頭像,點選使用者頭像可以自動開啟左側抽屜欄。
最終效果圖如下所示:
初學者一開始總是痛苦的,還好,解決問題的途徑“萬變不離其宗”。我們先來檢視一下 Flutter 官方文件 發現,要使用 AppBar來操作頁面,決定其左邊點選事件的屬性就是 leading
:
AppBar({
Key key,
Widget leading,
bool automaticallyImplyLeading: true,
Widget title,
List<Widget> actions,
Widget flexibleSpace,
PreferredSizeWidget bottom,
double elevation,
Color backgroundColor,
Brightness brightness,
IconThemeData iconTheme,
TextTheme textTheme,
bool primary: true,
bool centerTitle,
double titleSpacing: NavigationToolbar.kMiddleSpacing,
double toolbarOpacity: 1.0,
double bottomOpacity: 1.0
})
複製程式碼
可以看到,我們 AppBar 中的 leading
屬性是一個 Widget
型別,那麼它接收的引數範圍就很廣了,換句話說,我們可以直接將一個按鈕傳遞給它,並在 onPressed
方法中處理左邊側滑欄的開啟和關閉狀態。?,有思路了我們們久開始試驗一下:
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey();
@override
Widget build(BuildContext context) {
return new DefaultTabController(
length: _pages.length,
child: new Scaffold(
key: _scaffoldKey,
appBar: new AppBar(
/// 第一種方式
/// 通過可監聽點選的IconButton傳入widget,
/// 並在onPressed中處理drawer開啟,藉助於GlobalKey
leading: new IconButton(
icon: new Container(
padding: EdgeInsets.all(3.0),
child: new CircleAvatar(
radius: 30.0,
backgroundImage: AssetImage("assets/images/moosphon_logo.jpeg")
),
),
onPressed: (){
_scaffoldKey.currentState.openDrawer();
},
),
centerTitle: true,
title: new TabBar(
isScrollable: true,
labelPadding: EdgeInsets.all(14),
indicatorSize: TabBarIndicatorSize.label,
indicatorColor: Colors.white,
tabs: _titles.map((String title) =>
new Tab(text: title)).toList()
),
actions: <Widget>[
new IconButton(
icon: new Icon(Icons.search),
tooltip: '搜尋',
onPressed: (){
NavigatorUtil.intentToPage(context, new SearchPage(), pageName: "SearchPage");
}
)
],
),
body: new TabBindingView(),
drawer: new Drawer(
child: new HomeLeftDrawerPage(),
),
)
);
}
複製程式碼
為了不影響篇幅,這裡我只貼了關鍵程式碼,至於 Drawer
用法相信大家沒什麼問題,這裡我們給 AppBar 的leading
引數傳了一個 IconButton
控制元件,裡面我還實現了圓形頭像。這樣一來,我們的使用者頭像就可以被點選了響應了,接下來我們需要處理頭像點選事件與 Drawer
的聯動性了。這裡我們通過設定 GlobalKey
來讓 Scaffold
容器獲取到其內部的 Drawer
元件,進而控制它的開閉,這樣,我們就已經可以通過點選自定義的使用者頭像來開啟左邊側滑欄啦?。
細心的小夥伴一定會發現,上面的程式碼中我註釋這只是第一種實現方式,難道還有第二種實現方式?哈哈,沒錯,的確還有第二種思路。其實,通過檢視 AppBar
的原始碼,我們可以發現:AppBar內部已經自動實現了 AppBar 的 leading
與 Drawer
抽屜欄的關聯:
Widget leading = widget.leading;
if (leading == null && widget.automaticallyImplyLeading) {
if (hasDrawer) {
leading = IconButton(
icon: const Icon(Icons.menu),
onPressed: _handleDrawerButton,
tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip,
);
} else {
if (canPop)
leading = useCloseButton ? const CloseButton() : const BackButton();
}
}
if (leading != null) {
leading = ConstrainedBox(
constraints: const BoxConstraints.tightFor(width: _kLeadingWidth),
child: leading,
);
}
複製程式碼
由於程式碼較多,這裡我只放上了 build
方法中關於 leading
部分的關鍵程式碼,通過分析我們可以發現:
leading
如果 為空並且automaticallyImplyLeading
屬性為true,那麼就會自動推斷出leading
的Widget
的型別和用途,另外如果當前Scaffold
中存在Drawer
,則會自動建立一個IconButton
作為leading
使用,同時它的點選事件中處理了與Drawer
抽屜欄的關聯事件,無需開發者再處理。也就是說,在這種情況下,如果我們自定義了leading
(例如當前我們給它傳的是使用者的頭像Widget
),那麼leading
就無法自動關聯Drawer
,也就是說關聯Drawer
的這部分程式碼需要開發者自行實現。- 如果
leading
不為空呢?並且automaticallyImplyLeading
開關關閉,那麼leading
的空間就會被title
給佔據。
說了這麼多,最終的結論是:如果我們想要自定義 leading
,那麼目前官方原始碼中 AppBar 中 leading
自動關聯 Drawer
的處理我們沒辦法使用。如果我們非要用呢?那就只能修改原始碼啦:
/// 注意,前方高能來襲,以下為修改的部分code
Widget leading = widget.leading;
if (/*leading == null && */widget.automaticallyImplyLeading) {
if (hasDrawer) {
leading = IconButton(
icon: /*const Icon(Icons.menu)*/ leading ?? Icon(Icons.home), // 如果leading指定了widget那麼
onPressed: _handleDrawerButton,
tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip,
);
} else {
if (canPop)
leading = useCloseButton ? const CloseButton() : const BackButton();
}
}
if (leading != null) {
leading = ConstrainedBox(
constraints: const BoxConstraints.tightFor(width: _kLeadingWidth),
child: leading,
);
}
複製程式碼
鐺鐺鐺鐺~這就改好啦,改動的地方是不是很少?哈哈?,其實只要把 leading
為空的限制條件去掉,並且可以傳入我們自己定義的 Widget
就好啦!
這樣一來,我們就不用手動去處理 leading
與 Drawer
的關聯事件,只需要交給系統幫我們完成就可以啦:
@override
Widget build(BuildContext context) {
return new DefaultTabController(
length: _pages.length,
child: new Scaffold(
key: _scaffoldKey,
appBar: new DrawerAutoBindingAppBar(
/// 第二種方式
/// 通過修改[AppBar]原始碼來將普通widget作為icon傳給IconButton
/// 原始碼中已處理onPressed關聯drawer事件,無需額外處理,詳情見[DrawerAutoBindingAppBar]
leading: new Container(
padding: EdgeInsets.all(3.0),
child: new CircleAvatar(
radius: 30.0,
backgroundImage: AssetImage("assets/images/moosphon_logo.jpeg")
)
),
centerTitle: true,
title: new TabBar(
isScrollable: true,
labelPadding: EdgeInsets.all(14),
indicatorSize: TabBarIndicatorSize.label,
indicatorColor: Colors.white,
tabs: _titles.map((String title) =>
new Tab(text: title)).toList()
),
actions: <Widget>[
new IconButton(
icon: new Icon(Icons.search),
tooltip: '搜尋',
onPressed: (){
NavigatorUtil.intentToPage(context, new SearchPage(), pageName: "SearchPage");
}
)
],
),
body: new TabBindingView(),
drawer: new Drawer(
child: new HomeLeftDrawerPage(),
),
)
);
}
複製程式碼
程式碼就不多做解釋了,效果是和之前一樣的,好啦,這樣我們就更加了解 AppBar 的用法以及 leading
的背後“小彩蛋”啦?。
感謝大家的閱讀,以後我將給大家帶來更多好玩實用的應用型開發技巧,如果大家有任何建議,歡迎留言?。
QQ群: 601924443 一起玩技術~