仅在处置当前小部件之前关闭所有对话框和弹出窗口,不应删除其他小部件

Har*_*shi 2 flutter flutter-dialog

我一直在从事一个项目,该项目要求我根据移动设备的orientation.

例如,在portrait方向的情况下,我需要显示一个小部件(比如说纵向小部件),在方向的情况下landscape,我需要显示另一个小部件(我们称之为横向小部件)。

我用过OrientationBuilder

实际问题:在方向更改时,所有Dialogs和/OptionMenu或任何其他 Popup 类型的 Widget 都不会关闭。如何在方向改变时关闭它们?

重现问题的步骤:

  1. 运行下面给出的应用程序代码[用于重现问题的示例应用程序]
  2. 长按应用程序主体以查看对话框或单击OptionMenu左上角的
  3. 更改设备方向,您将看到小部件主体已根据方向发生变化,但小Popuped部件仍然可见。

注意:请注意,我需要针对此问题的全局解决方案。仅专门围绕此代码提供解决方案对我来说没有任何用处。这是我为了更好地理解问题而提供的示例代码,我根本不使用此代码。

示例应用程序代码:

// main.dart
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return OrientationBuilder(builder: (context, orientation) {
      bool isLandscape = orientation == Orientation.landscape;
      return isLandscape ? Landscape() : Portrait();
    });
  }
}

class Portrait extends StatefulWidget {
  @override
  _PortraitState createState() => _PortraitState();
}

