web-dev-qa-db-fra.com

Android: Différence entre onInterceptTouchEvent et dispatchTouchEvent?

Quelle est la différence entre onInterceptTouchEvent et dispatchTouchEvent dans 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, dispatchTouchEvent et onTouchEvent interagissent ensemble dans une hiérarchie de vues (ViewGroup)?

223
Anne Droid

Le meilleur endroit pour démystifier ceci est le code source. Les docs sont terriblement inadéquats pour expliquer cela.

dispatchTouchEvent est actuellement défini sur Activity, View et ViewGroup. Pensez-y 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 avec la méthode d'extension onTouchEvent .

Pour ViewGroup.dispatchTouchEvent les choses sont bien plus compliquées. Il doit déterminer laquelle de ses vues enfants doit recevoir l'événement (en appelant child.dispatchTouchEvent). Il s'agit en gros d'un algorithme de test de hit qui permet de déterminer le rectangle englobant de la vue enfant contenant 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 ce pour quoi onInterceptTouchEvent est là pour ça. Donc, cette méthode appelle d'abord cette méthode avant de tester le hit et si l'événement a été détourné (en retournant true à partir de onInterceptTouchEvent), il envoie un ACTION_CANCEL aux vues enfant afin qu'elles puissent abandonner le traitement de leur événement tactile et à partir de ce moment, tous les événements tactiles du niveau parent sont envoyés vers onTouchListener.onTouch (si défini) ou onTouchEvent (). Dans ce cas également, onInterceptTouchEvent n'est jamais appelé à nouveau.

Souhaitez-vous même remplacer le fichier [Activity | ViewGroup | View] .dispatchTouchEvent? Sauf si vous effectuez un routage personnalisé, vous ne devriez probablement pas. 

Les méthodes d'extension principales 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 des événements principaux.

Dans l’ensemble, son design trop compliqué, mais l’application Android s’appuie davantage sur la flexibilité que sur la simplicité.

255
numan salati

Réponse supplémentaire

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

 enter image description here

 enter image description here

La méthode dispatchTouchEvent() d'une ViewGroup utilise onInterceptTouchEvent() pour choisir si elle doit immédiatement gérer l'événement tactile (avec onTouchEvent()) ou continuer à notifier les méthodes dispatchTouchEvent() de ses enfants.

42
Suragch

Il y a beaucoup de confusion à propos de ces méthodes, mais ce n'est en réalité pas si compliqué. La plupart de la confusion est parce que:

  1. Si votre View/ViewGroup ou l'un de ses enfants ne retourne pas la valeur true dans onTouchEvent, dispatchTouchEvent et onInterceptTouchEvent sera SEULEMENT.__déclaré MotionEvent.ACTION_DOWN. Sans une variable true de onTouchEvent, la vue parent supposera que votre vue n'a pas besoin de Les MotionEvents.
  2. Lorsqu'aucun des enfants d'un ViewGroup ne retourne true dans onTouchEvent, onInterceptTouchEvent sera appelé UNIQUEMENT pour MotionEvent.ACTION_DOWN, même si votre ViewGroup renvoie true dans onTouchEvent.

L'ordre de traitement est comme ceci:

  1. dispatchTouchEvent est appelé.
  2. onInterceptTouchEvent est appelé pour MotionEvent.ACTION_DOWN ou lorsque L'un des enfants du groupe de vues retourne la valeur true dans onTouchEvent.
  3. onTouchEvent est d'abord appelé sur les enfants du groupe de vues et Quand aucun des enfants ne retourne vrai, il est appelé sur le View/ViewGroup.

Si vous souhaitez prévisualiser TouchEvents/MotionEvents sans désactiver les événements sur vos enfants, vous devez procéder de deux manières:

  1. Remplacez dispatchTouchEvent pour prévisualiser l'événement et renvoyer super.dispatchTouchEvent(ev);
  2. Remplacez onTouchEvent et renvoyez true, sinon vous n’obtiendrez aucune 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 suit:

  1. Prévisualisez les événements MotionEvent comme décrit ci-dessus et définissez un indicateur lorsque vous détectez votre geste.
  2. Renvoie true dans onInterceptTouchEvent lorsque votre indicateur est défini sur annuler le traitement de MotionEvent par vos enfants. C’est aussi un endroit pratique pour réinitialiser votre drapeau, car onInterceptTouchEvent ne sera plus appelé De nouveau jusqu’au prochain MotionEvent.ACTION_DOWN.

Exemple de substitution dans un FrameLayout (mon exemple est en C # car je programme avec Xamarin Android, mais la logique est identique 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;
}
16
Marcel W

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

  • boolean onTouchEvent (MotionEvent ev) - appelé chaque fois qu'un événement tactile ayant 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 la valeur true, MotionEvent sera intercepté, ce qui signifie qu'il ne sera pas transmis à l'enfant, mais plutôt à onTouchEvent de cette vue.
