是否可以为 Flutter 中的两个对话框设置单独的 BuildContext?

Is it possible to have separate BuildContext for two dialogs in Flutter?

我想控制在 Flutter 中关闭特定对话框的方式。我知道如果我调用 Navigator.of(context).pop() 它将关闭最新的对话框。

但是我的情况是我可以同时以不同的顺序打开两个对话框(a -> bb -> a),我想明确关闭其中一个。 我知道 showDialog 构建器方法提供了一个 BuildContext 我可以引用并执行 Navigator.of(storedDialogContext).pop() 但实际上并没有真正帮助,因为此上下文共享相同的导航堆栈。


更新Vandan has provided useful . One solution is to use Overlay widget but it has its downsides, see this answer


我的例子在dartpad.dev,例子代码:

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Completer<BuildContext>? _dialog1Completer;
  Completer<BuildContext>? _dialog2Completer;
  bool _opened1 = false;
  bool _opened2 = false;

  @override
  void initState() {
    super.initState();
    Timer(const Duration(seconds: 3), () {
      _openDialog1();
      debugPrint('Opened dialog 1. Dialog should read: "Dialog 1"');
      Timer(const Duration(seconds: 2), () {
        _openDialog2();
        debugPrint('Opened dialog 2. Dialog should read: "Dialog 2"');
        Timer(const Duration(seconds: 3), () {
          _closeDialog1();
          debugPrint('Closed dialog 1. Dialog should read: "Dialog 2"');
          Timer(const Duration(seconds: 5), () {
            _closeDialog2();
            debugPrint('Closed dialog 2. You should not see any dialog at all.');
          });
        });
      });
    });
  }

  Future<void> _openDialog1() async {
    setState(() {
      _opened1 = true;
    });

    _dialog1Completer = Completer<BuildContext>();

    await showDialog(
        barrierDismissible: false,
        context: context,
        routeSettings: const RouteSettings(name: 'dialog1'),
        builder: (dialogContext) {
          if (_dialog1Completer?.isCompleted == false) {
            _dialog1Completer?.complete(dialogContext);
          }

          return CustomDialog(title: 'Dialog 1', timeout: false, onClose: _closeDialog1);
        });
  }

  Future<void> _openDialog2() async {
    setState(() {
      _opened2 = true;
    });

    _dialog2Completer = Completer<BuildContext>();

    await showDialog(
        barrierDismissible: false,
        context: context,
        routeSettings: const RouteSettings(name: 'dialog1'),
        builder: (dialogContext) {
          if (_dialog2Completer?.isCompleted == false) {
            _dialog2Completer?.complete(dialogContext);
          }

          return CustomDialog(title: 'Dialog 2', timeout: false, onClose: _closeDialog2);
        });
  }

  Future<void> _closeDialog1() async {
    final ctx = await _dialog1Completer?.future;

    if (ctx == null) {
      debugPrint('Could not closed dialog 1, no context.');
      return;
    }

    Navigator.of(ctx, rootNavigator: true).pop();

    setState(() {
      _dialog1Completer = null;
      _opened1 = false;
    });
  }

  Future<void> _closeDialog2() async {
    final ctx = await _dialog2Completer?.future;

    if (ctx == null) {
      debugPrint('Could not closed dialog 2, no context.');
      return;
    }

    Navigator.of(ctx, rootNavigator: true).pop();

    setState(() {
      _dialog2Completer = null;
      _opened2 = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          mainAxisSize: MainAxisSize.max,
          children: <Widget>[
            TextButton(onPressed: _openDialog1, child: const Text('Open 1')),
            TextButton(onPressed: _openDialog2, child: const Text('Open 2')),
            const Spacer(),
            Align(
              alignment: Alignment.bottomCenter,
              child: Text('Opened 1? $_opened1\nOpened 2? $_opened2'),
            ),
          ],
        ),
      ),
    );
  }
}

class CustomDialog extends StatefulWidget {
  const CustomDialog({
    Key? key,
    required this.timeout,
    required this.title,
    required this.onClose,
  }) : super(key: key);

  final bool timeout;
  final String title;
  final void Function() onClose;
  @override
  createState() => _CustomDialogState();
}

class _CustomDialogState extends State<CustomDialog>
    with SingleTickerProviderStateMixin {
  late final Ticker _ticker;
  Duration? _elapsed;
  final Duration _closeIn = const Duration(seconds: 5);
  late final Timer? _timer;

  @override
  void initState() {
    super.initState();
    _timer = widget.timeout ? Timer(_closeIn, widget.onClose) : null;
    _ticker = createTicker((elapsed) {
      setState(() {
        _elapsed = elapsed;
      });
    });
    _ticker.start();
  }

  @override
  void dispose() {
    _ticker.dispose();
    _timer?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: Text(widget.title),
      content: SizedBox(
          height: MediaQuery.of(context).size.height / 3,
          child: Center(
          child: Text([
        '${_elapsed?.inMilliseconds ?? 0.0}',
        if (widget.timeout) ' / ${_closeIn.inMilliseconds}',
      ].join('')))),
      actions: [
        TextButton(onPressed: widget.onClose, child: const Text('Close'))
      ],
    );
  }
}

如果您要 运行 此代码并观察控制台,您可以看到正在打印的步骤,在步骤 #3 中您可以观察到不需要的行为:

  1. 打开对话框 1 - 确定
  2. 打开对话框 2 - 确定
  3. 关闭对话框 1 - 不正常

我想我理解了这个问题,Navigator.of(dialogContext, rootNavigator: true) 搜索最近的导航器,然后在其上调用 .pop() 方法,从其堆栈中删除最新的 route/dialog。 我需要删除特定的对话框。

这里的解决方案是什么?多个 Navigator 个对象?

我强烈建议您在这种情况下在 Flutter 中使用 Overlay。叠加层独立于屏幕上的小部件呈现,并有自己的生命周期。它们会在您要求时出现,您可以控制它们何时消失以及哪一个消失。