該文已授權(quán)公眾號 「碼個蛋」别惦,轉(zhuǎn)載請指明出處
前面的小節(jié)把常用的一些部件都介紹了狈茉,這節(jié)介紹下 Flutter
中的一些操作提示。Flutter
中的操作提示主要有這么幾種 SnackBar
掸掸、BottomSheet
氯庆、Dialog
,因為 Dialog
樣式比較多,放最后講好了
SnackBar
SnackBar
的源碼相對簡單
const SnackBar({
Key key,
@required this.content, // 提示信息
this.backgroundColor, // 背景色
this.action, // SnackBar 尾部的按鈕点晴,用于一些回退操作等
this.duration = _kSnackBarDisplayDuration, // 停留的時長感凤,默認 4000ms
this.animation, // 進出動畫
})
例如我們需要實現(xiàn)一個功能,修改某個值粒督,修改后給用戶一個提示陪竿,同時給用戶一個撤銷該操作的按鈕,那么就可以通過 SnackBar
來簡單實現(xiàn)屠橄。還有就是 SnackBar
可以和 floatingActionButton
完美的配合族跛,彈出的時候不會遮擋住 fab
class _PromptDemoPageState extends State<PromptDemoPage> {
var count = 0;
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
// 自增操作
increase() {
setState(() => count++);
}
// 自減操作
decrease() {
setState(() => count--);
}
_changeValue(BuildContext context) {
increase();
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('當(dāng)前值已修改'),
action: SnackBarAction(label: '撤銷', onPressed: decrease),
duration: Duration(milliseconds: 2000)));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Prompt Demo'),
),
body: Column(children: <Widget>[
Text('當(dāng)前值:$count', style: TextStyle(fontSize: 20.0)),
Expanded(
// 為了方便拓展,我這邊提取了 `snackBar` 的方法锐墙,并把按鈕放在列表
child: ListView(padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), children: <Widget>[
// SnackBar 需要提供一個包含 context礁哄,但是 context 不能是 Scaffold 節(jié)點下的 context,所以需要通過 Builder 包裹一層
Builder(builder: (context) => RaisedButton(onPressed: () => _changeValue(context), child: Text('修改當(dāng)前值'))),
]))
]),
// 當(dāng) SnackBar 彈出時溪北,fab 會上移一段距離
floatingActionButton: Builder(
builder: (context) => FloatingActionButton(onPressed: () => _changeValue(context), child: Icon(Icons.send))),
);
}
}
可以看下最后的效果圖桐绒,請注意看 fab
和值的變化:
BottomSheet
BottomSheet
看命名就知道是從底部彈出的菜單,展示 BottomSheet
有兩種方式之拨,分別是 showBottomSheet
和 showModalBottomSheet
茉继,兩種方式只有在展示類型上的差別,方法調(diào)用無差蚀乔,而且 showBottomSheet
和 fab
有組合動畫烁竭,showModalBottomSheet
則沒有,看下實際的例子吧吉挣。在 ListView
中增加一個 BottomSheet
的按鈕派撕,因為 BottomSheet
需要的 context
也不能是 Scaffold
下的 context
,所以需要通過 Builder
進行包裹一層睬魂,然后增加 _showBottomSheet
的方法
_showBottomSheet(BuildContext context) {
showBottomSheet(
context: context,
builder: (context) => ListView(
// 生成一個列表選擇器
children: List.generate(
20,
(index) => InkWell(
child: Container(alignment: Alignment.center, height: 60.0, child: Text('Item ${index + 1}')),
onTap: () {
print('tapped item ${index + 1}');
Navigator.pop(context);
}),
)),
);
}
把 showBottomSheet
替換成 showModalBottomSheet
就是另外一種展示方式了终吼,內(nèi)部不需要做任何改變,我們看下兩種的運行效果:
可以看到 showBottomSheet
會充滿整個屏幕汉买,然后 fab
會跟隨一起到 AppBar
的底部位置衔峰,而 showModalBottomSheet
展示的高度不會超過半個屏幕的高度,但是 fab
被其遮擋了蛙粘。假如我們只需要展示 2-3 個 item
垫卤,但是按照剛才的方式 showModalBottomSheet
的高度太高了,那我們可以在 ListView
外層包裹一層 Container
出牧,然后指定 height
即可
_showModalBottomSheet(BuildContext context) {
showModalBottomSheet(
context: context,
builder: (context) => Container(
child: ListView(
children: List.generate(
2,
(index) => InkWell(
child: Container(alignment: Alignment.center, height: 60.0, child: Text('Item ${index + 1}')),
onTap: () {
print('tapped item ${index + 1}');
Navigator.pop(context);
}),
)),
height: 120,
),
);
}
修改高度后的效果:
Dialog
相對于 SnackBar
和 BottomSheet
穴肘,Dialog
的使用場景相對會更多,在 MaterialDesign
下舔痕,Dialog
主要有 3 種:AlertDialog
评抚,SimpleDialog
和 AboutDialog
豹缀,當(dāng)然在 Cupertino
風(fēng)格下也有相應(yīng)的 Dialog
,因為這個系列以 MaterialDesign
風(fēng)格為主慨代,所以 Cupertiono
等下次有時間再寫吧邢笙。
AlertDialog
在 ListView
中增加一個 AlertDialog
的按鈕,用于點擊顯示 AlertDialog
用侍匙,然后加入顯示 AlertDilaog
的方法氮惯,并將按鈕的 onPressed
指向該方法,Dialog
的 context
可以是 Scaffold
下的 context
想暗,所以不需要用 Builder
來包裹一層妇汗。
_showAlertDialog() {
showDialog(
// 設(shè)置點擊 dialog 外部不取消 dialog,默認能夠取消
barrierDismissible: false,
context: context,
builder: (context) => AlertDialog(
title: Text('我是個標(biāo)題...嗯说莫,標(biāo)題..'),
titleTextStyle: TextStyle(color: Colors.purple), // 標(biāo)題文字樣式
content: Text(r'我是內(nèi)容\(^o^)/~, 我是內(nèi)容\(^o^)/~, 我是內(nèi)容\(^o^)/~'),
contentTextStyle: TextStyle(color: Colors.green), // 內(nèi)容文字樣式
backgroundColor: CupertinoColors.white,
elevation: 8.0, // 投影的陰影高度
semanticLabel: 'Label', // 這個用于無障礙下彈出 dialog 的提示
shape: Border.all(),
// dialog 的操作按鈕杨箭,actions 的個數(shù)盡量控制不要過多,否則會溢出 `Overflow`
actions: <Widget>[
// 點擊增加顯示的值
FlatButton(onPressed: increase, child: Text('點我增加')),
// 點擊減少顯示的值
FlatButton(onPressed: decrease, child: Text('點我減少')),
// 點擊關(guān)閉 dialog储狭,需要通過 Navigator 進行操作
FlatButton(onPressed: () => Navigator.pop(context),
child: Text('你點我試試.')),
],
));
}
最后看下效果:
SimpleDialog
SimpleDialog
相比于 AlertDialog
少了 content
和 action
參數(shù)互婿,多了 children
屬性,需要傳入 Widget
列表辽狈,那就可以自定義全部內(nèi)容了擒悬。那我們這里就實現(xiàn)一個性別選擇的 Dialog
,選擇后通過 Taost
提示選擇的內(nèi)容稻艰,Taost
就是之前導(dǎo)入的第三方插件,先看下效果圖吧
只要實現(xiàn) children
是個列表選擇器就可以了侈净,比較簡單尊勿,直接上代碼
_showSimpleDialog() {
showDialog(
barrierDismissible: false,
context: context,
builder: (context) => SimpleDialog(
title: Text('我是個比較正經(jīng)的標(biāo)題...\n選擇你的性別'),
// 這里傳入一個選擇器列表即可
children: _genders
.map((gender) => InkWell(
child: Container(height: 40.0, child: Text(gender), alignment: Alignment.center),
onTap: () {
Navigator.pop(context);
Fluttertoast.showToast(msg: '你選擇的性別是 $gender');
},
))
.toList(),
));
}
AboutDialog
AboutDialog
主要是用于展示你的 App
或者別的相關(guān)東西的內(nèi)容信息的,平時用的比較少畜侦,顯示 AboutDialog
有兩種方式可以展示元扔,一種是前面一樣的 showDialog
方法,傳入一個 AboutDialog
實例旋膳,還有中方法是直接調(diào)用 showAboutDialog
方法澎语。我們還是一樣在列表加個按鈕,并指向顯示 AboutDialog
的事件验懊。
_showAboutDialog() {
showDialog(
barrierDismissible: false,
context: context,
builder: (context) => AboutDialog(
// App 的名字
applicationName: 'Flutter 入門指北',
// App 的版本號
applicationVersion: '0.1.1',
// App 基本信息下面會顯示一行小字擅羞,主要用來顯示版權(quán)信息
applicationLegalese: 'Copyright: this is a copyright notice topically',
// App 的圖標(biāo)
applicationIcon: Icon(Icons.android, size: 28.0, color: CupertinoColors.activeBlue),
// 任何你想展示的
children: <Widget>[Text('我是個比較正經(jīng)的對話框內(nèi)容...你可以隨便把我替換成任何部件,只要你喜歡(*^▽^*)')],
));
}
也可以通過 showAboutDialog
實現(xiàn)同樣的效果
_showAboutDialog() {
showAboutDialog(
context: context,
applicationName: 'Flutter 入門指北',
applicationVersion: '0.1.1',
applicationLegalese: 'Copyright: this is a copyright notice topically',
applicationIcon: Image.asset('images/app_icon.png', width: 40.0, height: 40.0),
children: <Widget>[Text('我是個比較正經(jīng)的對話框內(nèi)容...你可以隨便把我替換成任何部件义图,只要你喜歡(*^▽^*)')],
);
}
最后的效果:
AboutDialog
會自帶兩個按鈕 VIEW LICENSES
和 CLOSE
减俏,VIEW LICENSES
會跳轉(zhuǎn)一個 Flutter Licenses
的網(wǎng)頁,CLOSE
會關(guān)閉碱工,至于為什么是英文的娃承,是因為我們沒有設(shè)置語言的原因奏夫,這個涉及到多語言,這邊推薦幾篇之前看過的文章历筝,如果下次有時間的話會單獨拿出來講下
英文原版多語言設(shè)置酗昼,介紹兩種方式實現(xiàn)
這邊為了支持中文梳猪,我們做下如下的修改麻削,首先打開 pubspec.ymal
文件加入如下支持
get package
后給 MaterialApp
加入如下屬性,這樣就會支持中文了舔示,這里需要導(dǎo)入包 package:flutter_localizations/flutter_localizations.dart
碟婆,再次運行,就會發(fā)現(xiàn)之前的英文變成中文了惕稻,當(dāng)然你也可以設(shè)置成別的語言竖共。
Dialog 狀態(tài)保持
假如有個需求,需要在彈出的 Dialog
顯示當(dāng)前被改變的值俺祠,然后通過按鈕可以修改這個值 公给,該如何實現(xiàn)。相信很多小伙伴都會這么認為蜘渣,通過 setState
來修改不就行了嗎淌铐,沒錯,我一開始的確這么去實現(xiàn)的蔫缸,我們先看下代碼好了腿准,增加一個 DialogState
按鈕,然后指向?qū)?yīng)的點擊事件
_showStateDialog() {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => SimpleDialog(
title: Text('我這邊能實時修改狀態(tài)值'),
contentPadding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
children: <Widget>[
Text('當(dāng)前的值是: $_count', style: TextStyle(fontSize: 18.0)),
Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0),
child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: <Widget>[
RaisedButton(
onPressed: increase,
child: Text('點我自增'),
),
RaisedButton(
onPressed: decrease,
child: Text('點我自減'),
),
RaisedButton(
onPressed: () => Navigator.pop(context),
child: Text('點我關(guān)閉'),
)
]),
)
],
));
}
然后我們運行看下
誒誒誒拾碌,怎么 Dialog
的值不改變呢吐葱,明明界面上的已經(jīng)修改了啊。所以說圖樣圖森破咯校翔,看下官方對 showDialog
方法的解釋吧
/// This function takes a `builder` which typically builds a [Dialog] widget. /// Content below the dialog is dimmed with a [ModalBarrier]. The widget /// returned by the `builder` does not share a context with the location that /// `showDialog` is originally called from. Use a [StatefulBuilder] or a /// custom [StatefulWidget] if the dialog needs to update dynamically.
糟糕透的翻譯又來了:該方法通過 builder
參數(shù)來傳入一個 Dialog
部件弟跑,dialog
下的內(nèi)容被一個「模態(tài)障礙」阻隔,builder
的 context
和調(diào)用 showDialog
時候的 context
不是共享的防症,如果需要動態(tài)修改 dialog
的狀態(tài)值孟辑,需要通過 StatefulBuilder
或者自定義 dialog
繼承于 StatefulWidget
來實現(xiàn)
所以解決的方法很明確,對上面的代碼進行修改蔫敲,在外層嵌套一個 StatefulBuilder
部件
_showStateDialog() {
showDialog(
context: context,
barrierDismissible: false,
// 通過 StatefulBuilder 來保存 dialog 狀態(tài)
// builder 需要傳入一個 BuildContext 和 StateSetter 類型參數(shù)
// StateSetter 有一個 VoidCallback饲嗽,修改狀態(tài)的方法在這寫
builder: (context) => StatefulBuilder(
builder: (context, dialogStateState) => SimpleDialog(
title: Text('我這邊能實時修改狀態(tài)值'),
contentPadding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
children: <Widget>[
Text('當(dāng)前的值是: $_count', style: TextStyle(fontSize: 18.0)),
Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0),
child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: <Widget>[
RaisedButton(
// 通過 StatefulBuilder 的 StateSetter 來修改值
onPressed: () => dialogStateState(() => increase()),
child: Text('點我自增'),
),
RaisedButton(
onPressed: () => dialogStateState(() => decrease()),
child: Text('點我自減'),
),
RaisedButton(
onPressed: () => Navigator.pop(context),
child: Text('點我關(guān)閉'),
)
]),
)
],
)));
}
然后再運行下,可以看到 dialog
和界面的值保持一致了
以上部分代碼查看 prompt_main.dart 文件
差不多常用彈窗和操作提示就在這了奈嘿,好好消化吧~
最后代碼的地址還是要的:
文章中涉及的代碼:demos
基于郭神
cool weather
接口的一個項目喝噪,實現(xiàn)BLoC
模式,實現(xiàn)狀態(tài)管理:flutter_weather一個課程(當(dāng)時買了想看下代碼規(guī)范的指么,代碼更新會比較慢酝惧,雖然是跟著課上的一些寫代碼榴鼎,但是還是做了自己的修改,很多地方看著不舒服晚唇,然后就改成自己的實現(xiàn)方式了):flutter_shop
如果對你有幫助的話巫财,記得給個 Star,先謝過哩陕,你的認可就是支持我繼續(xù)寫下去的動力~