Как определить касание вне виджета?

#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
            )
          )
        )