0

我一直在做一个项目,该项目需要我根据移动设备的orientation.

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

我用过OrientationBuilder

实际问题:在方向更改时,所有DialogsOptionMenu或任何其他 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();
    });
  }
}

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

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

更新

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

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

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

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

为了清楚起见

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

我从小部件树表示中发现的是,显示为弹出窗口(弹出菜单或对话框)的小部件与MaterialApp.

查看这些屏幕截图:

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

横向小部件中的弹出菜单

Landscape小部件中的可见对话框:

横向小部件中的对话框

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

肖像小部件中的弹出菜单

Portrait 小部件中的可见对话框:

肖像小部件中的对话框

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

更新 2

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

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

4

2 回答 2

5

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

// 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'),
),
...

输出:
1

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

更新:
如果您在使用此代码更改方向时使用生命周期,则只会调用 build 方法而不是 dispose 方法。您可以在构建方法调用时删除所有弹出窗口。
查看下面的代码。
在这里长按,我打开了 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();
    });
  }
}

输出:
在此处输入图像描述

于 2020-04-20T13:14:52.087 回答
0

这是我在 dispose 方法中弹出底部工作表和对话框的解决方案:

  NavigatorState _navigatorState;

  bool init = false;

  @override
  void didChangeDependencies() {
    if (!init) {
      final navigator = Navigator.of(context);
      setState(() {
        _navigatorState = navigator;
      });
      init = true;
    }
    super.didChangeDependencies();
  }

  @override
  void dispose() {
    if(_isOpen) {
      _navigatorState.maybePop();
    }
    super.dispose();
  }

我只是创建一个_isOpen在打开对话框/底页时更新的变量。这样我就知道对话框/底页何时打开,如果当前打开,请使用maybePop().

使用maybePop()而不是的原因pop()是因为使用pop()给了我们setState() or markNeedsBuild() called when widget tree was locked.错误。

于 2020-12-10T19:35:23.577 回答