Flutter 中如何优雅地使用弹框

news2024/12/27 10:47:01

在这里插入图片描述
日常开发中,Flutter 弹框(Dialog)是我们使用频率非常高的控件。无论是提示用户信息、确认用户操作,还是表单填写,弹框都能派上用场。然而,看似简单的弹框,实际使用起来却有不少坑和使用的技巧。今天,我们就来聊聊这些弹框的使用技巧,文末还有关于在 bloc 如何使用弹框的内容,保证你看完之后干货满满,下面直接开始吧。

返回值的处理

用户在点击退出登录时,通常的做法就是弹框用来确认是否退出登录,返回值是 bool 类型,为 true 表示退出登录,反之不需要退出,这个时候应该怎处理这个 bool 类型的返回值呢?我们先来看看最常见的写法。

_showDialogWithCallBack(Function(bool) callBack){
  showDialog(
    context: context,
    builder: (context) {
      return AlertDialog(
        title: const Text("Logout"),
        content: const Text("Are you sure you want to logout?"),
        actions: [
          TextButton(
            onPressed: () {
              callBack(false);
              Navigator.of(context).pop();
            },
            child: const Text("Cancel"),
          ),
          TextButton(
            onPressed: () {
              callBack(true);
              Navigator.of(context).pop();
            },
            child: const Text("Sure"),
          )
        ],
      );
    },
  );
}

点击按钮后调用 _showDialogWithCallBack 函数来显示弹框。

TextButton(
  onPressed: () {
    _showDialogWithCallBack((result) {
      print("Result: $result");
    });
  },
  child: Text('Dialog with callBack'),
)

除了上面这种使用回调函数传递结果,还有没有其它更加优雅的写法呢?我们知道 showDialog 本身返回的就是 Future 对象,如果需要在 Dialog 关闭后继续执行一些其它逻辑,我们可以使用 awaitasync 关键字来接收返回数据处理异步操作,下面来看看该怎么实现。

Future<bool?> _showAsyncDialog() {
  return showDialog<bool>(
    context: context,
    builder: (context) {
      return AlertDialog(
        title: const Text("Logout"),
        content: const Text("Are you sure you want to logout?"),
        actions: [
          TextButton(
            onPressed: () {
              Navigator.of(context).pop(false);
            },
            child: const Text("Cancel"),
          ),
          TextButton(
            onPressed: () {
              Navigator.of(context).pop(true);
            },
            child: const Text("Sure"),
          )
        ],
      );
    },
  );
}

利用 Navigator.of(context).pop(true) 方法来关闭对话框,并传递 true 或者 false 值作为对话框的返回结果,当点击按钮调用 _showAsyncDialog 弹出 Dialog

TextButton(
  onPressed: () async {
    bool? result = await _showAsyncDialog();
    print("Result: $result");
  },
  child: Text('Async Dialog'),
)

在返回 result 之后再去处理其它的逻辑,相对于使用回调函数来处理结果,这种写法是不是更加的简洁,避免了回调函数那种代码嵌套,代码看起来也更清晰易懂吧。

如果 Dialog 中带有表单(如:TextField)该如何处理返回数据呢?其实和上面的处理方式是一样的,也可使用 asyncawait 来处理 showDialog 返回的 Future 对象,以返回用户输入的年龄(int 类型)为例,其实现如下:

Future<int?> _getAge(BuildContext context) {
  final controller = TextEditingController();
  return showDialog(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: const Text("How old are you?"),
          content: TextField(
              decoration: const InputDecoration(hintText: "e.g:22"),
              keyboardType: TextInputType.number,
              autofocus: true,
              maxLength: 3,
              controller: controller,
              inputFormatters: [
                TextInputFormatter.withFunction((oldValue, newValue) {
                  if (newValue.text.isEmpty) return newValue;
                  try {
                    final age = int.parse(newValue.text);
                    if (age > 120) return oldValue;
                    return newValue;
                  } catch (err) {
                    return oldValue;
                  }
                })
              ]),
          actions: [
            TextButton(
                onPressed: () {
                  Navigator.pop(context);
                },
                child: const Text("Cancel")),
            TextButton(
                onPressed: () {
                  final age = int.parse(controller.text);
                  Navigator.of(context).pop(age);
                },
                child: const Text("Save"))
          ],
        );
      });
}

