#flutter
#flutter
Вопрос:
У меня есть меню, которое отображается при нажатии кнопки, я хочу иметь возможность закрывать меню при нажатии вне меню, как обычное PopupMenuButton
.
Мое текущее решение — использовать a, у Stack
которых есть GestureDetector
в качестве одного из дочерних элементов, для обнаружения нажатий, но с этим решением мое выравнивание контролируется Stack
, я просто хочу, чтобы выравнивание контролировалось родительским элементом меню, а не Stack
.
//part of the build method
return Stack(
fit: StackFit.expand,
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
//Holds the list of menu items
menuContainer,
//button that displays the menu
floatingActionButton]
),
//Detects taps outside the menu
GestureDetector(onTap: (() => setVisibility(false))
)
]
);
Комментарии:
1. Невозможно обнаружить касания вне виджета. Вам нужно изменить свой макет, чтобы он больше не был снаружи
Ответ №1:
Вы можете добиться такого поведения, используя пользовательский маршрут:
class CustomDialog extends PopupRoute {
@override
Color get barrierColor => Colors.transparent;
@override
bool get barrierDismissible => true;
@override
String get barrierLabel => null;
@override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return _builder(context);
}
Widget _builder(BuildContext context) {
return Container();
}
@override
Duration get transitionDuration => Duration(milliseconds: 300);
}
barrierDismissible
Переопределение указывает, следует ли извлекать маршрут из стека при обнаружении касания вне виджета. Всякий раз, когда вы хотите отобразить меню, которое вы могли бы вызвать Navigator.of(context).push(CustomDialog())
.
Это решение не позволяет меню возвращать какое-либо значение, если вам нужны данные из него, вы можете переопределить ModalRoute вместо этого и добиться аналогичного результата.
Комментарии:
1. Это работает, возможно ли получить обратный вызов при уменьшении маршрута? чтобы сделать анимированный переход перед
2. Да, это возможно, если переопределить ModalRoute<T> вместо PopupRoute. Где T — тип значения, которое вы собираетесь вернуть. После этого у вас может быть что-то вроде Navigator.pop (контекст, результат) внутри класса CustomDialog, где результат — это то, что вы возвращаете вызывающему (он же, где был вызван Navigator.push).
Ответ №2:
Ответ Мариано Увалле, кстати, спасибо, больше не работает, потому что они помещают assert в _buildModalBarrier, проверяющий, установлен ли цвет барьера на прозрачный.
Моя работа заключалась в том, чтобы скопировать /packages/flutter/lib/src/widgets/routes.dart
файл в мою собственную /libs
папку и прокомментировать это утверждение в line: 1226
assert(barrierColor != _kTransparent);
также замените эти не- dart:
или package:
импортированные файлы вверху
import 'basic.dart';
import 'focus_manager.dart';
import 'focus_scope.dart';
import 'framework.dart';
import 'modal_barrier.dart';
import 'navigator.dart';
import 'overlay.dart';
import 'page_storage.dart';
import 'transitions.dart';
с помощью
import 'package:flutter/widgets.dart';
затем в моем приложении
import 'routes.dart' as my;
и используйте, используйте, class
он опубликовал Мариано Увалле. Только extend
это как my.PopupRoute
вместо.
Это не идеально, но это сделает свое дело.
Редактировать: я попробовал версию ModalRoute, описанную выше, и она также работает:
class CustomDialog extends my.ModalRoute<bool> {
@override
Color get barrierColor => Colors.transparent;
@override
bool get barrierDismissible => true;
@override
String get barrierLabel => null;
@override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return _builder(context);
}
Widget _builder(BuildContext context) {
return Container(alignment: Alignment.bottomLeft,
child: Container(height: kToolbarHeight,
child: Material(elevation: 8.0,
child:
Wrap(
children: <Widget>[
ListTile(
leading: new Icon(Icons.add_photo_alternate),
title: new Text('Add Image'),
onTap: () {
Navigator.pop(context, true);
},
)
],
),
),
),
);
}
@override
Duration get transitionDuration => Duration(milliseconds: 300);
@override
bool get maintainState => false;
@override
bool get opaque => false;
}
и я помещаю это в значение onpressed:
onPressed: () async {
bool notDismissed = await Navigator.of(context).push(CustomDialog());
if (notDismissed == true)
{
print('user clicke option');
}
else {
print('dismissed dialog');
}
Опять же, это не идеально, но в какой-то момент может пригодиться. Однако я обнаружил, что мне действительно нужно определять клики по определенным виджетам для моего варианта использования.
Комментарии:
1. Можете ли вы передать параметр в пользовательский каталог?
Ответ №3:
ответ бобаджеффа сработал для меня, но я обнаружил, что переопределение непрозрачного средства получения и установка значения barrierColor для null было немного чище.
class CustomDialog extends PageRoute {
@override
Color get barrierColor => null;
@override
bool get opaque => false;
@override
bool get maintainState => false;
@override
bool get barrierDismissible => true;
@override
String get barrierLabel => null;
@override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return _builder(context);
}
Widget _builder(BuildContext context) {
return Container();
}
@override
Duration get transitionDuration => Duration(milliseconds: 300);
}
Ответ №4:
Есть простой способ, который я только что сделал, просто поместите GestureDetector в другой, затем установите действие onTap, если это ваш случай для обоих GestureDetector, с той разницей, что в одном вы собираетесь ввести действие, а в другом вы можете просто оставить его пустым, вот так.
GestureDetector(
onTap: () { //The Gesture you dont want to afect the rest
Navigator.pop(context);
},
child: Container(
color: Colors.transparent,
child:GestureDetector(
onTap: () {}, //This way is not going to afect the inside widget
child: Container() //your widget
)
)
)