Android: Différence entre onInterceptTouchEvent et dispatchTouchEvent?

249

Quelle est la différence entre onInterceptTouchEventet dispatchTouchEventdans Android?

Selon le guide du développeur Android, les deux méthodes peuvent être utilisées pour intercepter un événement tactile ( MotionEvent), mais quelle est la différence?

Comment onInterceptTouchEvent, dispatchTouchEventet onTouchEventinteragissent ensemble dans une hiérarchie de vues ( ViewGroup)?

Anne Droid
la source

Réponses:

272

Le meilleur endroit pour démystifier cela est le code source. Les documents sont terriblement insuffisants pour expliquer cela.

dispatchTouchEvent est en fait défini sur Activity, View et ViewGroup. Considérez-le comme un contrôleur qui décide comment acheminer les événements tactiles.

Par exemple, le cas le plus simple est celui de View.dispatchTouchEvent qui acheminera l'événement tactile vers OnTouchListener.onTouch s'il est défini ou vers la méthode d'extension onTouchEvent .

Pour ViewGroup.dispatchTouchEvent, les choses sont beaucoup plus compliquées. Il doit déterminer laquelle de ses vues enfant doit obtenir l'événement (en appelant child.dispatchTouchEvent). Il s'agit essentiellement d'un algorithme de test de réussite dans lequel vous déterminez quel rectangle de délimitation de la vue enfant contient les coordonnées du point de contact.

Mais avant de pouvoir envoyer l'événement à la vue enfant appropriée, le parent peut espionner et / ou intercepter l'événement tous ensemble. C'est pourquoi onInterceptTouchEvent est là. Donc, il appelle cette méthode avant de faire le test de hit et si l'événement a été détourné (en renvoyant true depuis onInterceptTouchEvent), il envoie un ACTION_CANCEL aux vues enfant afin qu'ils puissent abandonner leur traitement des événements tactiles (des événements tactiles précédents) et à partir de là. tous les événements tactiles au niveau parent sont envoyés à onTouchListener.onTouch (si défini) ou onTouchEvent (). Dans ce cas également, onInterceptTouchEvent n'est plus appelé.

Souhaitez-vous même remplacer [Activity | ViewGroup | View] .dispatchTouchEvent? À moins que vous ne fassiez un routage personnalisé, vous ne devriez probablement pas le faire.

Les principales méthodes d'extension sont ViewGroup.onInterceptTouchEvent si vous souhaitez espionner et / ou intercepter un événement tactile au niveau parent et View.onTouchListener / View.onTouchEvent pour la gestion de l'événement principal.

Dans l'ensemble, son design trop compliqué imo mais les apis android se tournent davantage vers la flexibilité que la simplicité.

numan salati
la source
10
C'est une excellente réponse concise. Pour un exemple plus détaillé, voir la formation "Gérer les événements tactiles dans un ViewGroup"
TalkLittle
1
@numan salati: "à partir de là, tous les événements tactiles au niveau parent sont envoyés à onTouchListener.onTouch" - je veux remplacer la méthode de répartition tactile dans mon groupe de vues et lui faire envoyer les événements à onTouchListener. Mais je ne vois pas comment cela peut être fait. Il n'y a pas d'api pour cela comme View.getOnTouchListener (). OnTouch (). Il existe une méthode setOnTouchListener () mais pas de méthode getOnTouchListener (). Comment peut-on le faire alors?
Ashwin
@Ashwin mes pensées exactement, il n'y a pas non plus setOnInterceptTouchEvent. Vous pouvez remplacer une vue de sous-classe pour une utilisation dans une disposition / l'ajouter dans du code, mais vous ne pouvez pas jouer avec les vues racine au niveau du fragment / de l'activité, car vous ne pouvez pas sous-classer ces vues sans sous-classer le fragment / l'activité lui-même (comme dans la plupart des cas de compatibilité Mises en œuvre ProgressActivitiy). L'API a besoin d'un setOnInterceptTouchEvent pour plus de simplicité. Tout le monde utilise interceptTouch sur rootViews à un moment donné dans une application semi-complexe
leRobot
244

