закрытие всех диалогов и всплывающих окон "только" перед удалением текущего виджета; другие виджеты не должны удаляться
Я работаю над проектом, который требует от меня изменения виджета на экране в зависимости от мобильного устройства. orientation
.
Например, в случае portrait
ориентации, мне нужно показать виджет (скажем, портретный виджет) и в случае landscape
ориентации, мне нужно показать другой виджет (назовем его ландшафтным виджетом).
я использовал OrientationBuilder
Актуальная проблема: При смене ориентации все Dialogs
а также OptionMenu
или любой другой виджет Popup не закрывается. Как закрыть их при смене ориентации?
Шаги по воспроизведению проблемы:
- Запустите приведенный ниже код приложения [пример приложения для воспроизведения проблемы]
- Нажмите и удерживайте на теле приложения, чтобы увидеть диалоговое окно, или нажмите
OptionMenu
вверху слева - Измените ориентацию устройства, вы увидите, что тело виджета изменилось в соответствии с Ориентацией, но
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'),
),
...
Выход:
Вывод: 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.
ошибка.