likes
comments
collection
share

Flutter--不受Navigator影响的自定义弹窗

作者站长头像
站长
· 阅读数 5

解决了什么问题:

  1. 原生的showDialog基于Navigator弹出和关闭,在具有一定复杂度的业务中无法判断你在pop谁。我这个方案每个弹窗都有一个对应的实例可以控制,且不会被Navigatorpop方法影响。
  2. 优化了对于Loading业务的弹窗使用,原生弹窗很难在非线性流程下控制关闭弹窗。

样式展示

Flutter--不受Navigator影响的自定义弹窗

Flutter--不受Navigator影响的自定义弹窗

显示一般类型的Alert

DialogController alert = DialogController.alert(
  title: "Title",
  subTitle: "SubTitle",
  onCancel: () {
    print("alert cancle");
  },
  run: () async {
    // 一些耗时操作
    return true;
  },
);
alert.show(context);

显示加载类型的Alert

DialogController loadingAlert = DialogController.loadingAlert();
loadingAlert.showWithTimeout(context, timeout: 10);
// await 其他耗时操作 
loadingAlert.close();

实现代码,这里只是提供一个思路,更多样式按照这个规范拓展即可

import 'dart:async';

import 'package:flutter/material.dart';

class BaseDialog {
  BaseDialog(this._barrierDismissible, this._alignment);

  /// 点击背景是否关闭弹窗
  final bool _barrierDismissible;
  final AlignmentGeometry _alignment;

  /// 页面状态,用来做动画判断
  bool _isCloseState = true;

  /// 动画时长
  final _milliseconds = 240;

  /// 初始化dialog的内容
  /// [isClose]用来标识动画的状态
  /// [milliseconds]用来标识动画时长
  initContentView(Widget Function(BuildContext context, bool isClose, int milliseconds) builder) {
    _overlayEntry = OverlayEntry(
      builder: (context) {
        return Stack(
          alignment: _alignment,
          children: <Widget>[
            // 背景
            Positioned.fill(
              child: GestureDetector(
                onTap: () {
                  // 点击背景关闭页面
                  if (_barrierDismissible) close();
                },
                child: AnimatedOpacity(
                  opacity: _isCloseState ? 0.0 : 1,
                  duration: Duration(milliseconds: _milliseconds),
                  child: Container(
                    color: Colors.black.withOpacity(0.5),
                  ),
                ),
              ),
            ),
            builder(context, _isCloseState, _milliseconds),
          ],
        );
      },
    );
  }

  late OverlayEntry _overlayEntry;
  bool _isPop = true;

  /// 显示弹窗
  /// 小等于0不设置超时
  void show(BuildContext context, int timeout) async {
    //显示弹窗
    Overlay.of(context).insert(_overlayEntry);
    // 稍微延迟一下,不然动画不动
    await Future.delayed(const Duration(milliseconds: 10));
    _isCloseState = false;
    // 重新build启动动画
    _overlayEntry.markNeedsBuild();
    _isPop = true;
    // 启动计时器,timeout秒后执行关闭操作
    if (timeout > 0) {
      Future.delayed(Duration(seconds: timeout), () => close());
    }
  }

  /// 关闭弹窗
  Future<void> close() async {
    if (_isPop) {
      _isPop = false;
      _isCloseState = true;
      // 重新build启动动画
      _overlayEntry.markNeedsBuild();
      // 等待动画结束后再移除涂层
      await Future.delayed(Duration(milliseconds: _milliseconds));
      _overlayEntry.remove();
      onClose();
    }
  }

  void Function() onClose = (){};
}

class DialogController {
  DialogController(this._baseDialog);

  final BaseDialog _baseDialog;

  /// 关闭弹窗
  close() {
    _baseDialog.close();
  }

  /// 显示弹窗
  show(BuildContext context) {
    _baseDialog.show(context, 0);
  }

  /// 显示一个默认带超时的弹窗
  /// 小等于0不设置超时
  void showWithTimeout(BuildContext context, {int timeout = 20}) {
    _baseDialog.show(context, timeout);
  }

  /// 创造一个普通样式的alert弹窗
  /// 它显示在屏幕中央,具有一个标题和内容描述文本,
  /// [onBarrierTap]当点击背景时触发
  factory DialogController.alert({
    required String title,
    required String subTitle,
    bool barrierDismissible = true,
    Future<bool> Function()? run,
    void Function()? onCancel,
  }) {
    final dialog = BaseDialog(
      barrierDismissible,
      AlignmentDirectional.center,
    );
    if (onCancel != null) {
      dialog.onClose = onCancel;
    }
    dialog.initContentView((context, isClose, int milliseconds) {
      return AnimatedOpacity(
        opacity: isClose ? 0.0 : 1,
        duration: Duration(milliseconds: milliseconds),
        child: AlertDialog(
          title: Text(title),
          content: Text(subTitle),
          actions:  [
            FilledButton.tonal(
              onPressed: () {
                dialog.close();
              },
              child: const Text("取消"),
            ),
            FilledButton(
              onPressed: () async {
                if (run != null) {
                  final r = await run();
                  if (r) dialog.close();
                } else {
                  dialog.close();
                }
              },
              child: const Text("确认"),
            )
          ],
        ),
      );
    });
    return DialogController(dialog);
  }

  factory DialogController.loadingAlert({
    String? title,
    String? subTitle,
  }) {
    final dialog = BaseDialog(
      false,
      AlignmentDirectional.center,
    );
    dialog.initContentView((context, isClose, int milliseconds) {
      return AnimatedOpacity(
        opacity: isClose ? 0.0 : 1,
        duration: Duration(milliseconds: milliseconds),
        child: AlertDialog(
          title: Text(title ?? "正在加载"),
          content: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const SizedBox(height: 16),
              const SizedBox(
                width: 24,
                height: 24,
                child: CircularProgressIndicator(strokeWidth: 3,),
              ), // 添加一个加载指示器
              const SizedBox(height: 16),
              Text(subTitle ?? '请等待...'), // 提示用户等待
            ],
          ),
        ),
      );
    });
    return DialogController(dialog);
  }
}
转载自:https://juejin.cn/post/7347910930480742400
评论
请登录