Parce que c'est le premier résultat sur Google. Je veux partager avec vous un excellent Talk de Dave Smith sur Youtube: Maîtriser le système tactile Android et les diapositives sont disponibles ici . Cela m'a permis de bien comprendre le système Android Touch:

Comment les poignées d' activité se touchent:

  • Activity.dispatchTouchEvent()
    • Toujours le premier à être appelé
    • Envoie un événement à la vue racine attachée à la fenêtre
    • onTouchEvent()
      • Appelé si aucune vue ne consomme l'événement
      • Toujours le dernier à être appelé

Comment les poignées de vue se touchent:

  • View.dispatchTouchEvent()
    • Envoie l'événement à l'auditeur en premier, s'il existe
      • View.OnTouchListener.onTouch()
    • S'il n'est pas consommé, traite le toucher lui-même
      • View.onTouchEvent()

Comment un ViewGroup gère le toucher:

  • ViewGroup.dispatchTouchEvent()
    • onInterceptTouchEvent()
      • Vérifiez s'il doit remplacer les enfants
      • Laissez-passer ACTION_CANCEL pour enfant actif
      • S'il renvoie vrai une fois, le ViewGroupconsomme tous les événements suivants
    • Pour chaque vue enfant (dans l'ordre inverse, ils ont été ajoutés)
      • Si le toucher est pertinent (vue intérieure), child.dispatchTouchEvent()
      • S'il n'est pas géré par un précédent, envoyer à la vue suivante
    • Si aucun enfant ne gère l'événement, l'auditeur a une chance
      • OnTouchListener.onTouch()
    • S'il n'y a pas d'auditeur, ou s'il n'est pas géré
      • onTouchEvent()
  • Les événements interceptés sautent le pas de l'enfant

Il fournit également un exemple de code de contact personnalisé sur github.com/devunwired/ .

Réponse: Fondamentalement, le dispatchTouchEvent()est appelé sur chaque Viewcouche pour déterminer si un Viewest intéressé par un geste en cours. Dans un ViewGroupl' ViewGroupa la capacité de voler les événements tactiles dans son dispatchTouchEvent()-method, avant qu'il appellerait dispatchTouchEvent()les enfants. Le ViewGroupne stopperait la répartition que si la ViewGroup onInterceptTouchEvent()méthode-renvoie true. La différence est qu'il dispatchTouchEvent()s'agit de la répartition MotionEventset onInterceptTouchEventindique s'il doit intercepter (pas la distribution MotionEventaux enfants) ou non (la distribution aux enfants) .

Vous pourriez imaginer le code d'un ViewGroup faire plus ou moins cela (très simplifié):

public boolean dispatchTouchEvent(MotionEvent ev) {
    if(!onInterceptTouchEvent()){
        for(View child : children){
            if(child.dispatchTouchEvent(ev))
                return true;
        }
    }
    return super.dispatchTouchEvent(ev);
}
Seb
la source
64

Réponse supplémentaire

Voici quelques compléments visuels aux autres réponses. Ma réponse complète est ici .

entrez la description de l'image ici

entrez la description de l'image ici

La dispatchTouchEvent()méthode d'un ViewGrouputilise onInterceptTouchEvent()pour choisir s'il doit immédiatement gérer l'événement tactile (avec onTouchEvent()) ou continuer à notifier les dispatchTouchEvent()méthodes de ses enfants.

Suragch
la source
OnInterceptTouchEvent peut-il être appelé également dans l'activité ?? Je pense que c'est possible uniquement dans un ViewGroup ou je me trompe? @Suragch
Federico Rizzo
@ FedericoRizzo, vous avez raison! Merci beaucoup! J'ai mis à jour le schéma et ma réponse.
Suragch
20

Il y a beaucoup de confusion au sujet de ces méthodes, mais ce n'est en fait pas si compliqué. La confusion est principalement due au fait que:

  1. Si votre View/ViewGroupou l'un de ses enfants ne retourne pas vrai onTouchEvent, dispatchTouchEventet onInterceptTouchEventsera UNIQUEMENT appelé MotionEvent.ACTION_DOWN. Sans true from onTouchEvent, la vue parent supposera que votre vue n'a pas besoin de MotionEvents.
  2. Quand aucun des enfants d'un ViewGroup ne retourne true dans onTouchEvent, onInterceptTouchEvent sera UNIQUEMENT appelé MotionEvent.ACTION_DOWN, même si votre ViewGroup retourne true dans onTouchEvent.

L'ordre de traitement est comme ceci:

  1. dispatchTouchEvent est appelé.
  2. onInterceptTouchEventest appelé MotionEvent.ACTION_DOWNou lorsqu'un des enfants du ViewGroup a renvoyé true dans onTouchEvent.
  3. onTouchEventest d'abord appelé sur les enfants du ViewGroup et quand aucun des enfants ne retourne vrai, il est appelé sur le View/ViewGroup.

Si vous souhaitez prévisualiser TouchEvents/MotionEventssans désactiver les événements sur vos enfants, vous devez faire deux choses:

  1. Remplacez dispatchTouchEventpour prévisualiser l'événement et revenir super.dispatchTouchEvent(ev);
  2. Remplacez onTouchEventet retournez vrai, sinon vous n'en obtiendrez pas MotionEvent sauf MotionEvent.ACTION_DOWN.

Si vous souhaitez détecter un geste comme un événement de balayage, sans désactiver d'autres événements sur vos enfants tant que vous n'avez pas détecté le geste, vous pouvez le faire comme ceci:

  1. Prévisualisez les MotionEvents comme décrit ci-dessus et définissez un indicateur lorsque vous avez détecté votre geste.
  2. Renvoyez true onInterceptTouchEventlorsque votre indicateur est défini pour annuler le traitement MotionEvent par vos enfants. C'est également un endroit pratique pour réinitialiser votre indicateur, car onInterceptTouchEvent ne sera plus appelé avant le prochain MotionEvent.ACTION_DOWN.

Exemple de remplacements dans un FrameLayout(mon exemple en est C # car je programme avec Xamarin Android, mais la logique est la même en Java):

public override bool DispatchTouchEvent(MotionEvent e)
{
    // Preview the touch event to detect a swipe:
    switch (e.ActionMasked)
    {
        case MotionEventActions.Down:
            _processingSwipe = false;
            _touchStartPosition = e.RawX;
            break;
        case MotionEventActions.Move:
            if (!_processingSwipe)
            {
                float move = e.RawX - _touchStartPosition;
                if (move >= _swipeSize)
                {
                    _processingSwipe = true;
                    _cancelChildren = true;
                    ProcessSwipe();
                }
            }
            break;
    }
    return base.DispatchTouchEvent(e);
}

public override bool OnTouchEvent(MotionEvent e)
{
    // To make sure to receive touch events, tell parent we are handling them:
    return true;
}

public override bool OnInterceptTouchEvent(MotionEvent e)
{
    // Cancel all children when processing a swipe:
    if (_cancelChildren)
    {
        // Reset cancel flag here, as OnInterceptTouchEvent won't be called until the next MotionEventActions.Down:
        _cancelChildren = false;
        return true;
    }
    return false;
}
Marcel W
la source
3
Je ne sais pas pourquoi cela n'a pas plus de votes positifs. C'est une bonne réponse (OMI) et je l'ai trouvée très utile.
Mark Ormesher
8

Je suis tombé sur une explication très intuitive sur cette page Web http://doandroids.com/blogs/tag/codeexample/ . Tiré de là:

  • booléen onTouchEvent (MotionEvent ev) - appelé chaque fois qu'un événement tactile avec cette vue comme cible est détecté
  • boolean onInterceptTouchEvent (MotionEvent ev) - appelé chaque fois qu'un événement tactile est détecté avec ce ViewGroup ou un de ses enfants comme cible. Si cette fonction renvoie true, MotionEvent sera intercepté, ce qui signifie qu'il ne sera pas transmis à l'enfant, mais plutôt à onTouchEvent de cette vue.
Krzysztow
la source
2
la question concerne onInterceptTouchEvent et dispatchTouchEvent. Les deux sont appelés avant onTouchEvent. Mais dans cet exemple, vous ne pouvez pas voir dispatchTouchEvent.
Dayerman
8

dispatchTouchEvent gère avant onInterceptTouchEvent.

En utilisant cet exemple simple:

   main = new LinearLayout(this){
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            System.out.println("Event - onInterceptTouchEvent");
            return super.onInterceptTouchEvent(ev);
            //return false; //event get propagated
        }
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            System.out.println("Event - dispatchTouchEvent");
            return super.dispatchTouchEvent(ev);
            //return false; //event DONT get propagated
        }
    };

    main.setBackgroundColor(Color.GRAY);
    main.setLayoutParams(new LinearLayout.LayoutParams(320,480));    


    viewA = new EditText(this);
    viewA.setBackgroundColor(Color.YELLOW);
    viewA.setTextColor(Color.BLACK);
    viewA.setTextSize(16);
    viewA.setLayoutParams(new LinearLayout.LayoutParams(320,80));
    main.addView(viewA);

    setContentView(main);

