該文已授權(quán)公眾號(hào) 「碼個(gè)蛋」,轉(zhuǎn)載請指明出處
上一篇講完 Flutter
中的一些基本部件召烂,這篇就先填完上篇留下的沒寫的 AppBar
的坑碱工,以及 Scaffold
其他參數(shù)的使用,在開始前奏夫,先補(bǔ)一張縮略版的腦圖
完整版放在網(wǎng)盤怕篷,小伙伴自己下載。 完整版腦圖酗昼,提取碼:el9q廊谓,xmind 文件 提取碼:1o5d
AppBar(part2)
這一部分,我們只關(guān)注 Scaffold
中的 AppBar
剩下的還是埋坑【坑4】(/內(nèi)牛滿面麻削,居然已經(jīng)埋了那么多坑了蒸痹,坑雖多,代碼還是要繼續(xù)的)呛哟,因?yàn)樯院髸?huì)用到 StatefulWidget
的屬性叠荠,所以就直接先使用了,和 StatelessWidget
區(qū)別用法可以這么記 需要數(shù)據(jù)更新的界面用 StatefulWidget
扫责,當(dāng)然也不是絕對(duì)的榛鼎,就是之前留的【坑1】所說的狀態(tài)管理
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<String> _abs = ['A', 'B', 'S'];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true, // 標(biāo)題內(nèi)容居中
automaticallyImplyLeading: false, // 不使用默認(rèn)
leading: Icon(Icons.menu, color: Colors.red, size: 30.0), // 左側(cè)按鈕
flexibleSpace: Image.asset('images/app_bar_hor.jpg', fit: BoxFit.cover), // 背景
title: Text('AppBar Demo', style: TextStyle(color: Colors.red)), // 標(biāo)題內(nèi)容
// 末尾的操作按鈕列表
actions: <Widget>[
PopupMenuButton(
onSelected: (val) => print('Selected item is $val'),
icon: Icon(Icons.more_vert, color: Colors.red),
itemBuilder: (context) =>
List.generate(_abs.length, (index) => PopupMenuItem(value: _abs[index], child: Text(_abs[index]))))
],
),
);
}
}
最后的效果圖,未點(diǎn)擊右側(cè)按鈕如左側(cè)所示鳖孤,點(diǎn)擊右側(cè)按鈕會(huì)彈出相應(yīng)的 mune
該部分代碼查看 app_bar_main.dart
文件
看到效果圖者娱,相信很多小伙伴會(huì)吐槽,「**苏揣,上面那層半透明的啥玩意黄鳍,那么丑」,接下來我們來解決這個(gè)問題平匈,修改 void main
方法
void main() {
runApp(DemoApp());
// 添加如下代碼框沟,使?fàn)顟B(tài)欄透明
if (Platform.isAndroid) {
var style = SystemUiOverlayStyle(statusBarColor: Colors.transparent);
SystemChrome.setSystemUIOverlayStyle(style);
}
}
關(guān)閉后重新運(yùn)行藏古,就可以看到那層丑丑的「半透明蒙層」沒有了。
PopupMenuButton
這個(gè)部件忍燥,還是按照慣例看構(gòu)造函數(shù)
// itemBuilder
typedef PopupMenuItemBuilder<T> = List<PopupMenuEntry<T>> Function(BuildContext context);
// onSelected
typedef PopupMenuItemSelected<T> = void Function(T value);
const PopupMenuButton({
Key key,
@required this.itemBuilder, // 用于定義 menu 列表校翔,需要傳入 List<PopupMenuEntry<T>>
this.initialValue, // 初始值,是個(gè)泛型 T灾前,也就是類型和你傳入的值有關(guān)
this.onSelected, // 選中 item 的回調(diào)函數(shù),返回 T value孟辑,例如選中 `s` 則返回 s
this.onCanceled, // 未選擇任何 menu哎甲,直接點(diǎn)擊外側(cè)使 mune 列表關(guān)閉的回調(diào)
this.tooltip, // 長按時(shí)的提示
this.elevation = 8.0,
this.padding = const EdgeInsets.all(8.0),
this.child, // 用于自定義按鈕的內(nèi)容
this.icon, // 按鈕的圖標(biāo)
this.offset = Offset.zero, // 展示時(shí)候的便宜,Offset 需要傳入 x,y 軸偏移量饲嗽,會(huì)根據(jù)傳入值平移
})
AppBar - bottom
AppBar
還有個(gè) bottom
屬性沒講炭玫,因?yàn)?bottom
這個(gè)屬性和圖片背景一起使用會(huì)比較丑,所以就單獨(dú)拎出來講貌虾,我們直接在原來的代碼上修改
// 這里需要用 with 引入 `SingleTickerProviderStateMixin` 這個(gè)類
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
List<String> _abs = ['A', 'B', 'S'];
TabController _tabController; // TabBar 必須傳入這個(gè)參數(shù)
@override
void initState() {
super.initState();
// 引入 `SingleTickerProviderStateMixin` 類主要是因?yàn)?_tabController 需要傳入 vsync 參數(shù)
_tabController = TabController(length: _abs.length, vsync: this);
}
@override
void dispose() {
// 需要在界面 dispose 之前把 _tabController dispose吞加,防止內(nèi)存泄漏
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
automaticallyImplyLeading: false,
leading: Icon(Icons.menu, color: Colors.red, size: 30.0),
// flexibleSpace: Image.asset('images/app_bar_hor.jpg', fit: BoxFit.cover),
title: Text('AppBar Demo', style: TextStyle(color: Colors.red)),
actions: <Widget>[
PopupMenuButton(
offset: Offset(50.0, 100.0),
onSelected: (val) => print('Selected item is $val'),
icon: Icon(Icons.more_vert, color: Colors.red),
itemBuilder: (context) =>
List.generate(_abs.length, (index) => PopupMenuItem(value: _abs[index], child: Text(_abs[index]))))
],
bottom: TabBar(
labelColor: Colors.red, // 選中時(shí)的顏色
unselectedLabelColor: Colors.white, // 未選中顏色
controller: _tabController,
isScrollable: false, // 是否固定,當(dāng)超過一定數(shù)量的 tab 時(shí)尽狠,如果一行排不下衔憨,可設(shè)置 true
indicatorColor: Colors.yellow, // 導(dǎo)航的顏色
indicatorSize: TabBarIndicatorSize.tab, // 導(dǎo)航樣式,還有個(gè)選項(xiàng)是 TabBarIndicatorSize.label tab 時(shí)候袄膏,導(dǎo)航和 tab 同寬践图,label 時(shí)候,導(dǎo)航和 icon 同寬
indicatorWeight: 5.0, // 導(dǎo)航高度
tabs: List.generate(_abs.length, (index) => Tab(text: _abs[index], icon: Icon(Icons.android)))), // 導(dǎo)航內(nèi)容列表
),
);
}
}
最終的效果圖如下:
PageView + TabBar
那么如何通過 TabBar
切換界面呢沉馆,這邊我們需要用到 PageView
這個(gè)部件码党,當(dāng)然還有別的部件,例如 IndexStack
等斥黑,小伙伴可以自己嘗試使用別的揖盘,這邊通過 PageView
和 TabBar
進(jìn)行關(guān)聯(lián),帶動(dòng)頁面切換锌奴,PageViede
的屬性參數(shù)相對(duì)比較簡單兽狭,這邊就不貼啦。最終的效果我們目前只展示一個(gè)文字即可缨叫,我們先定義一個(gè)通用的切換界面
class TabChangePage extends StatelessWidget {
// 需要傳入的參數(shù)
final String content;
// TabChangePage(this.content); 不推薦這樣寫構(gòu)造方法
// 推薦用這樣的構(gòu)造方法椭符,key 可以作為唯一值查找
TabChangePage({Key key, this.content}) : super(key: key);
@override
Widget build(BuildContext context) {
// 僅展示傳入的內(nèi)容
return Container(
alignment: Alignment.center, child: Text(content, style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 30.0)));
}
}
定義通用界面后,就可以作為 PageView
的子界面?zhèn)魅氩⒄故?/p>
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
List<String> _abs = ['A', 'B', 'S'];
TabController _tabController;
// 用于同 TabBar 進(jìn)行聯(lián)動(dòng)
PageController _pageController;
@override
void initState() {
super.initState();
_tabController = TabController(length: _abs.length, vsync: this);
_pageController = PageController(initialPage: 0);
_tabController.addListener(() {
// 判斷 TabBar 是否切換位置了耻姥,如果切換了销钝,則修改 PageView 的顯示
if (_tabController.indexIsChanging) {
// PageView 的切換通過 controller 進(jìn)行滾動(dòng)
// duration 表示切換滾動(dòng)的時(shí)長,curve 表示滾動(dòng)動(dòng)畫的樣式琐簇,
// flutter 已經(jīng)在 Curves 中定義許多樣式蒸健,可以自行切換查看效果
_pageController.animateToPage(_tabController.index,
duration: Duration(milliseconds: 300), curve: Curves.decelerate);
}
});
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
automaticallyImplyLeading: false,
leading: Icon(Icons.menu, color: Colors.red, size: 30.0),
// flexibleSpace: Image.asset('images/app_bar_hor.jpg', fit: BoxFit.cover),
title: Text('AppBar Demo', style: TextStyle(color: Colors.red)),
actions: <Widget>[
PopupMenuButton(
offset: Offset(50.0, 100.0),
onSelected: (val) => print('Selected item is $val'),
icon: Icon(Icons.more_vert, color: Colors.red),
itemBuilder: (context) =>
List.generate(_abs.length, (index) => PopupMenuItem(value: _abs[index], child: Text(_abs[index]))))
],
bottom: TabBar(
labelColor: Colors.red,
unselectedLabelColor: Colors.white,
controller: _tabController,
isScrollable: false,
indicatorColor: Colors.yellow,
indicatorSize: TabBarIndicatorSize.tab,
indicatorWeight: 5.0,
tabs: List.generate(_abs.length, (index) => Tab(text: _abs[index], icon: Icon(Icons.android)))),
),
// 通過 body 來展示內(nèi)容座享,body 可以傳入任何 Widget,里面就是你需要展示的界面內(nèi)容
// 所以前面留下 Scaffold 中 body 部分的坑就解決了
body: PageView(
controller: _pageController,
children:
_abs.map((str) => TabChangePage(content: str)).toList(), // 通過 Map 轉(zhuǎn)換后再通過 toList 轉(zhuǎn)換成列表似忧,效果同 List.generate
onPageChanged: (position) {
// PageView 切換的監(jiān)聽渣叛,這邊切換 PageView 的頁面后,TabBar 也需要隨之改變
// 通過 tabController 來改變 TabBar 的顯示位置
_tabController.index = position;
},
),
);
}
}
最終的效果圖就不貼了盯捌,可以發(fā)現(xiàn)滑動(dòng) PageView
或者點(diǎn)擊切換 TabBar
的位置淳衙,界面顯示的內(nèi)容都會(huì)隨之改變,同時(shí)饺著,解決前面 Scaffold
留下 body
屬性沒講的一個(gè)坑箫攀,就剩下 drawer
、 bottomNavigationBar
屬性沒講了幼衰,在解決這兩個(gè)坑之前靴跛,我們先處理下另一個(gè)問題
Scaffold
能夠使我們快速去搭建一個(gè)界面,但是渡嚣,并不是所有的界面都需要 AppBar
這個(gè)標(biāo)題梢睛,那么我們就不會(huì)傳入 appBar
的屬性,我們注釋 _HomePageState
中 Scaffold
的 appBar
傳入值识椰,把 body
傳入的 PageView
修改成單個(gè) TabChangePage
绝葡,然后把 TabChangePage
這個(gè)類做下修改,把 Container
的 aligment
屬性也注釋了裤唠,這樣顯示的內(nèi)容就會(huì)顯示在左上角
// _HomePageState
// ..
@override
Widget build(BuildContext context) {
return Scaffold(body: TabChangePage(content: 'Content'));
}
class TabChangePage extends StatelessWidget {
final String content;
TabChangePage({Key key, this.content}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(child: Text(content, style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 30.0)));
}
}
然后運(yùn)行下挤牛,「**,文字怎么被狀態(tài)欄給擋了...」
不要慌种蘸,靜下心喝杯茶墓赴,眺望下遠(yuǎn)方,這里就需要用 SafeArea
來處理了航瞭,在 TabChangePage
的 Container
外層加一層 SafeArea
@override
Widget build(BuildContext context) {
return SafeArea(
child:
Container(child: Text(content, style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 30.0))));
}
然后重新運(yùn)行诫硕,一切正常,SafeArea
的用途可以看下源碼的解釋
/// A widget that insets its child by sufficient padding to avoid intrusions by
/// the operating system.
///
/// For example, this will indent the child by enough to avoid the status bar at
/// the top of the screen.
翻譯過來大概就是「給子部件和系統(tǒng)點(diǎn)擊無效區(qū)域留有足夠空間刊侯,比如狀態(tài)欄和系統(tǒng)導(dǎo)航欄」章办,SafeArea
可以很好解決劉海屏覆蓋頁面內(nèi)容的問題,那么到目前為止滨彻,AppBar
的一些坑就說的差不多了藕届,就要解決剩下的坑了
Scaffold - Drawer
drawer
同 endDrawer
屬性是一樣的,除了滑動(dòng)的方向亭饵,Drawer
這個(gè)組件也相對(duì)比較簡單休偶,只要傳入一個(gè) child
即可,在展示之前辜羊,先對(duì) appBar
做下處理踏兜,設(shè)置 leading
為系統(tǒng)默認(rèn)词顾,點(diǎn)擊 leading
的時(shí)候 Drawer
就可以滑出來了,當(dāng)然手動(dòng)滑也可以
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
// automaticallyImplyLeading: false,
// leading: Icon(Icons.menu, color: Colors.red, size: 30.0),
// flexibleSpace: Image.asset('images/app_bar_hor.jpg', fit: BoxFit.cover),
title: Text('AppBar Demo', style: TextStyle(color: Colors.red)),
actions: <Widget>[
PopupMenuButton(
offset: Offset(50.0, 100.0),
onSelected: (val) => print('Selected item is $val'),
icon: Icon(Icons.more_vert, color: Colors.red),
itemBuilder: (context) =>
List.generate(_abs.length, (index) => PopupMenuItem(value: _abs[index], child: Text(_abs[index]))))
],
bottom: TabBar(
labelColor: Colors.red,
unselectedLabelColor: Colors.white,
controller: _tabController,
isScrollable: false,
indicatorColor: Colors.yellow,
indicatorSize: TabBarIndicatorSize.tab,
indicatorWeight: 5.0,
tabs: List.generate(_abs.length, (index) => Tab(text: _abs[index], icon: Icon(Icons.android)))),
),
// body ....
drawer: Drawer(
// 記得要先添加 `SafeArea` 防止視圖頂?shù)綘顟B(tài)欄下面
child: SafeArea(
child: Container(
child: Text('Drawer', style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 30.0)),
)),
),
);
// return Scaffold(body: TabChangePage(content: 'Content'));
}
最終的效果圖也不貼了碱妆,當(dāng)手勢從左側(cè)滑出或者點(diǎn)擊 leading
圖標(biāo)肉盹,抽屜就出來了
AppBar - bottomNavigationBar
bottomNavigarionBar
可以傳入一個(gè) BottomNavigationBar
實(shí)例,BottomNavigationBar
需要傳入 BottomNavigationBarItem
列表作為 items
疹尾,但是這邊為了實(shí)現(xiàn)一個(gè) bottomNavigationBar
和 floatingActionButton
一個(gè)特殊的組合效果上忍,我們不使用 BottomNavigationBar
,換做 BottomAppBar
纳本,直接上代碼吧
@override
Widget build(BuildContext context) {
return Scaffold(
/// 一樣的代碼省略....
bottomNavigationBar: BottomAppBar(
shape: CircularNotchedRectangle(),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
IconButton(icon: Icon(Icons.android, size: 30.0, color: Theme.of(context).primaryColor), onPressed: () {}),
IconButton(icon: Icon(Icons.people, size: 30.0, color: Theme.of(context).primaryColor), onPressed: () {})
],
),
),
floatingActionButton:
FloatingActionButton(onPressed: () => print('Add'), child: Icon(Icons.add, color: Colors.white)),
// FAB 的位置睡雇,一共有 7 中位置可以選擇,centerDocked, endDocked, centerFloat, endFloat, endTop, startTop, miniStartTop饮醇,這邊選擇懸浮在 dock
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
);
最終的效果圖:
既然提到了 StatefulWidget
,順帶提下兩種比較簡單的部件秕豫,也算是基礎(chǔ)部件吧朴艰。CheckBox
、CheckboxListTile
混移,Switch
祠墅、SwitchListTile
因?yàn)楸容^簡單,就直接上代碼了歌径,里面都有完整的注釋
class CheckSwitchDemoPage extends StatefulWidget {
@override
_CheckSwitchDemoPageState createState() => _CheckSwitchDemoPageState();
}
class _CheckSwitchDemoPageState extends State<CheckSwitchDemoPage> {
var _isChecked = false;
var _isTitleChecked = false;
var _isOn = false;
var _isTitleOn = false;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Check Switch Demo'),
),
body: Column(children: <Widget>[
Row(
children: <Widget>[
Checkbox(
// 是否開啟三態(tài)
tristate: true,
// 控制當(dāng)前 checkbox 的開啟狀態(tài)
value: _isChecked,
// 不設(shè)置該方法毁嗦,處于不可用狀態(tài)
onChanged: (checked) {
// 管理狀態(tài)值
setState(() => _isChecked = checked);
},
// 選中時(shí)的顏色
activeColor: Colors.pink,
// 這個(gè)值有 padded 和 shrinkWrap 兩個(gè)值,
// padded 時(shí)候所占有的空間比 shrinkWrap 大回铛,別的原諒我沒看出啥
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
/// 點(diǎn)擊無響應(yīng)
Checkbox(value: _isChecked, onChanged: null, tristate: true)
],
),
Row(
children: <Widget>[
Switch(
// 開啟時(shí)候狗准,那個(gè)條的顏色
activeTrackColor: Colors.yellow,
// 關(guān)閉時(shí)候,那個(gè)條的顏色
inactiveTrackColor: Colors.yellow[200],
// 設(shè)置指示器的圖片茵肃,當(dāng)然也有 color 可以設(shè)置
activeThumbImage: AssetImage('images/ali.jpg'),
inactiveThumbImage: AssetImage('images/ali.jpg'),
// 開始時(shí)候的顏色腔长,貌似會(huì)被 activeTrackColor 頂?shù)? activeColor: Colors.pink,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
value: _isOn,
onChanged: (onState) {
setState(() => _isOn = onState);
}),
/// 點(diǎn)擊無響應(yīng)
Switch(value: _isOn, onChanged: null)
],
),
CheckboxListTile(
// 描述選項(xiàng)
title: Text('Make this item checked'),
// 二級(jí)描述
subtitle: Text('description...description...\ndescription...description...'),
// 和 checkbox 對(duì)立邊的部件,例如 checkbox 在頭部验残,則 secondary 在尾部
secondary: Image.asset('images/ali.jpg', width: 30.0, height: 30.0),
value: _isTitleChecked,
// title 和 subtitle 是否為垂直密集列表中一員捞附,最明顯就是部件會(huì)變小
dense: true,
// 是否需要使用 3 行的高度,該值為 true 時(shí)候您没,subtitle 不可為空
isThreeLine: true,
// 控制 checkbox 選擇框是在前面還是后面
controlAffinity: ListTileControlAffinity.leading,
// 是否將主題色應(yīng)用到文字或者圖標(biāo)
selected: true,
onChanged: (checked) {
setState(() => _isTitleChecked = checked);
},
),
SwitchListTile(
title: Text('Turn On this item'),
subtitle: Text('description...description...\ndescription...description...'),
secondary: Image.asset('images/ali.jpg', width: 30.0, height: 30.0),
isThreeLine: true,
value: _isTitleOn,
selected: true,
onChanged: (onState) {
setState(() => _isTitleOn = onState);
})
]),
);
}
}
該部分代碼查看 checkbox_swicth_main.dart
文件
終于這節(jié)把 Scaffold
留下的坑都填完了鸟召,然后又講了兩種基礎(chǔ)部件,下節(jié)要填留下的別的坑了氨鹏,目測還留了 2 個(gè)大坑欧募,那就等以后繼續(xù)解決吧~
最后代碼的地址還是要的:
文章中涉及的代碼:demos
基于郭神
cool weather
接口的一個(gè)項(xiàng)目,實(shí)現(xiàn)BLoC
模式喻犁,實(shí)現(xiàn)狀態(tài)管理:flutter_weather一個(gè)課程(當(dāng)時(shí)買了想看下代碼規(guī)范的槽片,代碼更新會(huì)比較慢何缓,雖然是跟著課上的一些寫代碼,但是還是做了自己的修改还栓,很多地方看著不舒服碌廓,然后就改成自己的實(shí)現(xiàn)方式了):flutter_shop
如果對(duì)你有幫助的話,記得給個(gè) Star剩盒,先謝過谷婆,你的認(rèn)可就是支持我繼續(xù)寫下去的動(dòng)力~