最近使用了Flutter的展現對話框的功能,踩了一點坑,順便作下總結,方便各位之後少踩坑,若是有說錯的地方,還請你們指出來。ios
下面將介紹對話框的幾種場景和踩坑。git
對話框本質上是屬於一個路由的頁面route,由Navigator進行管理,因此控制對話框的顯示和隱藏,也是調用Navigator.of(context)的push和pop方法。github
在Flutter中,對話框會有兩種風格,調用showDialog()方法展現的是material風格的對話框,調用showCupertinoDialog()方法展現的是ios風格的對話框。 而這兩個方法其實都會去調用showGeneralDialog()方法,能夠從源碼中看到最後是利用Navigator.of(context, rootNavigator: true).push()一個頁面。bash
基本要傳的參數:context上下文,builder用於建立顯示的widget,barrierDismissible能夠控制點擊對話框之外的區域是否隱藏對話框。app
Future<T> showCupertinoDialog<T>({
@required BuildContext context,
@required WidgetBuilder builder,
});
Future<T> showDialog<T>({
@required BuildContext context,
bool barrierDismissible = true,
WidgetBuilder builder,
})
Future<T> showGeneralDialog<T>({
@required BuildContext context,
@required RoutePageBuilder pageBuilder,
bool barrierDismissible,
String barrierLabel,
Color barrierColor,
Duration transitionDuration,
RouteTransitionsBuilder transitionBuilder,
}) {
assert(pageBuilder != null);
assert(!barrierDismissible || barrierLabel != null);
return Navigator.of(context, rootNavigator: true).push<T>(_DialogRoute<T>(
pageBuilder: pageBuilder,
barrierDismissible: barrierDismissible,
barrierLabel: barrierLabel,
barrierColor: barrierColor,
transitionDuration: transitionDuration,
transitionBuilder: transitionBuilder,
));
}
複製代碼
Flutter中的Dialog主要是SimpleDialog和AlertDialog。less
展現一個簡單的SimpleDialog,代碼以下:ide
void showMySimpleDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) {
return new SimpleDialog(
title: new Text("SimpleDialog"),
children: <Widget>[
new SimpleDialogOption(
child: new Text("SimpleDialogOption One"),
onPressed: () {
Navigator.of(context).pop("SimpleDialogOption One");
},
),
new SimpleDialogOption(
child: new Text("SimpleDialogOption Two"),
onPressed: () {
Navigator.of(context).pop("SimpleDialogOption Two");
},
),
new SimpleDialogOption(
child: new Text("SimpleDialogOption Three"),
onPressed: () {
Navigator.of(context).pop("SimpleDialogOption Three");
},
),
],
);
});
}
複製代碼
展現一個簡單的Material風格的AlertDialog,代碼以下:學習
void showMyMaterialDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) {
return new AlertDialog(
title: new Text("title"),
content: new Text("內容內容內容內容內容內容內容內容內容內容內容"),
actions: <Widget>[
new FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: new Text("確認"),
),
new FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: new Text("取消"),
),
],
);
});
}
複製代碼
展現一個簡單的IOS風格的AlertDialog,代碼以下:動畫
void showMyCupertinoDialog(BuildContext context) {
showCupertinoDialog(
context: context,
builder: (context) {
return new CupertinoAlertDialog(
title: new Text("title"),
content: new Text("內容內容內容內容內容內容內容內容內容內容內容"),
actions: <Widget>[
new FlatButton(
onPressed: () {
Navigator.of(context).pop("點擊了肯定");
},
child: new Text("確認"),
),
new FlatButton(
onPressed: () {
Navigator.of(context).pop("點擊了取消");
},
child: new Text("取消"),
),
],
);
});
}
複製代碼
構造對話框的時候,咱們都須要傳一個content對象,來構造對話框的主要內容。通常狀況下,若是content裏面只是簡單的一些內容,那問題不大,能夠正常顯示。 可是有時候,咱們須要展現一個列表對話框。這個時候,若是列表項比較多,就會出現一些問題。ui
當Column的列表項數據比較多的時候,屏幕已經放不了,就會出現overflow錯誤了,因此這個時候須要在外部嵌套一個SingleChildScrollView控件,使內部child控件能夠滾動, 不會出現overflow錯誤。(哈哈,可使用下面的代碼跑一跑,而後去掉SingleChildScrollView,對比運行結果)
void showMyDialogWithColumn(BuildContext context) {
showDialog(
context: context,
builder: (context) {
return new AlertDialog(
title: new Text("title"),
content: new SingleChildScrollView(
child: new Column(
children: <Widget>[
new SizedBox(
height: 100,
child: new Text("1"),
),
new SizedBox(
height: 100,
child: new Text("1"),
),
new SizedBox(
height: 100,
child: new Text("1"),
),
new SizedBox(
height: 100,
child: new Text("1"),
),
new SizedBox(
height: 100,
child: new Text("1"),
),
new SizedBox(
height: 100,
child: new Text("1"),
),
new SizedBox(
height: 100,
child: new Text("1"),
),
new SizedBox(
height: 100,
child: new Text("1"),
),
new SizedBox(
height: 100,
child: new Text("1"),
),
new SizedBox(
height: 100,
child: new Text("1"),
),
new SizedBox(
height: 100,
child: new Text("1"),
),
new SizedBox(
height: 100,
child: new Text("1"),
),
],
),
),
actions: <Widget>[
new FlatButton(
onPressed: () {},
child: new Text("確認"),
),
new FlatButton(
onPressed: () {},
child: new Text("取消"),
),
],
);
});
}
複製代碼
要將ListView包裝在具備特定寬度和高度的Container中。 若是Container沒有定義這兩個屬性的話,會報錯,沒法顯示ListView。(目前我也是這樣解決的,不知道有沒有人有其餘更好的方法哈。) 報錯以下:
void showMyDialogWithListView(BuildContext context) {
showDialog(
context: context,
builder: (BuildContext context) {
return new AlertDialog(
content: new Container(
/*
暫時的解決方法:要將ListView包裝在具備特定寬度和高度的Container中
若是Container沒有定義這兩個屬性的話,會報錯,沒法顯示ListView
*/
width: MediaQuery.of(context).size.width * 0.9,
height: MediaQuery.of(context).size.height * 0.9,
child: new ListView.builder(
itemBuilder: (context, index) {
return new SizedBox(
height: 100,
child: new Text("1"),
);
},
itemCount: 10,
shrinkWrap: true,
),
));
},
);
//若是直接將ListView放在dialog中,會報錯,好比
//下面這種寫法會報錯:I/flutter (10721): ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
// I/flutter (10721): The following assertion was thrown during performLayout():
// I/flutter (10721): RenderShrinkWrappingViewport does not support returning intrinsic dimensions.
// I/flutter (10721): Calculating the intrinsic dimensions would require instantiating every child of the viewport, which
// I/flutter (10721): defeats the point of viewports being lazy.
// I/flutter (10721): If you are merely trying to shrink-wrap the viewport in the main axis direction, you should be able
// I/flutter (10721): to achieve that effect by just giving the viewport loose constraints, without needing to measure its
// I/flutter (10721): intrinsic dimensions.
// I/flutter (10721):
// I/flutter (10721): When the exception was thrown, this was the stack:
// I/flutter (10721): #0 RenderShrinkWrappingViewport.debugThrowIfNotCheckingIntrinsics.<anonymous closure> (package:flutter/src/rendering/viewport.dart:1544:9)
// I/flutter (10721): #1 RenderShrinkWrappingViewport.debugThrowIfNotCheckingIntrinsics (package:flutter/src/rendering/viewport.dart:1554:6)
// I/flutter (10721): #2 RenderViewportBase.computeMaxIntrinsicWidth (package:flutter/src/rendering/viewport.dart:321:12)
// I/flutter (10721): #3 RenderBox._computeIntrinsicDimension.<anonymous closure> (package:flutter/src/rendering/box.dart:1109:23)
// I/flutter (10721): #4 __InternalLinkedHashMap&_HashVMBase&MapMixin&_LinkedHashMapMixin.putIfAbsent (dart:collection/runtime/libcompact_hash.dart:277:23)
// I/flutter (10721): #5 RenderBox._computeIntrinsicDimension (package:flutter/src/rendering/box.dart:1107:41)
// I/flutter (10721): #6 RenderBox.getMaxIntrinsicWidth (package:flutter/src/rendering/box.dart:1291:12)
// I/flutter (10721): #7 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.computeMaxIntrinsicWidth (package:flutter/src/rendering/proxy_box.dart:81:20)
// showDialog(context: context, builder: (context) {
// return new AlertDialog(title: new Text("title"),
// content: new SingleChildScrollView(
// child: new Container(
// height: 200,
// child: new ListView.builder(
// itemBuilder: (context, index) {
// return new SizedBox(height: 100, child: new Text("1"),);
// }, itemCount: 10, shrinkWrap: true,),
// ),
// ),
// actions: <Widget>[
// new FlatButton(onPressed: () {}, child: new Text("確認"),),
// new FlatButton(onPressed: () {}, child: new Text("取消"),),
// ],);
// });
複製代碼
利用StatefulBuilder來實現一些對話框場景,須要對話框動態更新界面的。
好比在對話框裏面顯示一個checkbox,而後點擊會修改checkbox的顯示狀態。若是是跟以前同樣的實現對話框方法, 是沒法實現動態去刷新對話框的界面的。
StatefulBuilder能夠包含一個child,具備狀態,能夠調用setState刷新界面。
builder參數,用於建立想要顯示的widget,能夠調用StateSetter類型的setState參數來進行刷新界面。
typedef StatefulWidgetBuilder = Widget Function(BuildContext context, StateSetter setState);
const StatefulBuilder({
Key key,
@required this.builder
}) : assert(builder != null),
super(key: key);
複製代碼
實例的代碼以下:
void showMyDialogWithStateBuilder(BuildContext context) {
showDialog(
context: context,
builder: (context) {
bool selected = false;
return new AlertDialog(
title: new Text("StatefulBuilder"),
content:
new StatefulBuilder(builder: (context, StateSetter setState) {
return Container(
child: new CheckboxListTile(
title: new Text("選項"),
value: selected,
onChanged: (bool) {
setState(() {
selected = !selected;
});
}),
);
}),
);
});
}
複製代碼
好比我想顯示一個菊花的loading加載框,那麼用上面的方法都是行不通的。這個時候就須要咱們去自定義一個對話框。
首先咱們能夠先去看一下Dialog的源碼實現,而後只需再照着源碼的實現,修改一下就好了。大部分代碼是保持一致的,因此 對話框的顯示效果好比動畫,主題都是一致的。
下面是Dialog源碼中的build方法實現。簡單的修改下child屬性所傳的參數就好了。
@override
Widget build(BuildContext context) {
final DialogTheme dialogTheme = DialogTheme.of(context);
return AnimatedPadding(
padding: MediaQuery.of(context).viewInsets + const EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0),
duration: insetAnimationDuration,
curve: insetAnimationCurve,
child: MediaQuery.removeViewInsets(
removeLeft: true,
removeTop: true,
removeRight: true,
removeBottom: true,
context: context,
//因此咱們其實只須要修改child這個屬性了,改爲咱們想要展現的widget就好了。
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(minWidth: 280.0),
child: Material(
elevation: 24.0,
color: _getColor(context),
type: MaterialType.card,
child: child,
shape: shape ?? dialogTheme.shape ?? _defaultDialogShape,
),
),
),
),
);
}
複製代碼
下面是一個自定義加載框Dialog的例子,就是將AlertDialog的源碼進行剛纔所說的修改就好了。
void showMyCustomLoadingDialog(BuildContext context) {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return new MyCustomLoadingDialog();
});
}
class MyCustomLoadingDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
Duration insetAnimationDuration = const Duration(milliseconds: 100);
Curve insetAnimationCurve = Curves.decelerate;
RoundedRectangleBorder _defaultDialogShape = RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(2.0)));
return AnimatedPadding(
padding: MediaQuery.of(context).viewInsets +
const EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0),
duration: insetAnimationDuration,
curve: insetAnimationCurve,
child: MediaQuery.removeViewInsets(
removeLeft: true,
removeTop: true,
removeRight: true,
removeBottom: true,
context: context,
child: Center(
child: SizedBox(
width: 120,
height: 120,
child: Material(
elevation: 24.0,
color: Theme.of(context).dialogBackgroundColor,
type: MaterialType.card,
//在這裏修改爲咱們想要顯示的widget就好了,外部的屬性跟其餘Dialog保持一致
child: new Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new CircularProgressIndicator(),
Padding(
padding: const EdgeInsets.only(top: 20),
child: new Text("加載中"),
),
],
),
shape: _defaultDialogShape,
),
),
),
),
);
}
}
複製代碼
最近弄了個學習Flutter的公衆號(入魔的冬瓜),一部分是搬磚一些國外文章再加些本身的理解,一部分是本身平時的總結。
但願與你們在2019一塊兒學習,共同進步!
祝你們狗年快樂!