class _PortraitState extends State<Portrait> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: buildTitle(),
        actions: <Widget>[_buildOptionMenu(context)],
      ),
      body: GestureDetector(
        onLongPress: () {
          showDialog(
            context: context,
            builder: (context) => AlertDialog(
              content: buildTitle(),
            ),
          );
        },
        child: Container(
          color: Colors.blue.withOpacity(0.4),
          child: Stack(
            fit: StackFit.expand,
            children: <Widget>[
              Center(
                child: buildTitle(),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildOptionMenu(BuildContext context) {
    return PopupMenuButton(itemBuilder: (context) {
      var list = <String>['Portrait-Item-1', 'Portrait-Item-2'];
      return list
          .map<PopupMenuEntry<String>>(
            (e) => PopupMenuItem<String>(
              child: Text(e),
            ),
          )
          .toList();
    });
  }

  Text buildTitle() => Text('Portrait');
}

class Landscape extends StatefulWidget {
  @override
  _LandscapeState createState() => _LandscapeState();
}

class _LandscapeState extends State<Landscape> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: buildTitle(),
        actions: <Widget>[_buildOptionMenu(context)],
      ),
      body: GestureDetector(
        onLongPress: () {
          showDialog(
            context: context,
            builder: (context) => AlertDialog(
              content: buildTitle(),
            ),
          );
        },
        child: Container(
          color: Colors.orange.withOpacity(0.3),
          child: Stack(
            fit: StackFit.expand,
            children: <Widget>[
              Center(
                child: buildTitle(),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Text buildTitle() => Text('Landscape');

  Widget _buildOptionMenu(BuildContext context) {
    return PopupMenuButton(itemBuilder: (context) {
      var list = <String>[
        'Landscape-Item-1',
        'Landscape-Item-2',
        'Landscape-Item-3',
      ];
      return list
          .map<PopupMenuEntry<String>>(
            (e) => PopupMenuItem<String>(
              child: Text(e),
            ),
          )
          .toList();
    });
  }
}
Run Code Online (Sandbox Code Playgroud)

我找不到任何可行的解决方案。有一些解决方案需要监听方向变化并根据新方向推送/弹出小部件。

但它有点太多了,需要在纵向和横向小部件中添加相同类型的代码。如果我需要处理其他方向,例如反向纵向和反向横向,这也是不可扩展的。

更新

可行的解决方案之一是弹出所有小部件直到根小部件,然后根据方向推送新小部件。这有效但会带来副作用。

例如,如果我从纵向推送一些新的小部件(假设是登录页面)。

然后,如果我将设备旋转到横向,它应该以横向模式膨胀登录页面 UI,但根据弹出所有小部件直到根的代码。

我将看到的是横向小部件,而不是横向模式下的登录页面。

为了清楚起见

我正在寻找一个答案,以在其父小部件之前关闭所有打开的对话框/弹出窗口disposed。该解决方案不应依赖于从导航器中弹出整个小部件。

我从小部件树表示中发现,显示为弹出窗口(弹出菜单或对话框)的小部件直接位于小部件的不同分支MaterialApp

查看这些屏幕截图:

横向小部件中可见的弹出菜单:

横向小部件中的弹出菜单

横向小部件中的可见对话框:

横向小部件中的对话框

肖像小部件中可见的弹出菜单:

肖像小部件中的弹出菜单

纵向小部件中的可见对话框:

肖像小部件中的对话框

因此,基本上我正在寻找一种方法来查找并弹出所有这些类型的小部件,我们可以在处理父小部件之前弹出这些小部件。我想这应该适用于所有小部件屏幕,并且不应更改当前小部件上方的现有小部件树。

更新2

我得到的答案成功删除了对话框和弹出窗口,但它有一个副作用,即在删除所有对话框/弹出窗口的同时,它还会删除添加在显示弹出窗口的根小部件顶部的任何小部件。

例如:在此示例中,考虑我从小portrait部件访问的详细信息页面。因此,当portrait小部件重建时,详细信息页面将被处理,并且仅portrait再次显示,即使我没有打开任何对话框/弹出窗口。

San*_*iya 6

根据给出的代码,我更改了长按显示对话框代码及其子代码的手势检测器,如下所示:

// alert dialog code
...
builder: (context) => AlertDialog(
              content:
                  MediaQuery.of(context).orientation == Orientation.portrait
                      ? Text('Portrait')
                      : Text('Landscape'),
            ),
...

// gesture detector  child code

...
Center(
    child: MediaQuery.of(context).orientation == Orientation.portrait
                        ? Text('Portrait')
                        : Text('Landscape'),
),
...
Run Code Online (Sandbox Code Playgroud)

输出:
1

结论: MediaQuery.of(context).orientation 可以自行处理。

更新:
如果您在使用此代码改变方向时使用生命周期,则只会调用构建方法而不是处置方法。您可以在构建方法调用时删除所有弹出窗口。
查看下面的代码。
长按时,我打开了 3 个弹出窗口用于演示目的(可以有任意数量的弹出窗口或菜单)...当方向更改时, Navigator.of(context).popUntil((route) => route.isFirst);将首先调用并首先弹出所有弹出窗口和菜单。

// main.dart
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return OrientationBuilder(builder: (context, orientation) {
      bool isLandscape = orientation == Orientation.landscape;
      return isLandscape ? Landscape() : Portrait();
    });
  }
}

class Portrait extends StatefulWidget {
  @override
  _PortraitState createState() => _PortraitState();
}

class _PortraitState extends State<Portrait> {
  // this method will not be called when orientation changes
//  @override
//  void dispose() {
//    super.dispose();
//    Navigator.pop(context);
//    print("Portrait dispose");
//  }

  @override
  Widget build(BuildContext context) {
    // the below line will pop all the popups
    Navigator.of(context).popUntil((route) => route.isFirst);
    // code to check this method is called when orientation is changed
    print("Portrait build");
    return Scaffold(
      appBar: AppBar(
        title: buildTitle(),
        actions: <Widget>[_buildOptionMenu(context)],
      ),
      body: GestureDetector(
        onLongPress: () {
          showDialog(
            context: context,
            builder: (context) => AlertDialog(
              content: Center(
                child:
                    MediaQuery.of(context).orientation == Orientation.portrait
                        ? Text('Portrait')
                        : Text('Landscape'),
              ),
            ),
          );
          showDialog(
            context: context,
            builder: (context) => AlertDialog(
              content: Center(
                child:
                    MediaQuery.of(context).orientation == Orientation.portrait
                        ? Text('Portrait')
                        : Text('Landscape'),
              ),
            ),
          );
          showDialog(
            context: context,
            builder: (context) => AlertDialog(
              content: Center(
                child:
                    MediaQuery.of(context).orientation == Orientation.portrait
                        ? Text('Portrait')
                        : Text('Landscape'),
              ),
            ),
          );
        },
        child: Container(
          color: Colors.blue.withOpacity(0.4),
          child: Stack(
            fit: StackFit.expand,
            children: <Widget>[
              Center(
                child: Center(
                  child:
                      MediaQuery.of(context).orientation == Orientation.portrait
                          ? Text('Portrait')
                          : Text('Landscape'),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildOptionMenu(BuildContext context) {
    return PopupMenuButton(itemBuilder: (context) {
      var list = <String>['Portrait-Item-1', 'Portrait-Item-2'];
      return list
          .map<PopupMenuEntry<String>>(
            (e) => PopupMenuItem<String>(
              child: Text(e),
            ),
          )
          .toList();
    });
  }

  Text buildTitle() => Text('Portrait');
}

class Landscape extends StatefulWidget {
  @override
  _LandscapeState createState() => _LandscapeState();
}

class _LandscapeState extends State<Landscape> {
  // this method will not be called when orientation changes
//  @override
//  void dispose() {
//    super.dispose();
//    Navigator.pop(context);
//    print("Landscape dispose");
//  }

  @override
  Widget build(BuildContext context) {
    // the below line will pop all the popups
    Navigator.of(context).popUntil((route) => route.isFirst);
    // code to check this method is called when orientation is changed
    print("Landscape build");
    return Scaffold(
      appBar: AppBar(
        title: buildTitle(),
        actions: <Widget>[_buildOptionMenu(context)],
      ),
      body: GestureDetector(
        onLongPress: () {
          showDialog(
            context: context,
            builder: (context) => AlertDialog(
              content: Center(
                child:
                    MediaQuery.of(context).orientation == Orientation.portrait
                        ? Text('Portrait')
                        : Text('Landscape'),
              ),
            ),
          );
          showDialog(
            context: context,
            builder: (context) => AlertDialog(
              content: Center(
                child:
                    MediaQuery.of(context).orientation == Orientation.portrait
                        ? Text('Portrait')
                        : Text('Landscape'),
              ),
            ),
          );
          showDialog(
            context: context,
            builder: (context) => AlertDialog(
              content: Center(
                child:
                    MediaQuery.of(context).orientation == Orientation.portrait
                        ? Text('Portrait')
                        : Text('Landscape'),
              ),
            ),
          );
        },
        child: Container(
          color: Colors.orange.withOpacity(0.3),
          child: Stack(
            fit: StackFit.expand,
            children: <Widget>[
              Center(
                child: Center(
                  child:
                      MediaQuery.of(context).orientation == Orientation.portrait
                          ? Text('Portrait')
                          : Text('Landscape'),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Text buildTitle() => Text('Landscape');

  Widget _buildOptionMenu(BuildContext context) {
    return PopupMenuButton(itemBuilder: (context) {
      var list = <String>[
        'Landscape-Item-1',
        'Landscape-Item-2',
        'Landscape-Item-3',
      ];
      return list
          .map<PopupMenuEntry<String>>(
            (e) => PopupMenuItem<String>(
              child: Text(e),
            ),
          )
          .toList();
    });
  }
}
Run Code Online (Sandbox Code Playgroud)

输出:
在此输入图像描述