写点什么

【Flutter 专题】124 日常问题小结 (三) 自定义 Dialog 二三事

发布于: 2021 年 05 月 27 日
【Flutter 专题】124 日常问题小结 (三) 自定义 Dialog 二三事

    针对日常不同的需求,我们时常需要自定义 Dialog,而小菜在尝试过程中遇到一些小问题,简单记录总结一下;

Dialog

Q1. 软键盘遮挡含文本框对话框

    小菜在自定义含有文本框的 Dialog 时,文本框获取焦点时,软键盘会部分遮挡对话框,但当小菜替换为 AlertDialog 时,文本框获取焦点时,对话框会向上浮动,避免软键盘遮挡;


return Material(    type: MaterialType.transparency,    child: Stack(children: [      Center( child: Container(              decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20.0)),              margin: EdgeInsets.only(left: 20.0, right: 20.0),              child: Padding( padding: EdgeInsets.symmetric( horizontal: 20.0, vertical: 25.0),                  child: Column(mainAxisSize: MainAxisSize.min, children: [                    Text('账单名称', style: TextStyle(fontSize: 16.0)),                    _nameWid(),                    Row(children: [ Expanded(child: _actionButtons(0)), SizedBox(width: 15.0), Expanded(child: _actionButtons(1)) ])                  ]))))    ]));
Future<void> _showBillNameDialog() async { await showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return BillNameDialog(onCancelEvent: () { Navigator.pop(context); }, onSureEvent: (name) { Navigator.pop(context); }); });}
复制代码



A1. Scaffold & resizeToAvoidBottomInset

    对于含有文本框的自定义 Dialog,小菜在最外层使用的是 Material 嵌套,小菜通过采用 Scaffold 来嵌套处理,默认 ScaffoldresizeToAvoidBottomPadding / resizeToAvoidBottomInsettrue,当设置为 false 时,文本框获取焦点时,依旧会被软键盘遮挡;因为在固定情景可以配合 resizeToAvoidBottomPadding 实现是否被软键盘遮挡效果;


    resizeToAvoidBottomPadding 主要用于自身 Widget 是否避免被其他窗口遮挡;其中小菜查资料介绍在 Flutter 1.1.9 之后更推荐使用 resizeToAvoidBottomInset


class BillNameDialog extends Dialog {  final Function onCancelEvent;  final Function onSureEvent;
BillNameDialog({Key key, @required this.onCancelEvent, @required this.onSureEvent});
@override Widget build(BuildContext context) { return Material( type: MaterialType.transparency, child: Scaffold( backgroundColor: Colors.transparent, resizeToAvoidBottomPadding: true, body: Stack(children: [ Center( child: Container( decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.0)), margin: EdgeInsets.only(left: 20.0, right: 20.0), child: Padding(padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 25.0), child: Column(mainAxisSize: MainAxisSize.min, children: [ Text('账单名称', style: TextStyle(fontSize: 16.0)), _nameWid(), Row(children: [ Expanded(child: _actionButtons(0)), SizedBox(width: 15.0), Expanded(child: _actionButtons(1)) ]) ])))) ]))); }
_nameWid() { return Container( margin: EdgeInsets.symmetric(vertical: 25.0), child: TextField( controller: TextEditingController(), decoration: InputDecoration( contentPadding: const EdgeInsets.symmetric(horizontal: 15.0), hintText: '创建订单名称', hintStyle: TextStyle(fontSize: 14.0), border: OutlineInputBorder(borderRadius: BorderRadius.circular(10.0), borderSide: BorderSide.none), filled: true, fillColor: Color(0xFFf1efe5)))); }
_actionButtons(type) { return InkWell( child: Container( alignment: Alignment.center, decoration: BoxDecoration( color: type == 0 ? Color(0xFF1E1E1E) : Colors.deepOrange, border: Border.all( color: type == 0 ? Color(0xFF1E1E1E) : Colors.deepOrange), borderRadius: BorderRadius.circular(6.0)), child: Padding( padding: EdgeInsets.symmetric(vertical: 6.0, horizontal: 12.0), child: Text(type == 0 ? '取消' : '确认', style: TextStyle(color: Colors.white, fontSize: 15.0)))), onTap: () { if (type == 0) { onCancelEvent(); } else { onSureEvent(); } }); }}
复制代码



Q2. 对话框进行状态更新

    小菜自定义一个可以多选 itemDialog,但 Dialog 中并没有状态更新的 State,如何进行 Dialog 中状态更新呢?


