Mécanisme de diffusion d'événements Android : analyse du code source de dispatchTouchEvent avec des exemples spécifiques

**Remarque : étant donné qu'il analyse le code source, cet article nécessite une certaine compréhension du mécanisme de diffusion d'événements d'Android. **Bien sûr, je résumerai également brièvement les connaissances en matière d'organisation d'événements :

1. Résumé simple

  1. La même séquence d'événements : fait référence à une série d'événements générés dans le processus à partir du moment où le doigt touche l'écran jusqu'au moment où le doigt quitte l'écran. Cet événement commence par l'événement down et contient un nombre indéfini de mouvements au milieu. ., et se termine finalement par l'événement up.
  2. La distribution des événements de clic est la distribution de la même séquence d'événements. Ce processus est complété par trois méthodes très importantes : dispatchTouchEvent, onInterceptTouchEvent, onTouchEvent.
  3. Lorsqu'un événement de clic est généré, le processus de livraison suit la séquence suivante : Activité-VueGroupe-Vue. Une fois que la vue de niveau supérieur reçoit l'événement, il sera distribué selon le mécanisme de distribution d'événements.
  4. dispatchTouchEvent() : indique si cette couche ou la couche inférieure a la capacité de gérer des événements. View et ViewGroup ont tous deux cette méthode. , notez que cette méthode indique si elle est capable . La distribution des événements est affectée par la méthode onInterceptTouchEvent de cette couche, la valeur de retour onTouchEvent et le résultat dispatchTouchEvent de la vue subordonnée.
    Cette méthode renvoie true, indiquant que l'événement a peut-être été traité par cette couche ou une couche inférieure et que l'événement n'est plus distribué. Il existe les situations suivantes : 1.) onInterceptTouchEvent a intercepté l'événement (renvoie
    true
    ) et onTouchEvent de cette couche a traité l'événement (renvoyé vrai) );
    2.) La valeur de retour de dispatchTouchEvent de l'élément inférieur est vraie et l'événement est géré par la couche inférieure.
    Cette méthode renvoie false, indiquant que l'événement n'est pas géré par cette couche ou la couche inférieure. L'événement est renvoyé à la couche parent
    dans les situations suivantes :
    1.) onInterceptTouchEvent intercepte l'événement, mais le onTouchEvent de cette couche ne traite pas l'événement (renvoie false), ce qui signifie que cette couche n'a pas la capacité de gérer l'événement. événement, et l'événement est lancé vers l'adresse du conteneur parent.
    2.) onInterceptTouchEvent n'a pas intercepté l'événement (renvoyé false), mais le dispatchTouchEvent de la couche inférieure a renvoyé false en raison du onTouchEvent de la couche, indiquant que la couche inférieure n'était pas capable de gérer l'événement.

Remarque : Si la méthode dispatchTouchEvent() d'un composant renvoie true, alors les événements suivants dans la séquence d'événements seront traités par le composant, à moins que nous substituions dispatchTouchEvent() pour renvoyer false ou intercepter l'événement via onInterceptTouchEvent().

  1. onInterceptTouchEvent() : indique si ViewGroup intercepte l'événement (la valeur par défaut est false, ce qui signifie qu'aucune interception n'est effectuée et est transmise directement au dispatchTouchEvent de la sous-vue). Seul ViewGroup dispose de cette méthode.
    Renvoie true, intercepte l'événement et transmet l'événement intercepté au onTouchEvent de ce contrôle de couche pour traitement ;
    renvoie false, n'intercepte pas l'événement, l'événement est distribué à la sous-vue et distribué par le dispatchTouchEvent du sous- Voir.
    Par défaut,super.onInterceptTouchEvent(ev)est renvoyé. L'événement ne sera pas intercepté par défaut et sera géré par dispatchTouchEvent de la vue enfant.

Si la vue actuelle intercepte l'événement, les événements suivants dans la même séquence d'événements seront directement transmis à la vue pour traitement et la méthode onInterceptTouchEvent ne sera plus appelée.

2. Analyse du code source de la méthode dispatchTouchEvent

Il existe une variable très importante mFirstTouchTarget qui nécessite une attention supplémentaire pendant le processus d'analyse.
mFirstTouchTarget représente un sous-composant dans une série d'événements qui peut gérer les événements tactiles. Si mFirstTouchTarget n'est pas nul, cela signifie que nous avons trouvé le sous-composant.

