博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
flutter demo (四):对话框
阅读量:7104 次
发布时间:2019-06-28

本文共 13952 字,大约阅读时间需要 46 分钟。

我在使用flutter里的对话框控件的时候遇到了一个奇怪的错误:

Another exception was thrown: Navigator operation requested with a context that does not include a Navigator复制代码

研究了一下才知道,flutter里的dialog不是随便就能用的。

原代码如下:

import 'package:flutter/material.dart';main() {  runApp(new MyApp());}class MyApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return new MaterialApp(      title: 'Test',      home: new Scaffold(        appBar: new AppBar(title: new Text('Test')),        body: _buildCenterButton(context),      ),    );  }}Widget _buildCenterButton(BuildContext context) {  return new Container(      alignment: Alignment.center,      child: new Container(        child: _buildButton(context),      ));}Widget _buildButton(BuildContext context) {  return new RaisedButton(      padding: new EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 10.0),      //padding      child: new Text(        'show dialog',        style: new TextStyle(          fontSize: 18.0, //textsize          color: Colors.white, // textcolor        ),      ),      color: Theme.of(context).accentColor,      elevation: 4.0,      //shadow      splashColor: Colors.blueGrey,      onPressed: () {        showAlertDialog(context);      });}void showAlertDialog(BuildContext context) {  showDialog(      context: context,      builder: (_) => new AlertDialog(          title: new Text("Dialog Title"),          content: new Text("This is my content"),          actions:
[ new FlatButton(child:new Text("CANCEL"), onPressed: (){ Navigator.of(context).pop(); },), new FlatButton(child:new Text("OK"), onPressed: (){ Navigator.of(context).pop(); },) ] ));}复制代码

点击按钮的时候没有任何反应,控制台的报错是: Another exception was thrown: Navigator operation requested with a context that does not include a Navigator。大致意思是,context里没有Navigator对象,却做了Navigator相关的操作。有点莫名其妙。

分析下源码吧~

看showDialog方法的源码:

Future
showDialog
({ @required BuildContext context, bool barrierDismissible: true, @Deprecated( 'Instead of using the "child" argument, return the child from a closure ' 'provided to the "builder" argument. This will ensure that the BuildContext ' 'is appropriate for widgets built in the dialog.' ) Widget child, WidgetBuilder builder,}) { assert(child == null || builder == null); return Navigator.of(context, rootNavigator: true/*注意这里*/).push(new _DialogRoute
( child: child ?? new Builder(builder: builder), theme: Theme.of(context, shadowThemeOnly: true), barrierDismissible: barrierDismissible, barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, ));}复制代码

Navigator.of 的源码:

static NavigatorState of(    BuildContext context, {      bool rootNavigator: false,      bool nullOk: false,    }) {    final NavigatorState navigator = rootNavigator        ? context.rootAncestorStateOfType(const TypeMatcher
()) : context.ancestorStateOfType(const TypeMatcher
()); assert(() { if (navigator == null && !nullOk) { throw new FlutterError( 'Navigator operation requested with a context that does not include a Navigator.\n' 'The context used to push or pop routes from the Navigator must be that of a ' 'widget that is a descendant of a Navigator widget.' ); } return true; }()); return navigator; }复制代码

找到了一模一样的错误信息字符串!看来就是因为Navigator.of(context)抛出了一个FlutterError。 之所以出现这个错误,是因为满足了if (navigator == null && !nullOk) 的条件, 也就是说: context.rootAncestorStateOfType(const TypeMatcher()) 是null。

Navigator.of函数有3个参数,第一个是BuildContext,第二个是rootNavigator,默认为false,可不传,第三个是nullOk,默认为false,可不传。rootNavigator的值决定了是调用ancestorStateOfType还是rootAncestorStateOfType,nullOk的值决定了如果最终结果为null值时该抛出异常还是直接返回一个null。

我们做个测试,传入不同的rootNavigator和nullOk的值,看有什么结果:

void showAlertDialog(BuildContext context) {  try{     debugPrint("Navigator.of(context, rootNavigator=true, nullOk=false)="+        (Navigator.of(context, rootNavigator: true, nullOk: false)).toString());  }catch(e){    debugPrint("error1 " +e.toString());  }  try{    debugPrint("Navigator.of(context, rootNavigator=false, nullOk=false)="+       (Navigator.of(context, rootNavigator: false, nullOk: false)).toString());  }catch(e){    debugPrint("error2 " +e.toString());  }  try{    debugPrint("Navigator.of(context, rootNavigator=false, nullOk=true)="+       (Navigator.of(context, rootNavigator: false, nullOk: true)).toString());  }catch(e){    debugPrint("error3 " +e.toString());  }  //先注释掉showDialog部分的代码//  showDialog(//      context: context,//      builder: (_) => new AlertDialog(//          title: new Text("Dialog Title"),//          content: new Text("This is my content"),//          actions:
[// new FlatButton(child:new Text("CANCEL"), onPressed: (){// Navigator.of(context).pop();//// },),// new FlatButton(child:new Text("OK"), onPressed: (){// Navigator.of(context).pop();//// },)// ]//// ));}复制代码

打印结果:

error1 Navigator operation requested with a context that does not include a Navigator.error2 Navigator operation requested with a context that does not include a Navigator.Navigator.of(context, rootNavigator=false, nullOk=true)=null复制代码

显然,无论怎么改rootNavigator和nullOk的值,Navigator.of(context, rootNavigator, nullOk)的值都是null。

为什么呢?

rootAncestorStateOfType函数的实现位于framework.dart里,我们可以看一下ancestorStateOfTyperootAncestorStateOfType的区别:

@override  State ancestorStateOfType(TypeMatcher matcher) {    assert(_debugCheckStateIsActiveForAncestorLookup());    Element ancestor = _parent;    while (ancestor != null) {      if (ancestor is StatefulElement && matcher.check(ancestor.state))        break;       ancestor = ancestor._parent;    }    final StatefulElement statefulAncestor = ancestor;    return statefulAncestor?.state;  }  @override  State rootAncestorStateOfType(TypeMatcher matcher) {    assert(_debugCheckStateIsActiveForAncestorLookup());    Element ancestor = _parent;    StatefulElement statefulAncestor;    while (ancestor != null) {      if (ancestor is StatefulElement && matcher.check(ancestor.state))        statefulAncestor = ancestor;      ancestor = ancestor._parent;    }    return statefulAncestor?.state;  }复制代码

可以看出: ancestorStateOfType的作用是: 如果某个父元素满足一定条件, 则返回这个父节点的state属性; rootAncestorStateOfType的作用是: 返回最顶层的满足一定条件的父元素。 这个条件是: 这个元素必须属于StatefulElement , 而且其state属性与参数里的TypeMatcher 相符合。

查询源码可以知道:StatelessWidget 里的元素是StatelessElement,StatefulWidget里的元素是StatefulElement。

也就是说,要想让context.rootAncestorStateOfType(const TypeMatcher())的返回值不为null, 必须保证context所在的Widget的顶层Widget属于StatefulWidget(注意是顶层Widget,而不是自己所在的widget。如果context所在的Widget就是顶层Widget,也是不可以的)。

这样我们就大概知道为什么会出错了。我们的showAlertDialog方法所用的context是属于MyApp的, 而MyApp是个StatelessWidget。

那么,修改方案就比较清晰了,我们的对话框所使用的context不能是顶层Widget的context,同时顶层Widget必须是StatefulWidget。

修改后的完整代码如下:

import 'package:flutter/material.dart';main() {  runApp(new MyApp());}class MyApp extends StatefulWidget {  @override  State
createState() { return new MyState(); }}class MyState extends State
{ @override Widget build(BuildContext context) { return new MaterialApp( title: 'Test', home: new Scaffold( appBar: new AppBar(title: new Text('Test')), body: new StatelessWidgetTest(), ), ); }}class StatelessWidgetTest extends StatelessWidget { @override Widget build(BuildContext context) { return _buildCenterButton(context); }}Widget _buildCenterButton(BuildContext context) { return new Container( alignment: Alignment.center, child: new Container( child: _buildButton(context), ));}Widget _buildButton(BuildContext context) { return new RaisedButton( padding: new EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 10.0), //padding child: new Text( 'show dialog', style: new TextStyle( fontSize: 18.0, //textsize color: Colors.white, // textcolor ), ), color: Theme.of(context).accentColor, elevation: 4.0, //shadow splashColor: Colors.blueGrey, onPressed: () { showAlertDialog(context); });}void showAlertDialog(BuildContext context) { NavigatorState navigator= context.rootAncestorStateOfType(const TypeMatcher
()); debugPrint("navigator is null?"+(navigator==null).toString()); showDialog( context: context, builder: (_) => new AlertDialog( title: new Text("Dialog Title"), content: new Text("This is my content"), actions:
[ new FlatButton(child:new Text("CANCEL"), onPressed: (){ Navigator.of(context).pop(); },), new FlatButton(child:new Text("OK"), onPressed: (){ Navigator.of(context).pop(); },) ] ));}复制代码

实验结果:

至于为什么flutter里的对话框控件对BuildContext的要求这么严格,暂时还不清楚原因。

如何知道用户点的是"OK"还是"CANCEL"呢?

我们可以借鉴下 官方demo里的写法:

Widget _buildButton(BuildContext context) {    return new RaisedButton(        padding: new EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 10.0),        //padding        child: new Text(          'test dialog',          style: new TextStyle(            fontSize: 18.0, //textsize            color: Colors.white, // textcolor          ),        ),        color: Theme.of(context).accentColor,        elevation: 4.0,        //shadow        splashColor: Colors.blueGrey,        onPressed: () {          showDemoDialog
( context: context, child: new AlertDialog( title: new Text("Dialog Title"), content: new Text("This is is a dialog"), actions:
[ new FlatButton( child: new Text("CANCEL"), onPressed: () { Navigator.pop(context, DialogItemAction.cancel); }, ), new FlatButton( child: new Text("OK"), onPressed: () { Navigator.pop(context, DialogItemAction.agree); }, ) ])); }); } void showDemoDialog
({BuildContext context, Widget child}) { showDialog
( context: context, builder: (BuildContext context) => child, ).then
((T value) { // The value passed to Navigator.pop() or null. if (value != null) { _scaffoldKey.currentState.showSnackBar( new SnackBar(content: new Text('hey, You selected: $value'))); } }); }复制代码

最终的完整源码:

import 'package:flutter/material.dart';main() {  runApp(new MyApp());}class MyApp extends StatefulWidget {  @override  MyAppState createState() => new MyAppState();}class MyAppState extends State
{ @override Widget build(BuildContext context) { return new MaterialApp(title: 'Test', home: new TestMyApp()); }}class TestMyApp extends StatefulWidget { @override TestMyAppState createState() => new TestMyAppState();}enum DialogItemAction { cancel, agree,}class TestMyAppState extends State
{ final GlobalKey
_scaffoldKey = new GlobalKey
(); @override Widget build(BuildContext context) { return new Scaffold( key: _scaffoldKey, appBar: new AppBar(title: const Text('Dialogs')), body: _buildCenterButton(context)); } Widget _buildCenterButton(BuildContext context) { return new Container( alignment: Alignment.center, child: new Container( child: _buildButton(context), )); } Widget _buildButton(BuildContext context) { return new RaisedButton( padding: new EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 10.0), //padding child: new Text( 'test dialog', style: new TextStyle( fontSize: 18.0, //textsize color: Colors.white, // textcolor ), ), color: Theme.of(context).accentColor, elevation: 4.0, //shadow splashColor: Colors.blueGrey, onPressed: () { showDemoDialog
( context: context, child: new AlertDialog( title: new Text("Dialog Title"), content: new Text("This is is a dialog"), actions:
[ new FlatButton( child: new Text("CANCEL"), onPressed: () { Navigator.pop(context, DialogItemAction.cancel); }, ), new FlatButton( child: new Text("OK"), onPressed: () { Navigator.pop(context, DialogItemAction.agree); }, ) ])); }); } void showDemoDialog
({BuildContext context, Widget child}) { showDialog
( context: context, builder: (BuildContext context) => child, ).then
((T value) { // The value passed to Navigator.pop() or null. if (value != null) { _scaffoldKey.currentState.showSnackBar( new SnackBar(content: new Text('hey, You selected: $value'))); } }); }}复制代码

后记:

在flutter里,Widget,Element和BuildContext之间的关系是什么呢?

摘抄部分系统源码如下:

abstract class Element extends DiagnosticableTree implements BuildContext{....}abstract class ComponentElement extends Element {}class StatelessElement extends ComponentElement {  @override  Widget build() => widget.build(this);}class StatefulElement extends ComponentElement {  @override  Widget build() => state.build(this);}abstract class Widget extends DiagnosticableTree {  Element createElement();}abstract class StatelessWidget extends Widget {  @override  StatelessElement createElement() => new StatelessElement(this);  @protected  Widget build(BuildContext context);}abstract class StatefulWidget extends Widget {  @override  StatefulElement createElement() => new StatefulElement(this);  @protected  State createState();}abstract class State
extends Diagnosticable { @protected Widget build(BuildContext context);}复制代码

转载地址:http://awyhl.baihongyu.com/

你可能感兴趣的文章
mysql删除重复数据只保留一条
查看>>
Cubieboard开发环境与Uboot的SD启动卡制作
查看>>
linux中强大且常用命令:find、grep
查看>>
【Objective-C】OC中的数值的概念和常用方法(NSArray和NSMutableArray)
查看>>
linux 系统ubuntu minicom 和cutecom下串口 设置和常见问题。
查看>>
岗位角色管理,打造杰出员工
查看>>
TCP 三次握手 -转载
查看>>
NMath应用教程:如何通过代码访问底层数据和应用函数
查看>>
PHP缓存技术
查看>>
人生苦短,快用Python
查看>>
redhat使用yum命令报错
查看>>
朴素贝叶斯分类算法
查看>>
get与post请求方式的区别
查看>>
OpenFlow协议通信流程解读
查看>>
JAVA中this用法小结
查看>>
Leetcode PHP题解--D19 867. Transpose Matrix
查看>>
Android设置布局背景为白色的三种方法
查看>>
机房建设
查看>>
mysql中的锁
查看>>
stm32f051 adc单次软件选择循环采样
查看>>