类似上面这种写法也同样适用于各种类型的 ActionSheet 返回值的处理。

自定义 Dialog 内容

上面的例子中 showDialogbuilder 函数返回内容都使用的是 AlertDialog 类,它属于 Material 设计风格的,而在我们平时开发的时候往往需要根据设计稿定制自己的 Dialog ,这个时候就需要自定义 Dialog 的内容了,如直接在 builder 函数中返回自定义的 widget 类,但是这个时候需要自己处理圆角、边框和阴影,还有处理点击对话框外部自动关闭对话框等功能。如果想要快速的实现一个自定义内容的弹框可以使用 Dialog 组件。

class Dialog extends StatelessWidget {
  const Dialog({
    super.key,
    this.backgroundColor,
    this.elevation,
    this.shadowColor,
    this.surfaceTintColor,
    this.insetAnimationDuration = const Duration(milliseconds: 100),
    this.insetAnimationCurve = Curves.decelerate,
    this.insetPadding = _defaultInsetPadding,
    this.clipBehavior = Clip.none,
    this.shape,
    this.alignment,
    this.child,
  }) : assert(clipBehavior != null),
       assert(elevation == null || elevation >= 0.0),
       _fullscreen = false;
  
  // .....
}

Dialog 小组价的构造函数可以看出来,其本身内置了背景色、阴影、边框、对其、边距和动画等属性,为对话框提供了默认样式和行为,定制自己的对话框时改起来也很方便。实现代码如下:

_showCustomDialog() {
  showDialog(
    context: context,
    // 设置背景透明度
    barrierColor: Colors.black.withOpacity(0.5),
    builder: (BuildContext context) {
      return Dialog(
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(12.0),
        ),
        child: Container(
          padding: EdgeInsets.all(16.0),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              SizedBox(height: 10),
              Text('Custom Dialog Title', style: TextStyle(fontSize: 20)),
              SizedBox(height: 16),
              Text('This is a custom dialog content.'),
              SizedBox(height: 16),
              TextButton(
                onPressed: () {
                  Navigator.of(context).pop();
                },
                child: Text('OK'),
              ),
              SizedBox(height: 10),
            ],
          ),
        ),
      );
    },
  );
}

bloc 中使用弹框

在使用 bloc(业务逻辑组件)进行状态管理时,很多的时候特定的业务逻辑执行过程中弹出对话框,如保存表单数据时,如果出现网络错误或验证错误,可以弹出对话框通知用户;执行删除操作时需要弹出对话框让用户确认操作是否继续;执行耗时操作弹出一个加载对话框,向用户展示操作正在进行,特别是在和硬件网关 zigbee 通信时,如查找设备,会有等待和重试的过程;以及完成某个操作时提示用户操作结果等等场景。但是我们在调用 showDialog 又需要传 context 参数,context 是用来标记 widgetwidget tree 的位置,bloc 中获取不到 context,那么在bloc 中使用弹框改如何实现呢?

还记得在《Flutter大型项目架构:路由管理篇》文章中实现的 AppNavigatorImpl 类吗?还有实现的 pushpop等操作,我们今天要实现在 bloc 弹框也是在 AppNavigatorImpl 类中实现。首先在其抽象类 AppNavigator 中添加如下代码:

abstract class AppNavigator {
	// ...
  Future<T?> showDialog<T extends Object?>(
    AppPopupInfo appPopupInfo, {
    bool barrierDismissible = true,
    bool useSafeArea = false,
    bool useRootNavigator = true,
  });
  // ...
}

AppNavigatorImpl 实现 showDialog 函数:

(as: AppNavigator)
class AppNavigatorImpl extends AppNavigator with LogMixin {
  AppNavigatorImpl(
    this._appRouter,
    // 注入 PopupInfoMapper
    this._appPopupInfoMapper,
    this._appRouteInfoMapper,
  );

  TabsRouter? tabsRouter;