Le code source de la méthode dispatchTouchEvent est le suivant : Le code source est très long. Je l'ai divisé en quatre étapes et j'ai ajouté quelques commentaires pour aider à comprendre. Si vous ne souhaitez pas lire le code source, vous pouvez ignorer le code source et regardez directement les quatre étapes suivantes :

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    
    
    if (mInputEventConsistencyVerifier != null) {
    
    
        mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
    }


    if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
    
    
        ev.setTargetAccessibilityFocus(false);
    }

    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
    
    
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;
//第一步:
//ACTION_DOWN是事件序列的开端,所以触摸事件ACTION_DOWN时进行初始化操作。
//cancelAndClearTouchTargets和resetTouchState方法都用于清空上一次触摸事件的缓存数据。会把 mFirstTouchTarget 清空为null
        if (actionMasked == MotionEvent.ACTION_DOWN) {
    
    

            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

//第二步:检查触摸事件是否需要拦截
        final boolean intercepted; //定义一个变量intercepted来标志是否需要拦截触摸事件。
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
    
    
//事件为ACTION_DOWN或者mFirstTouchTarget不为null,
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//判断disallowIntercept禁止拦截   
//因为在其他地方可能调用了requestDisallowInterceptTouchEvent(boolean disallowIntercept)
//从而禁止执行是否需要拦截的判断。字面意思,禁止调用owInterceptTouchEvent()方法进行触摸事件的拦截。
            if (!disallowIntercept) {
    
    
//如果可以调用owInterceptTouchEvent(),则调用onInterceptTouchEvent()方法判断是否拦截这个触摸事件
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); 
            } else {
    
    
//不允许调用owInterceptTouchEvent()方法,不拦截事件
                intercepted = false;
            }
        } else {
    
    
//当不是ACTION_DOWN事件并且mFirstTouchTarget为null(即没有可以处理触摸事件的子组件)时    
//设置 intercepted = true表示ViewGroup拦截触摸事件。
            intercepted = true;
        }

        if (intercepted || mFirstTouchTarget != null) {
    
    
            ev.setTargetAccessibilityFocus(false);
        }

        // 判断是不是ACTION_CANCEL事件
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

// 第三步:进行事件分发:
        final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
                && !isMouseEvent;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        if (!canceled && !intercepted) {
    
    
  //不是ACTION_CANCEL事件 并且ViewGroup不拦截,那我们可以去找一个子组件处理触摸事件。
            View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                    ? findChildWithAccessibilityFocus() : null;

            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
    
    
//如果是ACTION_DOWN事件
//下面代码概述:
// 开启了一个循环,循环所有的子View判断哪个子View可以处理触摸事件。
子view是否可以处理触摸事件肯定也是调用子view 的dispatchTouchEvent()方法判断,该循环中的dispatchTransformedTouchEvent()里面就是调用的子view 的dispatchTouchEvent()方法,如果返回true,我们就找到了可以触摸事件的子view ,并将该子view 赋值给mFirstTouchTarget。
                final int actionIndex = ev.getActionIndex(); 
                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                        : TouchTarget.ALL_POINTER_IDS;

           
                removePointersFromTouchTargets(idBitsToAssign);

                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
    
    
                    final float x =
                            isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
                    final float y =
                            isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
        
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    for (int i = childrenCount - 1; i >= 0; i—) {
    
    
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);


                        if (childWithAccessibilityFocus != null) {
    
    
                            if (childWithAccessibilityFocus != child) {
    
    
                                continue;
                            }
                            childWithAccessibilityFocus = null;
                            i = childrenCount - 1;
                        }

                        if (!child.canReceivePointerEvents()
                                || !isTransformedTouchPointInView(x, y, child, null)) {
    
    
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }

                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
    
    
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }
                        resetCancelNextUpFlag(child);
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
    
    

                            mLastTouchDownTime = ev.getDownTime();
                            if (preorderedList != null) {
    
    
   
                                for (int j = 0; j < childrenCount; j++) {
    
    
                                    if (children[childIndex] == mChildren[j]) {
    
    
                                        mLastTouchDownIndex = j;
                                        break;
                                    }
                                }
                            } else {
    
    
                                mLastTouchDownIndex = childIndex;
                            }
                            mLastTouchDownX = ev.getX();
                            mLastTouchDownY = ev.getY();
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }


                        ev.setTargetAccessibilityFocus(false);
                    }
                    if (preorderedList != null) preorderedList.clear();
                }

                if (newTouchTarget == null && mFirstTouchTarget != null) {
    
    

                    newTouchTarget = mFirstTouchTarget;
                    while (newTouchTarget.next != null) {
    
    
                        newTouchTarget = newTouchTarget.next;
                    }
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            }
        }

