закрытие всех диалогов и всплывающих окон "только" перед удалением текущего виджета; другие виджеты не должны удаляться

Я работаю над проектом, который требует от меня изменения виджета на экране в зависимости от мобильного устройства. orientation.

Например, в случае portrait ориентации, мне нужно показать виджет (скажем, портретный виджет) и в случае landscape ориентации, мне нужно показать другой виджет (назовем его ландшафтным виджетом).

я использовал OrientationBuilder

Актуальная проблема: При смене ориентации все Dialogs а также OptionMenuили любой другой виджет Popup не закрывается. Как закрыть их при смене ориентации?

Шаги по воспроизведению проблемы:

  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();
    });
  }
}

Я не мог найти подходящего решения. Есть некоторые решения, которые требуют прослушивания изменений ориентации и виджетов push / pop на основе новой ориентации.

Но это слишком много для работы и требует добавления кода одного и того же типа как в портретный, так и в альбомный виджет. Что также не масштабируется, если мне нужно обрабатывать дополнительные ориентации, такие как обратный портрет и обратный пейзаж.

Обновить

Одно из возможных решений - вытолкнуть все виджеты до корневого виджета, а затем нажать новый виджет в зависимости от ориентации. Это работает, но имеет побочный эффект.

Например, если я нажимаю какой-нибудь новый виджет с портретной ориентации (скажем, страницу входа).

Затем, если я поверну свое устройство в альбомную ориентацию, он должен раздувать пользовательский интерфейс страницы входа в альбомном режиме, но в соответствии с кодом, который выталкивает все виджеты до корня.

Я увижу виджет "Пейзаж" вместо страницы входа в альбомный режим.

Для ясности:

Я ищу ответ, чтобы закрыть все открытые диалоги / всплывающие окна до того, как родительский виджет станет disposed. Решение не должно зависеть от вывода всего виджета из навигатора.

Из представления дерева виджетов я выяснил, что виджеты, которые отображаются как всплывающее (всплывающее меню или диалоговое окно), находятся в другой ветви виджета непосредственно изMaterialApp.

Посмотрите эти скриншоты:

Видимое всплывающее меню в виджете "Пейзаж":

Видимый диалог в виджете "Пейзаж":

Видимое всплывающее меню в виджете Портрет:

Видимый диалог в виджете "Портрет":

Итак, в основном я ищу способ найти и вставить все эти типы виджетов, которые мы можем выдвинуть, прежде чем избавляться от родительского виджета. Я предполагаю, что это должно быть применимо ко всему экрану виджетов и не должно изменять существующее дерево виджетов над текущим виджетом.

Обновление 2

Я получил ответ, который успешно удаляет диалоговые окна и всплывающие окна, но имеет побочный эффект, заключающийся в том, что при удалении всех диалогов / всплывающих окон он также удаляет все виджеты, которые добавляются поверх корневого виджета, который показывал всплывающие окна.

Например: в этом примере рассмотрим страницу сведений, которую я посещаю из portraitвиджет. Итак, когдаportrait виджет перестраивает страницу сведений удаляется и только portrait отображается снова, хотя у меня не открыты диалоговые / всплывающие окна.

2 ответа

В соответствии с приведенным кодом я изменил детектор жестов при длительном нажатии, отображая диалоговый код, и его дочерний код, как показано ниже:

// 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 справляется сам.

Обновление:
если вы переходите с жизненным циклом при изменении ориентации с помощью этого кода, будет вызываться только метод сборки, а не метод удаления. Вы можете удалить все всплывающие окна при вызове метода сборки.
Ознакомьтесь с кодом ниже.
Здесь при долгом нажатии я открыл 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();
    });
  }
}

Выход:

Это мое решение для всплывающих нижних листов и диалогов в методе 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()вместо is потому что с помощью pop()дайте нам setState() or markNeedsBuild() called when widget tree was locked.ошибка.

Другие вопросы по тегам