  final AppRouter _appRouter;
  // BasePopupInfoMapper 是抽象类, PopupInfoMapper 的父类
  final BasePopupInfoMapper _appPopupInfoMapper;
  final BaseRouteInfoMapper _appRouteInfoMapper;
  final _popups = <AppPopupInfo>{};
  
  StackRouter? get _currentTabRouter =>
      tabsRouter?.stackRouterOfIndex(currentBottomTab);

  StackRouter get _currentTabRouterOrRootRouter =>
      _currentTabRouter ?? _appRouter;

  BuildContext get _rootRouterContext =>
      _appRouter.navigatorKey.currentContext!;

  BuildContext? get _currentTabRouterContext =>
      _currentTabRouter?.navigatorKey.currentContext;

  BuildContext get _currentTabContextOrRootContext =>
      _currentTabRouterContext ?? _rootRouterContext;
  
    
  int get currentBottomTab {
    if (tabsRouter == null) {
      throw 'Not found any TabRouter';
    }
    return tabsRouter?.activeIndex ?? 0;
  }
  
  
  Future<T?> showDialog<T extends Object?>(
    AppPopupInfo appPopupInfo, {
    bool barrierDismissible = true,
    bool useSafeArea = false,
    bool useRootNavigator = true,
  }) {
    if (_popups.contains(appPopupInfo)) {
      return Future.value(null);
    }
    _popups.add(appPopupInfo);
    
    return showDialog<T>(
      context: useRootNavigator
          ? _rootRouterContext
          : _currentTabContextOrRootContext,
      builder: (_) => m.WillPopScope(
        onWillPop: () async {
          _popups.remove(appPopupInfo);
          return Future.value(true);
        },
        child: _appPopupInfoMapper.map(appPopupInfo, this),
      ),
      useRootNavigator: useRootNavigator,
      barrierDismissible: barrierDismissible,
      useSafeArea: useSafeArea,
    );
  }
}

这里的 _rootRouterContextauto_route 的根 navigatorKey 提供的,用于管理和控制导航操作,这样可以实现在整个应用程序的任何地方访问和操作导航堆栈。auto_route 库通过 NavigatorGlobalKey 的结合实现了对 navigatorKey 的支持。_currentTabRouterContext 是当前所显示的 tabnavigatorKey 提供的,负责当前 tab 及子页面的导航操作。当调用 showDialog 时就可以使用 _rootRouterContext 或者 _currentTabContextOrRootContext 参数,这就解决了 context 参数的问题了。

AppPopupInfoAppRouteInfo 的作用是一样的,实现了抽象类 BasePopupInfoMapper,使用 @freezed 注解,在 AppPopupInfoMappermap 函数中使用 when 可根据不同工厂方法返回不同类型的 AppPopupInfo 实例。


class AppPopupInfo with _$AppPopupInfo {
  const factory AppPopupInfo.confirmDialog({
    ('') String message,
    Function<void>? onPressed,
  }) = _ConfirmDialog;
}

abstract class BasePopupInfoMapper {
  Widget map(AppPopupInfo appRouteInfo, AppNavigator navigator);
}

(as: BasePopupInfoMapper)
class AppPopupInfoMapper extends BasePopupInfoMapper {
  
  Widget map(AppPopupInfo appPopupInfo, AppNavigator navigator) {
    return appPopupInfo.when(
      confirmDialog: (message, onPressed) {
        // 这里返回的是 Dialog 的内容
        return Dialog(
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(12.0),
          ),
          child: Container(
            padding: EdgeInsets.all(16.0),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                Text(message),
                SizedBox(height: 16),
                TextButton(
                  onPressed: onPressed,
                  child: Text('OK'),
                ),
                SizedBox(height: 10),
              ],
            ),
          ),
        );
      },
    );
  }
}

bloc 中或者页面中调用:

navigator.showDialog(AppPopupInfo.confirmDialog(
  message: "显示弹框",
  onPressed: () {
    //...
  }));

这里的 navigator 是从哪里来的呢?在《Flutter大型项目架构:路由管理篇》文章中最后一部分bloc 中使用 navigator 跳转页面介绍了将 navigator 注入到 BaseBlocBasePageState,感兴趣的可以去看看,这样无论是在 bloc 层还是 page 页面都能使用 navigator 跳转页面和弹出 Dialog 等操作,在 bloc 层使用 ActionSheetToast 等和 Dialog 一样的逻辑。