//第四步,后续事件处理,我们通过第三步可能找到一个可以处理子事件的子view
        if (mFirstTouchTarget == null) {
    
    
如果没有找到,则调用dispatchTransformedTouchEvent()方法,不过这里传的参数是null,该方法内部如果是null,就会判断ViewGroup自身是否可以处理触摸事件。
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
    
    
// 如果找到了,则调用 dispatchTransformedTouchEvent()方法,
//并传值是mFirstTouchTarget,dispatchTransformedTouchEvent内部会调用子View的dispatchTouchEvent()方法,完成事件的分发,
//因为我们在第三步为了找到一个可以出发触摸事件的子View,可能已经调用过dispatchTransformedTouchEvent()方法分发过一次了,所以在第三步和第四步都加了一个boolean值 alreadyDispatchedToNewTouchTarget,避免进行重复分发。
            TouchTarget predecessor = null;
            TouchTarget target = mFirstTouchTarget;
            while (target != null) {
    
    
                final TouchTarget next = target.next;
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
    
    
                    handled = true;
                } else {
    
    
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
    
    
                        handled = true;
                    }
                    if (cancelChild) {
    
    
                        if (predecessor == null) {
    
    
                            mFirstTouchTarget = next;
                        } else {
    
    
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
                }
                predecessor = target;
                target = next;
            }
        }

        // Update list of touch targets for pointer up or cancel, if needed.
        if (canceled
                || actionMasked == MotionEvent.ACTION_UP
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
    
    
            resetTouchState();
        } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
    
    
            final int actionIndex = ev.getActionIndex();
            final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
            removePointersFromTouchTargets(idBitsToRemove);
        }
    }

    if (!handled && mInputEventConsistencyVerifier != null) {
    
    
        mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
    }
    return handled;
}




//另一个重要的方法:
//该方法很简单,如果传入的child不是null,调用child的dispatchTouchEvent()方法,如果传入的child是null,则调用ViewGroup基类也就是View类的dispatchTouchEvent()方法,判断当前ViewGroup自身可以不可以处理触摸事件。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    
    
    final boolean handled;

    final int oldAction = event.getAction();
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
    
    
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {
    
    
            handled = super.dispatchTouchEvent(event);
        } else {
    
    
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }

    // Calculate the number of pointers to deliver.
    final int oldPointerIdBits = event.getPointerIdBits();
    final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

    if (newPointerIdBits == 0) {
    
    
        return false;
    }

    final MotionEvent transformedEvent;
    if (newPointerIdBits == oldPointerIdBits) {
    
    
        if (child == null || child.hasIdentityMatrix()) {
    
    
            if (child == null) {
    
    
                handled = super.dispatchTouchEvent(event);
            } else {
    
    
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                event.offsetLocation(offsetX, offsetY);

                handled = child.dispatchTouchEvent(event);

                event.offsetLocation(-offsetX, -offsetY);
            }
            return handled;
        }
        transformedEvent = MotionEvent.obtain(event);
    } else {
    
    
        transformedEvent = event.split(newPointerIdBits);
    }

    // Perform any necessary transformations and dispatch.
    if (child == null) {
    
    
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
    
    
        final float offsetX = mScrollX - child.mLeft;
        final float offsetY = mScrollY - child.mTop;
        transformedEvent.offsetLocation(offsetX, offsetY);
        if (! child.hasIdentityMatrix()) {
    
    
            transformedEvent.transform(child.getInverseMatrix());
        }

        handled = child.dispatchTouchEvent(transformedEvent);
    }

    // Done.
    transformedEvent.recycle();
    return handled;
}

Trions le processus de code ci-dessus

étapes du code source dispatchTouchEvent :

La première étape : l'événement ACTION_DOWN arrive, indiquant qu'une nouvelle série tactile a commencé. ViewGroup efface le cache de la dernière série d'événements tactiles, principalement la vue qui a géré l'événement tactile dans le dernier événement tactile : mFirstTouchTarget.