8
Krzysztow

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 renvoie 'false', l'événement ne sera pas propagé à l'enfant, dans ce cas, l'EditText, tandis que si vous renvoyez false dans onInterceptTouchEvent, l'événement sera toujours envoyé à l'EditText.

8
Dayerman

Vous pouvez trouver la réponse dans cette vidéo https://www.youtube.com/watch?v=SYoN-OvdZ3M&list=PLonJJ3BVjZW6CtAMbJz1XD8ELUs1KXaTD&index=19 et les 3 prochaines vidéos. Tous les événements tactiles sont très bien expliqués, c’est très clair et plein d’exemples.

4
BlueMango

Réponse courte:dispatchTouchEvent() sera appelé first of all.

Petit conseil: ne doit pas remplacer dispatchTouchEvent() car difficile à contrôler, cela peut parfois ralentir vos performances. IMHO, je suggère de remplacer onInterceptTouchEvent().

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

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

  1. Si vous renvoyez false dans 3 cas (ACTION_DOWN, ACTION_MOVE, ACTION_UP), il sera considéré que le parent n'aura pas besoin de cet événement tactile, donc onTouch() des parents n'appelle jamais, mais onTouch() des enfants appellera à la place; cependant s'il vous plaît noter: 

    • onInterceptTouchEvent() continue de recevoir l'événement tactile tant que ses enfants n'appellent pas requestDisallowInterceptTouchEvent(true)
    • Si aucun enfant ne reçoit cet événement (cela peut se produire dans 2 cas: aucun enfant à la position touchée par l'utilisateur, ou s'il y a des enfants mais renvoie False à ACTION_DOWN), les parents renverront cet événement à onTouch().
  2. Inversement, si vous renvoyez true, le parent volera immédiatement cet événement tactile et onInterceptTouchEvent() s'arrêtera immédiatement. Au lieu de cela, onTouch() des parents sera appelé ainsi que tous les onTouch() des enfants recevront le dernier événement d'action - ACTION_CANCEL (ainsi, cela signifie que les parents ont volé un événement tactile et que les enfants ne pourront plus le gérer). Le flux de onInterceptTouchEvent() return false est normal, mais il y a une petite confusion avec return true, alors je le répertorie ici:

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

Une dernière chose que important est ACTION_DOWN de l’événement dans onTouch() déterminera si la vue souhaite recevoir plus d’actions de cet événement ou non. Si la vue retourne true à ACTION_DOWN dans onTouch(), cela signifie que la vue est disposée à recevoir plus d'actions de cet événement. Sinon, renvoyer false à ACTION_DOWN dans onTouch() signifiera que la vue ne recevra plus aucune action de cet événement.

3
Nguyen Tan Dat

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 en arrière-plan de répondre aux événements tactiles.

3
James Wald

La différence principale: 

• Activity.dispatchTouchEvent (MotionEvent) - Cela permet à votre activité pour intercepter tous les événements tactiles avant leur envoi au la fenêtre. 
• ViewGroup.onInterceptTouchEvent (MotionEvent) - Cela permet un ViewGroup pour regarder les événements tels qu'ils sont envoyés aux vues enfants.

1
ChapMic

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

Si vous souhaitez que ViewGroup traite ce geste, retournez true à partir de onInterceptTouchEvent(). En renvoyant true, le groupe onTouchEvent() de ViewGroup recevra tous les événements suivants jusqu'au prochain ACTION_UP ou ACTION_CANCEL et, dans la plupart des cas, les événements tactiles entre ACTION_DOWN et ACTION_UP ou ACTION_CANCEL sont ACTION_MOVE. , qui seront normalement reconnus comme des gestes de défilement/défilement.

Si vous renvoyez false à partir de onInterceptTouchEvent(), le onTouchEvent() de la vue cible sera appelé. Il sera répété pour les messages suivants jusqu'à ce que vous renvoyiez la valeur true à partir de onInterceptTouchEvent().

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

1
vipsy
public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume =false;
    if(onInterceptTouchEvent(ev){
        consume = onTouchEvent(ev);
    }else{
        consume = child.dispatchTouchEvent(ev);
    }
}
0
Mc_fool_himself

À mon avis, dispatchTouchEvent() est une partie très importante de la manipulation des contacts sur un périphérique. Activity, ViewGroup, View ont une réalisation de cette méthode. Si nous prenons en compte la méthode de ViewGroup, nous verrons que dispatchTouchEvent() appelle onInterceptTouchEvent()

enter image description here

La réponse complète SO est ici

0
yoAlex5

Activity et View utilisent les méthodes dispatchTouchEvent () et onTouchEvent.Le ViewGroup utilise également ces méthodes, mais 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 distribution via la valeur de retour.

La répartition de l'événement dans Android commence par Activité-> ViewGroup-> View.

0
Ryan