Vous pouvez voir que le journal sera comme:

I/System.out(25900): Event - dispatchTouchEvent
I/System.out(25900): Event - onInterceptTouchEvent

Donc, si vous travaillez avec ces 2 gestionnaires, utilisez dispatchTouchEvent pour gérer en première instance l'événement, qui ira à onInterceptTouchEvent.

Une autre différence est que si dispatchTouchEvent retourne 'false', l'événement ne se propage pas à l'enfant, dans ce cas le EditText, alors que si vous retournez false dans onInterceptTouchEvent, l'événement est toujours distribué au EditText

Dayerman
la source
5

Réponse courte: dispatchTouchEvent() sera appelée en premier .

Petit conseil: ne doit pas passer outre dispatchTouchEvent()car il est difficile à contrôler, cela peut parfois ralentir vos performances. À mon humble avis, je suggère de passer outre onInterceptTouchEvent().


Parce que la plupart des réponses mentionnent assez clairement l'événement de flux tactile sur l'activité / voir le groupe / la vue, j'ajoute seulement plus de détails sur le code de ces méthodes dans ViewGroup(ignorer dispatchTouchEvent()):

onInterceptTouchEvent()sera appelé en premier, l'événement ACTION sera appelé respectivement en bas -> déplacer -> en haut. Il y a 2 cas:

  1. Si vous retournez false dans 3 cas (ACTION_DOWN, ACTION_MOVE, ACTION_UP), il considérera que le parent n'aura pas besoin de cet événement tactile , donc onTouch()des parents n'appelle jamais , mais onTouch()des enfants appellent à la place ; cependant veuillez noter:

    • Ils onInterceptTouchEvent()continuent de recevoir un événement tactile, tant que leurs enfants n'appellent pas requestDisallowInterceptTouchEvent(true).
    • S'il n'y a pas d'enfants recevant cet événement (cela peut arriver dans 2 cas: aucun enfant à la position que les utilisateurs touchent, ou il y a des enfants mais il renvoie faux à ACTION_DOWN), les parents renverront cet événement aux onTouch()parents.
  2. Inversement, si vous revenez vrai , le parent volera cet événement tactile immédiatement et onInterceptTouchEvent()s'arrêtera immédiatement, au lieu onTouch()des parents seront appelés ainsi que tous les onTouch()enfants recevront le dernier événement d'action - ACTION_CANCEL (ainsi, cela signifie que les parents a volé un événement tactile, et les enfants ne peuvent plus le gérer à partir de là). Le flux de onInterceptTouchEvent()retour false est normal, mais il y a un peu de confusion avec return true case, donc je l'énumère ici:

    • Renvoyez true à ACTION_DOWN, onTouch()des parents recevront à nouveau ACTION_DOWN et les actions suivantes (ACTION_MOVE, ACTION_UP).
    • Renvoie vrai à ACTION_MOVE, onTouch()des parents recevront ACTION_MOVE suivant (pas le même ACTION_MOVE dans onInterceptTouchEvent()) et les actions suivantes (ACTION_MOVE, ACTION_UP).
    • Retourne vrai à ACTION_UP, onTouch()des parents ne seront PAS appelés du tout car il est trop tard pour que les parents volent l'événement tactile.