Étape 2 : S'il s'agit d'un événement ACTION_DOWN ou si mFirstTouchTarget n'est pas vide (c'est-à-dire que la vue qui gère l'événement tactile a été trouvée à l'étape 3 ci-dessous), vous devez appeler la méthode onInterceptTouchEvent() pour déterminer s'il faut intercepter le toucher. événement.

Étape 3 : S'il n'est pas intercepté et qu'il s'agit d'un événement ACTION_DOWN (le début d'un événement tactile), nous devons trouver une sous-vue capable de gérer l'événement. La méthode doit consister à parcourir toutes les sous-vues et à appeler la méthode dispatchTouchEvent() sur eux. À ce stade, l’événement tactile a été distribué à la sous-vue.
Cette étape ne sera appelée qu'une seule fois lors de l'événement ACTION_DOWN, c'est-à-dire qu'une série d'événements tactiles ne trouvera qu'une sous-vue pouvant être traitée.
C'est pourquoi la méthode dispatchTouchEvent() renvoie true, puis les événements suivants dans la séquence d'événements seront transmis à un composant pour traitement, car la méthode dispatchTouchEvent() recherchera uniquement une sous-vue capable de gérer les événements tactiles pendant l'action ACTION_DOWN. événement, et les autres événements ne le rechercheront pas.

Étape 4 : Il s'agit d'un jugement if pour déterminer si mFirstTouchTarget (c'est-à-dire qu'après la troisième étape, nous avons trouvé une sous-vue capable de gérer les événements tactiles) est nul, puis les événements tactiles suivants ACTION_MOVE et ACTION_UP appelleront à nouveau cette sous-vue. .méthode dispatchTouchEvent(), qui termine la distribution des événements tactiles suivants aux sous-vues.
Si nous passons par la troisième étape et ne trouvons pas de sous-View capable de gérer les événements tactiles, nous devons déterminer si le ViewGroup lui-même peut gérer l'événement tactile. Parce que le ViewGroup hérite de View, la méthode dispatchTouchEvent() de la base View La classe est appelée pour déterminer si le ViewGroup lui-même peut gérer l'événement tactile. Peut gérer les événements tactiles.

La méthode principale des troisième et quatrième étapes est la suivante : la méthode dispatchTransformedTouchEvent().
Le code de base de cette méthode est :

if (child == null) {
    
    
    handled = super.dispatchTouchEvent(event);
    //mFirstTouchTarget是null,我们没有找到可以处理触摸事件的子View,交给ViewGroup处理,判断ViewGroup可以不可以处理触摸事件
} else {
    
    
    handled = child.dispatchTouchEvent(event);
    //mFirstTouchTarget不是null,我们找到了一个可以处理触摸事件的子View,那就分发事件给他
}

Ce dont nous avons parlé ci-dessus, ce sont toutes les situations où il n'y a pas d'interception. Que se passe-t-il en cas d'interception ? L'interception se divise en deux situations :

  1. ) La première consiste à intercepter directement à partir de l'événement ACTION_DOWN. Grâce à la troisième étape, nous savons que lorsque ACTION_DOWN arrive, ce n'est que si (!canceled && !intercepted) est satisfait que nous trouverons un sous-ViewmFirstTouchTarget capable de gérer l'événement tactile. Si nous commençons par Si ACTION_DOWN est intercepté, alors cette sous-vue ne sera pas trouvée, alors mFirstTouchTarget est nul,
    donc la quatrième étape de if le jugement sera exécuté
    if (mFirstTouchTarget == null) { handled = dispatchTransformedTouchEvent(ev, annulé, null, TouchTarget .ALL_POINTER_IDS ); } Grâce au code principal de dispatchTransformedTouchEvent, nous savons que lorsqu'une valeur null est transmise, dispatchTouchEvent de la classe de base View du ViewGroup actuel sera appelé pour gérer ses propres événements tactiles.



  2. ) Le deuxième type n'est pas d'intercepter l'événement ACTION_DOWN, mais de l'intercepter dans ACTION_MOVE. À ce stade, mFirstTouchTarget ne peut pas être nul, donc la branche else du premier cas est entrée :

TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
    
    
    final TouchTarget next = target.next;
    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
    
    
        handled = true;
    } else {
    
    
        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                || intercepted;
        if (dispatchTransformedTouchEvent(ev, cancelChild,
                target.child, target.pointerIdBits)) {
    
    
            handled = true;
        }
        if (cancelChild) {
    
    
            if (predecessor == null) {
    
    
                mFirstTouchTarget = next;
            } else {
    
    
                predecessor.next = next;
            }
            target.recycle();
            target = next;
            continue;
        }
    }
    predecessor = target;
    target = next;
}

