欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android事件分发剖析

程序员文章站 2022-05-19 16:09:03
...

前言

Android的事件分发机制在面试中是经常被问到的一个知识点,最近在看源码的过程中,看到了这一点,这里咱们就把Android的事件分发对照源码进行一下简单的剖析。

一、Activity的事件分发

说到事件分发不得不说一下Touch事件,Touch事件一共有四种事件类型,包括:MotionEvent.ACTION_DOWN、MotionEvent.ACTION_UP、MotionEvent.ACTION_MOVE、MotionEvent.CANCEL,分别对应按下、抬起、移动、和取消事件。

而事件分发是从Activity的dispatchTouchEvent方法开始的,下面我们以dispatchTouchEvent方法为入口,看下源码:

//Activity中的dispatchTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent ev) {
        //如果当前事件为按下事件,那么就会执行onUserInteraction()方法
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
}

我们看Activity 中的dispatchTouchEvent方法,如果过为按下事件执行onUserInteraction()方法,我们来看下onUserInteraction方法,发现这个方法是个空实现的方法:

public void onUserInteraction() {
}

紧接着看dispatchTouchEvent方法中的getWindow.superDispatchTouchEvent(ev),如果这个getWindow.superDispatchTouchEvent(ev)值返回true,那么事件传递结束。如果返回false,那么就会执行Activity的onTouchEvent方法,不管Activity的onTouchEvent方法返回true还是false,事件传递都会结束:

public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }

        return false;
}

 getWindow.superDispatchTouchEvent(ev)方法:

我们都知道每一个Activity都持有一个Window对象,而这里就是调用Activity所持有的Window对象的superDispatchTouchEvent,而Window类是一个抽象类,它的唯一的子类是PhoneWindow:

public abstract class Window {
    ...

}

 所以,getWindow.superDispatchTouchEvent(ev),其实是调用了PhoneWindow类里面的superDispatchTouchEvent方法:

//PhoneWindow的superDispatchTouchEvent方法
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
     return mDecor.superDispatchTouchEvent(event);
}

我们发现,这里其实又调用了DecorView的superDispatchTouchEvent方法:

//DecorView的superDispatchTouchEvent方法
public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
}

DecorView的superDispatchTouchEvent方法,又调用了其父类的dispatchTouchEvent方法,而DecorView是继承于FrameLayout的,而FrameLayout中没有dispatchTouchEvent方法,进而看FrameLayout的父类ViewGroup,所以找到了:DecorView其实是调用了ViewGroup里面的DispatchTouchEvent方法。所以到这事件就已经从Activity传递到了ViewGroup上。

在这里借用某大神的流程图:

Android事件分发剖析

二、ViewGroup的事件传递

ViewGroup的dispatchTouchEvent方法:

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        ...

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            ...

            // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }

            ...

            if (!canceled && !intercepted) {

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

                    ...
                }
            }

            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                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;
                }
            }

            ...
        }

        ...
        return handled;
    }

在这里,我们省略了一部分代码,关键看几个地方,一个是intercepted变量,他是通过onInterceptTouchEvent方法赋值的:

//ViewGroup中事件拦截的方法
public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
            return true;
        }
        return false;
}

如果onInterceptTouchEvent方法返回false那么就执行这里面的方法:

if (!canceled && !intercepted) {
    ...
}

我们看看当onInterceptTouchEvent为false时,这里面到底执行了什么方法?

大家注意到有这么一段代码:

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    // childIndex points into presorted list, find original index
                                    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;
}

通过dispatchTransformedTouchEvent方法来判断,如果这个方法的返回值为true,那么就执行里面的代码,如果为false,就不执行,这里面有个addTouchTarget方法:

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
}

这个addTouchTarget方法中有一句关键的代码:mFirstTouchTarget = target;通过这个,使mFirstTouchTarget变量的值不为空。返回到上面的代码中去,如果onInterceptTouchEvent方法不返回true(ViewGroup拦截事件),那么就不会执行到mFirstTouchTarget = target这行代码,那么mFirstTouchTarget 就是初始化值为null。

为什么说mFirstTouchTarget的值那么重要呢?我们看接下来的代码就会知晓:

// Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                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;
                }
}

根据mFirstTouchTarget变量是否为空,执行了不同的代码逻辑,不管mFirstTouchTarget是否为null都执行了dispatchTransformedTouchEvent这个方法。但是,

当mFirstTouchTarget==null时:

dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);

当mFirstTouchTarget不等于null时:

dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)

关键看 dispatchTransformedTouchEvent这个方法的第三个参数,当mFirstTouchTarget==null时,第三个参数为null,当mFirstTouchTarget不等于null时,第三个参数为target.child不为null。

再看dispatchTransformedTouchEvent方法的实现:

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

        ...
        return handled;
    }

由dispatchTransformedTouchEvent方法可以看出,

如果第三个参数child为null,那么执行:

handled = super.dispatchTouchEvent(event);

即执行当前ViewGroup父类的dispatchTouchEvent方法,因为ViewGroup的父类是View,那么就是执行View的dispatchTouchEvent方法。而ViewGroup的父类View又调用了onTouchEvent方法,即调用的就是子类ViewGroup的onTouchEvent方法。所以当onInterceptTouchEvent方法返回true的时候,就会调用ViewGroup的onTouchEvent方法,而不会再向下调用子View的方法。

如果第三个参数child不为null,那么执行:

handled = child.dispatchTouchEvent(event);

child即当前ViewGroup的子View(上面省略的代码部分,有for循环,获取到当前ViewGroup的子View),这里即执行ViewGroup的子View的dispatchTouchEvent方法。

到这里,ViewGroup就把事件传递给了View。

总结,如果onInterceptTouchEvent方法返回true,事件被拦截,那么接着就会调用ViewGroup的onTouchEvent方法,事件不会再向ViewGroup的子View进行传递。如果onInterceptTouchEvent方法返回false,事件未被拦截,那么接着会调用ViewGroup子View的dispatchTouchEvent,进而调用子View的onTouchEvent。

借用大神的流程图:

Android事件分发剖析

三、 View的事件传递

由ViewGroup传递过来的事件,首先会调用View的DispatchTouchEvent方法:

 public boolean dispatchTouchEvent(MotionEvent event) {
       ...

        boolean result = false;

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }

        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

       ...

        return result;
    }

在这里我们重点看一下这个:

if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
}

通过这个我们看到这里通过判断onTouch,如果设置了onTouchListener,那么result就会为true,那么就不会执行View的,onTouchEvent方法了,所以onTouch方法的优先级要高于onTouchEvent。而且通过这个我们可以知道,View中的事件传递,就是通过调用View的DispatchTouchEvent方法,紧接着调用View的onTouchEvent方法。

Android事件分发剖析

总结

通过以上的源码剖析,大概了解了事件从Activity到View分发的流程,下面借用大神的流程图,展示下,从Activity到View整个事件分发的流程:

Android事件分发剖析

相关标签: 源码剖析