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

简介: 0 基础学习 Flutter,第一百二十四步:继续整理日常遇到的小问题!

    针对日常不同的需求,我们时常需要自定义 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
@override
Widget build(BuildContext context) {
  return Material(
      child: Scaffold(
          appBar: AppBar(title: Text('Dialog Page')),
          body: _bodyWid()));
}
  • AppBar 中的 automaticallyImplyLeading 属性设置为 false
@override
Widget 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 的应用还不够熟悉,很多常用的场景会处理的很不到位,小菜会对日常的小问题进行简单记录,逐步学习;如有错误,请多多指导!

来源: 阿策小和尚
目录
相关文章
|
1月前
|
UED 开发者 容器
Flutter&鸿蒙next 的 Sliver 实现自定义滚动效果
Flutter 提供了强大的滚动组件,如 ListView 和 GridView,但当需要更复杂的滚动效果时,Sliver 组件是一个强大的工具。本文介绍了如何使用 Sliver 实现自定义滚动效果,包括 SliverAppBar、SliverList 等常用组件的使用方法,以及通过 CustomScrollView 组合多个 Sliver 组件实现复杂布局的示例。通过具体代码示例,展示了如何实现带有可伸缩 AppBar 和可滚动列表的页面。
110 1
|
1月前
Flutter 自定义组件继承与调用的高级使用方式
本文深入探讨了 Flutter 中自定义组件的高级使用方式,包括创建基本自定义组件、继承现有组件、使用 Mixins 和组合模式等。通过这些方法,您可以构建灵活、可重用且易于维护的 UI 组件,从而提升开发效率和代码质量。
131 1
|
1月前
|
前端开发 开发者
深入探索 Flutter 鸿蒙版的画笔使用与高级自定义动画
本文深入探讨了 Flutter 中的绘图功能,重点介绍了 CustomPainter 和 Canvas 的使用方法。通过示例代码,详细讲解了如何绘制自定义图形、设置 Paint 对象的属性以及实现高级自定义动画。内容涵盖基本绘图、动画基础、渐变动画和路径动画,帮助读者掌握 Flutter 绘图与动画的核心技巧。
80 1
|
1月前
|
Dart UED 开发者
Flutter&鸿蒙next中的按钮封装:自定义样式与交互
在Flutter应用开发中,按钮是用户界面的重要组成部分。Flutter提供了多种内置按钮组件,但有时这些样式无法满足特定设计需求。因此,封装一个自定义按钮组件变得尤为重要。自定义按钮组件可以确保应用中所有按钮的一致性、可维护性和可扩展性,同时提供更高的灵活性,支持自定义颜色、形状和点击事件。本文介绍了如何创建一个名为CustomButton的自定义按钮组件,并详细说明了其样式、形状、颜色和点击事件的处理方法。
86 1
|
1月前
|
Dart 搜索推荐 API
Flutter & 鸿蒙next版本:自定义对话框与表单验证的动态反馈与错误处理
在现代移动应用开发中,用户体验至关重要。本文探讨了如何在 Flutter 与鸿蒙操作系统(HarmonyOS)中创建自定义对话框,并结合表单验证实现动态反馈与错误处理,提升用户体验。通过自定义对话框和表单验证,开发者可以提供更加丰富和友好的交互体验,同时利用鸿蒙next版本拓展应用的受众范围。
84 1
|
2月前
|
开发者 UED
flutter:dialog (十一)
本文介绍了 Flutter 中常用的弹窗组件和方法,包括 `AlertDialog`、`SimpleDialog`、`showModalBottomSheet` 和 `toast`。每个组件的使用方法和示例代码都进行了详细说明,帮助开发者快速理解和应用这些弹窗功能。例如,`AlertDialog` 用于显示带有标题、内容和按钮的对话框;`SimpleDialog` 用于显示多个选项供用户选择;`showModalBottomSheet` 用于从屏幕底部弹出模态对话框;而 `toast` 则用于显示短暂的消息提示。文中还提供了如何处理点击事件、取消弹窗等常见操作的代码示例。
|
3月前
|
前端开发 搜索推荐
Flutter中自定义气泡框效果的实现
Flutter中自定义气泡框效果的实现
114 3
|
4月前
|
前端开发
Flutter快速实现自定义折线图,支持数据改变过渡动画
Flutter快速实现自定义折线图,支持数据改变过渡动画
109 4
Flutter快速实现自定义折线图,支持数据改变过渡动画
|
4月前
|
开发者 监控 开发工具
如何将JSF应用送上云端?揭秘在Google Cloud Platform上部署JSF应用的神秘步骤
【8月更文挑战第31天】本文详细介绍如何在Google Cloud Platform (GCP) 上部署JavaServer Faces (JSF) 应用。首先,确保已准备好JSF应用并通过Maven构建WAR包。接着,使用Google Cloud SDK登录并配置GCP环境。然后,创建`app.yaml`文件以配置Google App Engine,并使用`gcloud app deploy`命令完成部署。最后,通过`gcloud app browse`访问应用,并利用GCP的监控和日志服务进行管理和故障排查。整个过程简单高效,帮助开发者轻松部署和管理JSF应用。
64 0
|
4月前
|
开发者 容器 Java
Azure云之旅:JSF应用的神秘部署指南,揭开云原生的新篇章!
【8月更文挑战第31天】本文探讨了如何在Azure上部署JavaServer Faces (JSF) 应用,充分发挥其界面构建能力和云平台优势,实现高效安全的Web应用。Azure提供的多种服务如App Service、Kubernetes Service (AKS) 和DevOps简化了部署流程,并支持应用全生命周期管理。文章详细介绍了使用Azure Spring Cloud和App Service部署JSF应用的具体步骤,帮助开发者更好地利用Azure的强大功能。无论是在微服务架构下还是传统环境中,Azure都能为JSF应用提供全面支持,助力开发者拓展技术视野与实践机会。
21 0