Le code de base du code ci-dessus est le suivant :

        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                || intercepted;
        if (dispatchTransformedTouchEvent(ev, cancelChild,
                target.child, target.pointerIdBits)) {
    
    
            handled = true;
        }

Vous pouvez voir que l'événement est toujours distribué via la méthode dispatchTransformedTouchEvent().
Le code derrière le code ci-dessus est que si nous interceptons l'événement tactile, mFirstTouchTarget sera défini sur null. Il s'agit du code suivant : (C'est pourquoi si la vue actuelle intercepte l'événement, les événements suivants dans la même séquence d'événements seront directement transmis à la vue pour traitement, car mFirstTouchTarget a été reproduit comme null)

if (cancelChild) {
    
    
    if (predecessor == null) {
    
    
        mFirstTouchTarget = next;
    } else {
    
    
        predecessor.next = next;
    }
    target.recycle();
    target = next;
    continue;
}

Oui, si vous souhaitez intercepter des événements tactiles, vous transmettrez un false à dispatchTransformedTouchEvent. Dans la méthode dispatchTransformedTouchEvent, si vous transmettez false, un événement ACTION_CANCEL sera distribué à la place de l'événement d'origine.
Le code de traitement de la méthode dispatchTransformedTouchEvent() est le suivant :

final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
    
    
    event.setAction(MotionEvent.ACTION_CANCEL);
    if (child == null) {
    
    
        handled = super.dispatchTouchEvent(event);
    } else {
    
    
        handled = child.dispatchTouchEvent(event);
    }
    event.setAction(oldAction);
    return handled;
}

Exemple de démonstration :

class MyLinearLayout(
    context : Context , @Nullable attrs : AttributeSet? ,
    defStyleAttr : Int ,
) : LinearLayout(context , attrs , defStyleAttr) {
    
    
    constructor(context : Context) : this(context , null , -1)
    constructor(context : Context , @Nullable attrs : AttributeSet?) : this(context , attrs , -1)

    init {
    
    
        orientation = VERTICAL
        setBackgroundColor(Color.parseColor("#00FF00"))
    }

    override fun dispatchTouchEvent(ev : MotionEvent?) : Boolean {
    
    

        if (ev!!.action == MotionEvent.ACTION_DOWN) {
    
    
            Log.e("父组件分发了DOWN事件" , "--------")
//            return true
        } else if (ev.action == MotionEvent.ACTION_MOVE) {
    
    
            Log.e("父组件分发了MOVE事件" , "--------")
//           return false
        } else if (ev.action == MotionEvent.ACTION_UP) {
    
    
            Log.e("父组件分发了UP事件" , "--------")
        }
        return super.dispatchTouchEvent(ev)
    }

    override fun onInterceptTouchEvent(ev : MotionEvent?) : Boolean {
    
    
        if (ev!!.action == MotionEvent.ACTION_DOWN) {
    
    
            Log.e("父组件拦截了DOWN事件" , "--------")
            // return true
        } else if (ev.action == MotionEvent.ACTION_MOVE) {
    
    
            Log.e("父组件拦截了MOVE事件" , "--------")
//            return true
        } else if (ev.action == MotionEvent.ACTION_UP) {
    
    
            Log.e("父组件拦截了UP事件" , "--------")
//            return true
        }
        return super.onInterceptTouchEvent(ev)
    }

}
class MyView(
    context : Context , @Nullable attrs : AttributeSet? ,
    defStyleAttr : Int ,
) : View(context , attrs , defStyleAttr) {
    
    
    constructor(context : Context) : this(context , null , -1)
    constructor(context : Context , @Nullable attrs : AttributeSet?) : this(context , attrs , -1)

    init {
    
    
        setBackgroundColor(Color.parseColor("#FF0000"))
    }

    override fun onMeasure(widthMeasureSpec : Int , heightMeasureSpec : Int) {
    
    
        setMeasuredDimension(500 , 500)
    }

    override fun dispatchTouchEvent(event : MotionEvent?) : Boolean {
    
    
        if (event!!.action == MotionEvent.ACTION_DOWN) {
    
    
            Log.e("子组件分发了DOWN事件" , "--------")
        } else if (event.action == MotionEvent.ACTION_MOVE) {
    
    
            Log.e("子组件分发了MOVE事件" , "--------")
        } else if (event.action == MotionEvent.ACTION_UP) {
    
    
            Log.e("子组件分发了UP事件" , "--------")
        }else if (event.action == MotionEvent.ACTION_CANCEL) {
    
    
            Log.e("子组件分发了Cancel事件" , "--------")
        }
        return super.dispatchTouchEvent(event)
    }
    
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.cxkj.myapplication.MyLinearLayout
        android:layout_width="match_parent"

        android:layout_height="wrap_content">

        <com.cxkj.myapplication.MyView
            android:id="@+id/myView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">

        </com.cxkj.myapplication.MyView>

    </com.cxkj.myapplication.MyLinearLayout>

</LinearLayout>
val myView = findViewById<MyView>(R.id.myView)
myView.setOnClickListener {
    
    
    Log.e("子view被点击了" , "--------")
}

Code ci-dessus :
nous avons défini un LinaearLayout vert, réécrit les événements dispatchTouchEvent et onInterceptTouchEvent, et ajouté l'impression et l'observation.
Une vue rouge a été placée, réécrivant dispatchTouchEvent, et l'impression et l'observation ont été ajoutées.
1.) Nous interceptons Action_Move dans le composant parent, nous publions l'annotation // return true dans le code ci-dessous de MyLinearLayout, puis nous touchons MyView. Que va-t-il se passer ?