class TypeListDialog extends Dialog {  @override  Widget build(BuildContext context) {    return Material( type: MaterialType.transparency,        child: Center( child: Container(                decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.0)),                margin: EdgeInsets.symmetric(horizontal: 25.0),                child: Padding(padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 25.0),                    child: Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min,                        children: [                          _itemTag(allType, null, 0),                          SizedBox(height: 15.0),                          _wrapListTag(levelType, this.data),                          SizedBox(height: 10.0),                          Row(mainAxisAlignment: MainAxisAlignment.end,                              children: [ _bottomButton(0, context), SizedBox(width: 15.0), _bottomButton(1, context) ])                        ])))));  }
_wrapListTag(type, tagList) { List<Widget> tagWid = []; if (tagList != null && tagList.length > 0) { for (int i = 0; i < tagList.length; i++) { tagWid.add(_itemTag(type, tagList, i)); } } else { tagWid.add(Container(width: 0.0, height: 0.0)); } return Wrap(children: tagWid, spacing: 15.0, runSpacing: 15.0); }
_bottomButton(type, context) { return InkWell( child: Container( decoration: BoxDecoration(color: type == 0 ? Colors.black : Colors.deepOrange, border: Border.all(color: type == 0 ? Colors.black : Colors.deepOrange), borderRadius: BorderRadius.circular(6.0)), child: Padding(padding: EdgeInsets.symmetric(vertical: 6.0, horizontal: 12.0), child: Text(type == 0 ? 'Cancel' : 'Sure', style: TextStyle(color: Colors.white, fontSize: 16.0)))), onTap: () { }); }}
Future<void> _showTypeListDialog() async { await showDialog(context: context, barrierDismissible: false, builder: (BuildContext context) { return TypeListDialog(data: levelList); });}
复制代码


A2. 创建一个 StatefulBuilder 构造器

    小菜之前在 showDialog 时直接创建了 TypeListDialog,此时是无状态的,当 WidgetBuilder 创建一个 StatefulBuilder 有状态的构造器即可,可以将 state 传递到 Dialog 中;


final Function state;TypeListDialog({Key key, this.data, @required this.state, @required this.onSelectEvent});
_itemTag(type, list, index) { bool _isChecked = false; if (allType == type) { _isChecked = isAllChecked; } else { _isChecked = list[index].isChecked; } return InkWell( key: GlobalKey(), child: Container( decoration: BoxDecoration(color: _isChecked ? Colors.deepOrange : Colors.white, border: Border.all(color: _isChecked ? Colors.deepOrange : Colors.black), borderRadius: BorderRadius.circular(18.0)), child: Padding( padding: EdgeInsets.only( top: 5.0, bottom: 5.0, left: 16.0, right: 16.0), child: Text(type == allType ? '全部' : list[index].name, style: TextStyle(color: _isChecked ? Colors.white : Colors.black, fontSize: 16.0)))), onTap: () { if (type == levelType) { List.generate(data.length, (curIndex) { if (index == curIndex) { data[curIndex].isChecked = !data[curIndex].isChecked; if (data[curIndex].isChecked) { levelBackList.add(data[curIndex].name); } } }); state(() {}); } });}
Future<void> _showTypeListDialog() async { await showDialog(context: context, barrierDismissible: false, builder: (BuildContext context) { return StatefulBuilder(builder: (context, state) => TypeListDialog(state: state, data: levelList)); });}
复制代码


Q3. Dialog 回调传参

    小菜在自定义 Dialog 时如何在一个回调方法中传递多个参数?

A3. 接收方法与 Function 传递参数匹配

    小菜在 Dialog 的回调方法中传递两个 List,而在接收回调方法中匹配两个参数即可;小菜简单看作是一个函数方法;


// 传参方法onSelectEvent(levelBackList, ['a', 'b', 'c']);
// 接收方法Future<void> _showTypeListDialog() async { await showDialog(context: context, barrierDismissible: false, builder: (BuildContext context) => StatefulBuilder( builder: (context, state) => TypeListDialog( state: state, data: levelList, onSelectEvent: (list1, list2) { print('list1=$list1, list2=$list2'); Toast.show('list1=$list1, list2=$list2', context, duration: Toast.LENGTH_SHORT, gravity: Toast.BOTTOM); })));}
复制代码


Q4. AppBar 返回按钮

    小菜在重写 AppBar 时,如何取消默认的返回按钮?



A4. Material | automaticallyImplyLeading

    取消 AppBar 前面的返回图标有多种方式;


  • Scaffold 外层嵌套 Material


@overrideWidget build(BuildContext context) {  return Material(      child: Scaffold(          appBar: AppBar(title: Text('Dialog Page')),          body: _bodyWid()));}
复制代码


  • AppBar 中的 automaticallyImplyLeading 属性设置为 false


@overrideWidget build(BuildContext context) {  return Scaffold(      appBar: AppBar(title: Text('Dialog Page'), automaticallyImplyLeading: false),      body: _bodyWid());}
复制代码


  • 在动态路由跳转时 WidgetBuilder 设置为 MaterialPageRoute 方式;注意,此时 push 需要采用 pushReplacement / pushAndRemoveUntil 等方式;


Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => DialogPage()));
复制代码





    自定义 Dialog 案例源码




    小菜对于 Flutter 的应用还不够熟悉,很多常用的场景会处理的很不到位,小菜会对日常的小问题进行简单记录,逐步学习;如有错误,请多多指导!


来源: 阿策小和尚

发布于: 2021 年 05 月 27 日阅读数: 7
用户头像

还未添加个人签名 2021.05.13 加入

Android / Flutter 小菜鸟~

评论

发布
暂无评论
【Flutter 专题】124 日常问题小结 (三) 自定义 Dialog 二三事