3
votes

Comment détecter un tap en dehors d'un widget?

J'ai un menu qui s'affiche lorsqu'un bouton est enfoncé, je veux pouvoir fermer le menu quand il y a un Tap en dehors du menu, tout comme un PopupMenuButton normal.

Ma solution actuelle est d'utiliser un Stack qui a un GestureDetector comme l'un de ses enfants pour détecter les taps mais avec cette solution mon alignement est contrôlé par le Stack code>, je veux juste que l'alignement soit contrôlé par le parent du menu et non par le 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 commentaires

Il n'est pas possible de détecter les taps en dehors d'un widget. Vous devez réfracter votre mise en page pour qu'elle ne soit plus à l'extérieur


3 Réponses :


3
votes

Vous pouvez obtenir ce comportement en utilisant une route personnalisée:

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

Le remplacement de barrièreDismissible indique si la route doit être sortie de la pile lorsqu'un contact est détecté en dehors du widget. Chaque fois que vous souhaitez afficher le menu, vous pouvez appeler Navigator.of(context).push(CustomDialog()).

Cette solution ne permet au menu de renvoyer aucune valeur, si vous avez besoin de récupérer des données, vous pouvez remplacer ModalRoute à la place et obtenir un résultat similaire.


2 commentaires

Cela fonctionne, est-il possible de recevoir un rappel lorsque la Route est rejetée? pour faire une transition animée avant


Oui, c'est possible si vous substituez ModalRoute au lieu de PopupRoute. Où T est le type de la valeur que vous allez renvoyer. Ensuite, vous pouvez avoir quelque chose comme Navigator.pop (contexte, résultat) dans la classe CustomDialog, où result est ce que vous renvoyez à l'appelant (c'est-à-dire où Navigator.push a été appelé).



1
votes

La réponse de Mariano Uvalle, merci d'ailleurs, ne fonctionne plus car ils ont mis une assert dans le _buildModalBarrier vérifiant si vous définissez la couleur de la barrière sur transparent.

Mon travail était de copier le fichier /packages/flutter/lib/src/widgets/routes.dart dans mon propre dossier / libs et de commenter cette affirmation à la ligne: 1226

onPressed: () async {
                          bool notDismissed = await Navigator.of(context).push(CustomDialog());
                          if (notDismissed == true)
                          {
                            print('user clicke option');
                          }
                          else {
                            print('dismissed dialog');
                          }

remplace également ces paquets non fléchettes: ou : importe en haut

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

avec

import 'package:flutter/widgets.dart';

puis dans mon application importer 'routes.dart' comme mon; et utilisez la classe de Mariano Uvalle qu'il a postée. Seulement extend c'est comme my.PopupRoute à la place.

Ce n'est pas parfait mais ça fera l'affaire.


Modifier: J'ai essayé la version ModalRoute de ce qui précède et cela fonctionne également:

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';

et je mets ceci dans une valeur onpressée:

      assert(barrierColor != _kTransparent);


0 commentaires

1
votes

La réponse de bobajeff a fonctionné pour moi, mais j'ai trouvé que l'utilisation de la substitution du getter opaque et de la définition du getter de barrièreColor sur null était un peu plus propre.

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


0 commentaires