else if (ev.action == MotionEvent.ACTION_MOVE) {
    
    
    Log.e("父组件拦截了MOVE事件" , "--------")
    return true
}

Résultat de l'impression :
Le composant parent a distribué l'événement DOWN : --------
Le composant parent a intercepté l'événement DOWN : --------
Le composant enfant a distribué l'événement DOWN : ------- -
Le composant parent L'événement MOVE est distribué : --------
Le composant parent intercepte l'événement MOVE : --------
Le composant enfant distribue l'événement Cancel : --------
Le composant parent distribue l'événement MOVE : --------
Le composant parent distribue l'événement MOVE : --------
Le composant parent distribue l'événement UP : --------

On peut voir que l'événement DOWN a terminé la distribution normale et que le premier événement MOVE a été distribué uniquement par le ViewGroup, puis un événement Cancel supplémentaire a été distribué à la sous-vue, puis les événements MOVE suivants n'ont plus atteint le sous-vue. -View.Cette logique du code source ci-dessus est vérifiée (après interception, un événement Cancel sera distribué et mFirstTouchTarget sera reproduit comme null).

Et si on cliquait légèrement ? Qu'arrivera-t-il à l'impression sans aucune opération MOVE ? Le résultat de l'impression est le suivant :
Le composant parent a distribué l'événement DOWN : --------
Le composant parent a intercepté l'événement DOWN : ---------
Le composant enfant a distribué l'événement DOWN : --- -----
Parent Le composant a distribué l'événement UP : --------
Le composant parent a intercepté l'événement UP : ---------
Le composant enfant a distribué l'événement UP : ----- ---
La vue enfant a été cliquée : --------
Oui, car il n'y a pas d'événement MOVE et qu'il n'a pas été intercepté, mFirstTouchTarget n'a pas été réinitialisé à null, ce qui permet à l'événement UP d'être distribué normalement à le sous-composant.

2.) Et si nous interceptions directement l'événement DOWN ? Nous publions l'annotation // return true dans le code ci-dessous de MyLinearLayout, puis nous touchons MyView.

   if (ev!!.action == MotionEvent.ACTION_DOWN) {
    
    
            Log.e("父组件分发了DOWN事件" , "--------")
//            return true
        }

Résultat de l'impression :
Le composant parent a distribué l'événement DOWN : --------
Le composant parent a intercepté l'événement DOWN : --------

C'est étonnant que MyLinearLayout ne puisse distribuer que des événements DOWN. Pourquoi ? Parce que nous avons intercepté l'événement depuis le début, donc mFirstTouchTarget = null, tous les événements seront traités par MyLinearLayout lui-même, mais nous n'avons pas ajouté d'événement de clic à MyLinearLayout, c'est-à-dire que onTouchEvent de MyLinearLayout renvoie false, indiquant qu'il ne peut pas gérer les événements tactiles. N'oubliez pas que MyLinearLayout a également une mise en page parent. Pour la mise en page parent de MyLinearLayout, il effectuera également le processus ci-dessus. Lorsque la mise en page parent de MyLinearLayout sait que MyLinearLayout ne peut pas gérer les événements tactiles, elle ne distribuera plus les événements suivants à MyLinearLayout. la situation ci-dessus s'est produite.

Je suppose que tu aimes

Origine blog.csdn.net/weixin_43864176/article/details/123771939
conseillé
Classement