bloc 层使用弹框的实现过程其实也是路由管理的一部分,本篇文章是单独把弹框的使用做一个集锦,也希望能够帮到你。那么,在项目中你是如何使用弹框的呢,关于弹框的使用有什么好的建议和想法欢迎留言,感谢您的阅读,记得关注加点赞哦!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1688475.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

轻松找回误删短信 | 超强安卓短信恢复神器

概括 我们都曾经历过不小心删除了重要消息&#xff0c;后来又后悔并认为可能无法恢复它们的情况。从技术上讲&#xff0c;该消息不会被删除&#xff1b;它会在您的 Android 手机上存储一段时间。 可以执行 Android 短信恢复&#xff0c;因为它需要一段时间才能从您的 Android…

JavaScript面试 题

1.延时加载JS有哪些方式 延时加载 :async defer 例如:<script defer type"type/javascript" srcscript.js></ script> defer:等html全部解析完成,才会执行js代码,顺次执行的 async: js和html解析是同步的,不是顺次执行js脚本(谁先加载完先执行谁)2.JS数…

黑龙江等保测评深入理解

“没有网络安全&#xff0c;就没有国家安全”&#xff0c;等级保护测评是指按照网络安全系统制定的一系列的防护过程&#xff0c;对已经有的和即将上线的商业服务的基础设施&#xff08;系统&#xff0c;数据库&#xff0c;中间件等&#xff09;所做的一系列的检查&#xff0c;…

代码随想录-Day18

513. 找树左下角的值 给定一个二叉树的 根节点 root&#xff0c;请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至少有一个节点。 方法一&#xff1a;深度优先搜索 class Solution {int curVal 0;int curHeight 0;public int findBottomLeftValue(TreeNode roo…

Python筑基之旅-MySQL数据库(三)

目录 一、数据库操作 1、创建 1-1、用mysql-connector-python库 1-2、用PyMySQL库 1-3、用PeeWee库 1-4、用SQLAlchemy库 2、删除 2-1、用mysql-connector-python库 2-2、用PyMySQL库 2-3、用PeeWee库 2-4、用SQLAlchemy库 二、数据表操作 1、创建 1-1、用mysql-…

分布式理论--BASE

目录 是什么BASE 与 CAP&#xff0c;ACID 的区别BASE 和 Paxos 类共识算法的区别相关问题 是什么 BASE 理论是对 CAP 理论的进一步扩展主要强调在分布式系统中&#xff0c;为了获得更高的可用性和性能&#xff0c;可以放宽对一致性的要求&#xff0c;是对 CAP 中 AP 方案的一个…

卷爆短剧出海:五大关键,由AIGC重构

短剧高温下&#xff0c;谈谈AIGC的助攻路线。 短剧&#xff0c;一个席卷全球的高温赛道。 以往只是踏着霸总题材&#xff0c;如今&#xff0c;内容循着精品化、IP化的自然发展风向&#xff0c;给内容、制作、平台等产业全链都带来新机&#xff0c;也让短剧消费走向文化深处&am…

D60SB120-ASEMI整流桥D60SB120参数、封装、尺寸

编辑&#xff1a;ll D60SB120-ASEMI整流桥D60SB120参数、封装、尺寸 型号&#xff1a;D60SB120 品牌&#xff1a;ASEMI 封装&#xff1a;D-SB 批号&#xff1a;2024 最大重复峰值反向电压&#xff1a;1200V 最大正向平均整流电流(Vdss)&#xff1a;60A 功率(Pd)&#x…

Kubernetes 应用滚动更新

Kubernetes 应用版本号 在 Kubernetes 里&#xff0c;版本更新使用的不是 API 对象&#xff0c;而是两个命令&#xff1a;kubectl apply 和 kubectl rollout&#xff0c;当然它们也要搭配部署应用所需要的 Deployment、DaemonSet 等 YAML 文件。 在 Kubernetes 里应用都是以 …

uniapp开发vue3监听右滑返回操作,返回到指定页面