Une autre chose importante est ACTION_DOWN de l'événement onTouch()qui déterminera si la vue souhaite recevoir plus d'actions de cet événement ou non. Si la vue retourne vrai à ACTION_DOWN dans onTouch(), cela signifie que la vue est prête à recevoir plus d'actions de cet événement. Sinon, retourner false à ACTION_DOWN in onTouch()impliquera que la vue ne recevra plus aucune action de cet événement.

Nguyen Tan Dat
la source
3

Le code suivant dans une sous-classe ViewGroup empêcherait ses conteneurs parents de recevoir des événements tactiles:

  @Override
  public boolean dispatchTouchEvent(MotionEvent ev) {
    // Normal event dispatch to this container's children, ignore the return value
    super.dispatchTouchEvent(ev);

    // Always consume the event so it is not dispatched further up the chain
    return true;
  }

J'ai utilisé cela avec une superposition personnalisée pour empêcher les vues d'arrière-plan de répondre aux événements tactiles.

James Wald
la source
1

La principale différence:

• Activity.dispatchTouchEvent (MotionEvent) - Cela permet à votre activité d'intercepter tous les événements tactiles avant qu'ils ne soient envoyés à la fenêtre.
• ViewGroup.onInterceptTouchEvent (MotionEvent) - Cela permet à un ViewGroup de regarder les événements lorsqu'ils sont envoyés aux vues enfants.