想要在uniapp框架中监听左滑或者右滑手势&#xff0c;需要使用touchstart和touchend两个api&#xff0c;因为没有原生的左右滑监听api&#xff0c;所以我们只能依靠这两个api来获取滑动开始时候的x坐标和滑动结束后的x坐标做比对&#xff0c;右滑的话&#xff0c;结束时候的x坐…

RabbitMQ(一)概述第一个应用程序

文章目录 概述AMQP和JMS官网安装开始第一个程序 概述 消息队列是实现应用程序和应用程序之间通信的中间件产品 AMQP和JMS 工作体系 官网 https://www.rabbitmq.com/ RabbitMQ是一款基于AMQP、由Erlang语言开发的消息队列产品 安装 # 拉取镜像 docker pull rabbitmq:3.13-m…

微信小程序画布显示图片绘制矩形选区

wxml <view class"page-body"><!-- 画布 --><view class"page-body-wrapper"><canvas canvas-id"myCanvas" type"2d" id"myCanvas" classmyCanvas bindtouchstart"touchStart" bindtouchmo…

UEFI EDK2源码学习(一)——环境安装

部署环境 vmvare15.0 ubuntu20.04 docker edk2 源码 具体步骤 docker安装 # 更新apt软件包索引 sudo apt-get update# 添加docker依赖 sudo apt-get install -y \apt-transport-https \ca-certificates \curl \gnupg-agent \software-properties-common# 添加docker 官方…

白嫖免费图床!CloudFlare R2太香了!

1 为啥要折腾搭建一个专属图床&#xff1f; 技术大佬写博客都用 md 格式&#xff0c;要在多平台发布&#xff0c;图片就得有外链后续如博客迁移&#xff0c;国内博客网站如掘金&#xff0c;简书&#xff0c;语雀等都做了防盗链&#xff0c;图片无法迁移 2 为啥选择CloudFlare…

力扣刷题---返回word中所有不重复的单词

当需要从一个数据集合中去除重复元素时&#xff0c;set是一个很好的选择。由于其不允许存储重复的元素&#xff0c;因此可以很容易地实现去重功能。这在处理原始数据或进行数据分析时特别有用。 题目&#xff1a; 给定一个字符串数组 words&#xff0c;请返回一个由 words 中所…

SpringCloud微服务03-微服务保护-分布式事务-MQ基础-MQ高级

一、微服务保护 1.雪崩问题 如何做好后备方案就是后续&#xff1a; 2.雪崩解决方案 某一个服务的线程是固定的&#xff0c;出现故障线程占满后&#xff0c;就不会让取调用这个服务&#xff0c;对其他服务就没有影响。 3.Sentinel ①初识Sentinel 配置过程&#xff1a;day05-服…

Unity 实现心电图波形播放(需波形图图片)

实现 在Hierarchy 面板从2D Object 中新建一个Sprite&#xff0c;将波形图图片的赋给Sprite。 修改Sprite 的Sprite Renderer 组件中Draw Mode 为Tiled, 修改Sprite Renderer 的Size 即可实现波形图播放。 在Hierarchy 面板从2D Object 中新建一个Sprite Mask 并赋以遮罩图片…

表现层框架设计之表现层设计模式_2.MVP模式

1.MVP模式 MVP&#xff08;Model-View-Presenter&#xff09;模式提供数据&#xff0c;View负责显示&#xff0c;Controller/Presenter负责逻辑的处理。MVP是从经典的模式MVC演变而来&#xff0c;它们的基本思想有相通的地方&#xff1a;Controller/Presenter负责逻辑的处理&am…

10款手机黑科技app,每款都好用到爆!

AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频https://aitools.jurilu.com/ 1. 计划程序应用 —— Android Auto Text Android Auto Text&#xff08;前身为 Do It Later&#xff09;是一款简单易用的日程安排应用程序&am…

LLM实战:当网页爬虫集成gpt3.5

1. 背景 最近本qiang~关注了一个开源项目Scrapegraph-ai&#xff0c;是关于网页爬虫结合LLM的项目&#xff0c;所以想一探究竟&#xff0c;毕竟当下及未来&#xff0c;LLM终将替代以往的方方面面。 这篇文章主要介绍下该项目&#xff0c;并基于此项目实现一个demo页面&#x…