ChapMic
la source
1
Oui, je connais ces réponses du guide de développement Android - néanmoins peu claires. La méthode dispatchTouchEvent existe également pour un ViewGroup non seulement pour une Activity. Ma question était de savoir comment les trois méthodes dispatchTouchEvent, onInterceptTouchEvent et onTouchEvent interagissent ensemble au sein d'une hiérarchie de ViewGroups, par exemple RelativeLayouts.
Anne Droid
1

ViewGroup onInterceptTouchEvent()est toujours le point d'entrée pour l' ACTION_DOWNévénement qui est le premier événement à se produire.

Si vous souhaitez que ViewGroup traite ce geste, retournez true à partir de onInterceptTouchEvent(). De retour vrai, ViewGroup de onTouchEvent()recevra tous les événements qui ont suivi jusqu'à la prochaine ACTION_UPou ACTION_CANCEL, et dans la plupart des cas, les événements tactiles entre ACTION_DOWNet ACTION_UPou ACTION_CANCELsont ACTION_MOVE, qui sera normalement reconnu comme le défilement / gestes jeter.

Si vous retournez false de onInterceptTouchEvent(), la vue cible onTouchEvent()sera appelée. Il sera répété pour les messages suivants jusqu'à ce que vous reveniez vrai onInterceptTouchEvent().

Source: http://neevek.net/posts/2013/10/13/implementing-onInterceptTouchEvent-and-onTouchEvent-for-ViewGroup.html

vipsy
la source
0

Activity et View ont tous deux la méthode dispatchTouchEvent () et onTouchEvent. Le ViewGroup a également ces méthodes, mais a une autre méthode appelée onInterceptTouchEvent. Le type de retour de ces méthodes est booléen, vous pouvez contrôler l'itinéraire de répartition via la valeur de retour.

L'envoi de l'événement dans Android commence à partir de Activity-> ViewGroup-> View.

Ryan
la source
0
public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume =false;
    if(onInterceptTouchEvent(ev){
        consume = onTouchEvent(ev);
    }else{
        consume = child.dispatchTouchEvent(ev);
    }
}
Mc_fool_himself
la source
1
Pourriez-vous ajouter quelques explications?
Paul Floyd
3
Bien que cet extrait de code puisse résoudre la question, y compris une explication aide vraiment à améliorer la qualité de votre message. N'oubliez pas que vous répondrez à la question pour les lecteurs à l'avenir, et ces personnes pourraient ne pas connaître les raisons de votre suggestion de code.
Rosário Pereira Fernandes
-2

Petite réponse:

onInterceptTouchEvent précède setOnTouchListener.

sagits
la source