android中的事件处理,以及解决滑动冲突问题都离不开事件分发机制,android中的事件流,即MotionEvent都会经历一个从分发,拦截到处理的一个过程。即dispatchTouchEvent(),onInterceptEvent()到onTouchEvent()的一个过程,在dispatchTouchEvent()负责了事件的分发过程,在dispatchTouchEvent()中会调用onInterceptEvent()与onTouchEvent(),如果onInterceptEvent()返回true,那么会调用到当前view的onTouchEvent()方法,如果不拦截,事件就会下发到子view的dispatchTouchEvent()中进行同样的操作。本文将带领大家从源码角度来分析android是如何进行事件分发的。

android中的事件分发流程最先从activity的dispatchTouchEvent()开始:

public boolean dispatchTouchEvent(MotionEvent ev) {    if (ev.getAction() == MotionEvent.ACTION_DOWN) {        onUserInteraction();    }    if (getWidow().superDispatchTouchEvent(ev)) {        return true;    }    return onTouchEvent(ev);}

这里调用了getWindow().superDispatchTouchEvent(ev),这里可以看出activity将MotionEvent传寄给了Window。而Window是一个抽象类,superDispatchTouchEvent()也是一个抽象方法,这里用到的是window的子类phoneWindow。

@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) {    return mDecor.superDispatchTouchEvent(event);}  

从这里可以看出,event事件被传到了DecorView,也就是我们的顶层view.我们继续跟踪:

public boolean superDispatchTouchEvent(MotionEvent event) {    return super.dispatchTouchEvent(event);}

这里调用到了父类的dispatchTouchEvent()方法,而DecorView是继承自FrameLayout,FrameLayout继承了ViewGroup,所以这里会调用到ViewGroup的dispatchTouchEvent()方法。

所以整个事件流从activity开始,传递到window,最后再到我们的view(viewGroup也是继承自view)中,而view才是我们整个事件处理的核心阶段。

我们来看一下viewGroup的dispatchTouchEvent()中的实现:

if (actionMasked == MotionEvent.ACTION_DOWN) {            // Throw away all previous state when starting a new touch gesture.            // The framework may have dropped the up or cancel event for the previous gesture            // due to an app switch, ANR, or some other state change.            cancelAndClearTouchTargets(ev);            resetTouchState();        }

这是dispatchTouchEvent()开始时截取的一段代码,我们来看一下,首先,当我们手指按下view时,会调用到resetTouchState()方法,在resetTouchState()中:

private void resetTouchState() {    clearTouchTargets();    resetCancelNextUpFlag(this);    mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;    mNestedScrollAxes = SCROLL_AXIS_NONE;}

我们继续跟踪clearTouchTargets()方法:

private void clearTouchTargets() {    TouchTarget target = mFirstTouchTarget;    if (target != null) {        do {            TouchTarget next = target.next;            target.recycle();            target = next;        } while (target != null);        mFirstTouchTarget = null;    }}

在clearTouchTargets()方法中,我们最终将mFirstTouchTarget赋值为null,我们继续回到dispatchTouchEvent()中,接着执行了下段代码:

// 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 {            // There are no touch targets and this action is not an initial down            // so this view group continues to intercept touches.            intercepted = true;        }

当view被按下或mFirstTouchTarget != null 的时候,从前面可以知道,当每次view被按下时,也就是重新开始一次事件流的处理时,mFirstTouchTarget都会被设置成null,一会我们看mFirstTouchTarget是什么时候被赋值的。
从disallowIntercept属性我们大概能猜到是用来判断是否需要坐拦截处理,而我们知道可以通过调用父view的requestDisallowInterceptTouchEvent(true)可以让我们的父view不能对事件进行拦截,我们先来看看requestDisallowInterceptTouchEvent()方法中的实现:

@Overridepublic void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {    if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {        // We're already in this state, assume our ancestors are too        return;    }    if (disallowIntercept) {        mGroupFlags |= FLAG_DISALLOW_INTERCEPT;    } else {        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;    }    // Pass it up to our parent    if (mParent != null) {        mParent.requestDisallowInterceptTouchEvent(disallowIntercept);    }}

这里也是通过设置标志位做判断处理,所以这里是通过改变mGroupFlags标志,然后在dispatchTouchEvent()刚发中变更disallowIntercept的值判断是否拦截,当为true时,即需要拦截,这个时候便会跳过onInterceptTouchEvent()拦截判断,并标记为不拦截,即intercepted = false,我们继续看viewGroup的onInterceptTouchEvent()处理:

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

即默认情况下,只有在ACTION_DOWN时,viewGroup才会表现为拦截。

我们继续往下看:

final int childrenCount = mChildrenCount;if (newTouchTarget == null && childrenCount != 0) {      final float x = ev.getX(actionIndex);      final float y = ev.getY(actionIndex);      // Find a child that can receive the event.      // Scan children from front to back.      final ArrayList 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 there is a view that has accessibility focus we want it                        // to get the event first and if not handled we will perform a                        // normal dispatch. We may do a double iteration but this is                        // safer given the timeframe.                        if (childWithAccessibilityFocus != null) {                            if (childWithAccessibilityFocus != child) {                                continue;                            }                            childWithAccessibilityFocus = null;                            i = childrenCount - 1;                        }                        if (!canViewReceivePointerEvents(child)                                || !isTransformedTouchPointInView(x, y, child, null)) {                            ev.setTargetAccessibilityFocus(false);                            continue;                        }                        newTouchTarget = getTouchTarget(child);                        if (newTouchTarget != null) {                            // Child is already receiving touch within its bounds.                            // Give it the new pointer in addition to the ones it is handling.                            newTouchTarget.pointerIdBits |= idBitsToAssign;                            break;                        }                        resetCancelNextUpFlag(child);                        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;                        }                        // The accessibility focus didn't handle the event, so clear                        // the flag and do a normal dispatch to all children.                        ev.setTargetAccessibilityFocus(false);                    }                    if (preorderedList != null) preorderedList.clear();                }

这段代码首先会通过一个循环去遍历所有的子view,最终会调用到dispatchTransformedTouchEvent()方法,我们继续看dispatchTransformedTouchEvent()的实现:

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,        View child, int desiredPointerIdBits) {    final boolean handled;    // Canceling motions is a special case.  We don't need to perform any transformations    // or filtering.  The important part is the action, not the contents.    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 for some reason we ended up in an inconsistent state where it looks like we    // might produce a motion event with no pointers in it, then drop the event.    if (newPointerIdBits == 0) {        return false;    }    // If the number of pointers is the same and we don't need to perform any fancy    // irreversible transformations, then we can reuse the motion event for this    // dispatch as long as we are careful to revert any changes we make.    // Otherwise we need to make a copy.    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;}

这段代码就比较明显了,如果child不为null,始终会调用到child.dispatchTouchEvent();否则调用super.dispatchTouchEvent();

如果child不为null时,事件就会向下传递,如果子view处理了事件,即dispatchTransformedTouchEvent()即返回true。继续向下执行到addTouchTarget()方法,我们继续看addTouchTarget()方法的执行结果:

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

这个时候我们发现mFirstTouchTarget又出现了,这时候会给mFirstTouchTarget重新赋值,即mFirstTouchTarget不为null。也就是说,如果事件被当前view或子view消费了,那么在接下来的ACTION_MOVE或ACTION_UP事件中,mFirstTouchTarget就不为null。但如果我们继承了该viewGroup,并在onInterceptTouchEvent()的ACTION_MOVE中拦截了事件,那么后续事件将不会下发,将由该viewGroup直接处理,从下面代码我们可以得到:

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

当存在子view并且事件被子view消费时,即在ACTION_DOWN阶段mFirstTouchTarget会被赋值,即在接下来的ACTION_MOVE事件中,由于intercepted为true,所以将ACTION_CANCEL 事件传递过去,从dispatchTransformedTouchEvent()中可以看到:

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

并将mFirstTouchTarget 最终赋值为 next,而此时mFirstTouchTarget位于TouchTarget链表尾部,所以mFirstTouchTarget会赋值为null,那么接下来的事件将不会进入到onInterceptTouchEvent()中。也就会直接交由该view处理。

如果我们没有进行事件的拦截,而是交由子view去处理,由于ViewGroup的onInterceptTouchEvent()默认并不会拦截除了ACTION_DOWN以外的事件,所以后续事件将继续交由子view去处理,如果存在子view且事件位于子view内部区域的话。

所以无论是否进行拦截,事件流都会交由view的dispatchTouchEvent()中进行处理,我们接下来跟踪一下view中的dispatchTouchEvent()处理过程:

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

当被按下时,即ACTION_DOWN时,view会停止内部的滚动,如果view没有被覆盖或遮挡时,首先会进行mListenerInfo是否为空的判断,我们看下mListenerInfo是在哪里初始化的:

ListenerInfo getListenerInfo() {    if (mListenerInfo != null) {        return mListenerInfo;    }    mListenerInfo = new ListenerInfo();    return mListenerInfo;}

这里可以看出,mListenerInfo一般不会是null,知道在我们使用它时调用过这段代码,而当view被加入window中的时候,会调用下面这段代码,从注释中也可以看出来:

/** * Add a listener for attach state changes. * * This listener will be called whenever this view is attached or detached * from a window. Remove the listener using * {@link #removeOnAttachStateChangeListener(OnAttachStateChangeListener)}. * * @param listener Listener to attach * @see #removeOnAttachStateChangeListener(OnAttachStateChangeListener) */public void addOnAttachStateChangeListener(OnAttachStateChangeListener listener) {    ListenerInfo li = getListenerInfo();    if (li.mOnAttachStateChangeListeners == null) {        li.mOnAttachStateChangeListeners                = new CopyOnWriteArrayList();    }    li.mOnAttachStateChangeListeners.add(listener);}

到这里我们就知道,mListenerInfo一开始就是被初始化好了的,所以li不可能为null,li.mOnTouchListener != null即当设置了TouchListener时不为null,并且view是enabled状态,一般情况view都是enable的。这个时候会调用到onTouch()事件,当onTouch()返回true时,这个时候result会赋值true。而当result为true时,onTouchEvent()将不会被调用。

从这里可以看出,onTouch()会优先onTouchEvent()调用;
当view设置touch监听并返回true时,那么它的onTouchEvent()将被屏蔽。否则会调用onTouchEvent()处理。

那么让我们继续来看看onTouchEvent()中的事件处理:

if ((viewFlags & ENABLED_MASK) == DISABLED) {        if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {            setPressed(false);        }        // A disabled view that is clickable still consumes the touch        // events, it just doesn't respond to them.        return (((viewFlags & CLICKABLE) == CLICKABLE                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);    }

首先,当view状态是DISABLED时,只要view是CLICKABLE或LONG_CLICKABLE或CONTEXT_CLICKABLE,都会返回true,而button默认是CLICKABLE的,textview默认不是CLICKABLE的,而view一般默认都不是LONG_CLICKABLE的。

我们继续向下看:

if (mTouchDelegate != null) {        if (mTouchDelegate.onTouchEvent(event)) {            return true;        }    }

如果有代理事件,仍然会返回true.

if (((viewFlags & CLICKABLE) == CLICKABLE ||            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||            (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {        switch (action) {            case MotionEvent.ACTION_UP:                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {                    // take focus if we don't have it already and we should in                    // touch mode.                    boolean focusTaken = false;                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {                        focusTaken = requestFocus();                    }                    if (prepressed) {                        // The button is being released before we actually                        // showed it as pressed.  Make it show the pressed                        // state now (before scheduling the click) to ensure                        // the user sees it.                        setPressed(true, x, y);                   }                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {                        // This is a tap, so remove the longpress check                        removeLongPressCallback();                        // Only perform take click actions if we were in the pressed state                        if (!focusTaken) {                            // Use a Runnable and post this rather than calling                            // performClick directly. This lets other visual state                            // of the view update before click actions start.                            if (mPerformClick == null) {                                mPerformClick = new PerformClick();                            }                            if (!post(mPerformClick)) {                                performClick();                            }                        }                    }                    if (mUnsetPressedState == null) {                        mUnsetPressedState = new UnsetPressedState();                    }                    if (prepressed) {                        postDelayed(mUnsetPressedState,                                ViewConfiguration.getPressedStateDuration());                    } else if (!post(mUnsetPressedState)) {                        // If the post failed, unpress right now                        mUnsetPressedState.run();                    }                    removeTapCallback();                }                mIgnoreNextUpEvent = false;                break;            case MotionEvent.ACTION_DOWN:                mHasPerformedLongPress = false;                if (performButtonActionOnTouchDown(event)) {                    break;                }                // Walk up the hierarchy to determine if we're inside a scrolling container.                boolean isInScrollingContainer = isInScrollingContainer();                // For views inside a scrolling container, delay the pressed feedback for                // a short period in case this is a scroll.                if (isInScrollingContainer) {                    mPrivateFlags |= PFLAG_PREPRESSED;                    if (mPendingCheckForTap == null) {                        mPendingCheckForTap = new CheckForTap();                    }                    mPendingCheckForTap.x = event.getX();                    mPendingCheckForTap.y = event.getY();                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());                } else {                    // Not inside a scrolling container, so show the feedback right away                    setPressed(true, x, y);                    checkForLongClick(0, x, y);                }                break;            case MotionEvent.ACTION_CANCEL:                setPressed(false);                removeTapCallback();                removeLongPressCallback();                mInContextButtonPress = false;                mHasPerformedLongPress = false;                mIgnoreNextUpEvent = false;                break;            case MotionEvent.ACTION_MOVE:                drawableHotspotChanged(x, y);                // Be lenient about moving outside of buttons                if (!pointInView(x, y, mTouchSlop)) {                    // Outside button                    removeTapCallback();                    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {                        // Remove any future long press/tap checks                        removeLongPressCallback();                        setPressed(false);                    }                }                break;        }        return true;    }

当view是CLICKABLE或LONG_CLICKABLE或CONTEXT_CLICKABLE状态时,当手指抬起时,如果设置了click监听,最终会调用到performClick(),触发click()事件。这点从performClick()方法中可以看出:

public boolean performClick() {    final boolean result;    final ListenerInfo li = mListenerInfo;    if (li != null && li.mOnClickListener != null) {        playSoundEffect(SoundEffectConstants.CLICK);        li.mOnClickListener.onClick(this);        result = true;    } else {        result = false;    }    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);    return result;}

从这里我们也可以得出,click事件会在onTouchEvent()中被调用,如果view设置了onTouch()监听并返回true,那么click事件也会被屏蔽掉,不过我们可以在onTouch()中通过调用view的performClick()继续执行click()事件,这个就看我们的业务中的需求了。

从这里我们可以看出,如果事件没有被当前view或子view处理,即返回false,那么事件就会交由外层view继续处理,直到被消费。

如果事件一直没有被处理,会最终传递到Activity的onTouchEvent()中。

到这里我们总结一下:
事件是从Activity->Window->View(ViewGroup)的一个传递流程;

如果事件没有被中途拦截,那么它会一直传到最内层的view控件;

如果事件被某一层拦截,那么事件将不会向下传递,交由该view处理。如果该view消费了事件,那么接下来的事件也会交由该view处理;如果该view没有消费该事件,那么事件会交由外层view处理,...并最终调用到activity的onTouchEvent()中,除非某一层消费了该事件;

一个事件只能交由一个view处理;

DispatchTouchEvent()总是会被调用,而且最先被调用,onInterceptTouchEvent()和onTouchEvent()在DispatchTouchEvent()内部调用;

子view不能干扰ViewGroup对ACTION_DOWN事件的处理;

子view可以通过requestDisallowInterceptTouchEvent(true)控制父view不对事件进行拦截,跳过onInterceptTouchEvent()方法的执行。

更多相关文章

  1. Android(安卓)LayoutInflater 源码分析
  2. Android(安卓)API学习 SoundPool 和 MediaPlayer
  3. Android(安卓)内存溢出(Out Of Memory)的总结
  4. android 之activity生命周期(转载)
  5. Unity与Android接口互相调用
  6. cocos2dx程序 在android上 触碰屏幕就崩溃 crash in cocos on to
  7. Android防止过快点击造成多次事件执行(防止按钮重复点击)
  8. IntentFilter使用
  9. 【Android(安卓)开源系列】之视频处理框架

随机推荐

  1. 阅读手札:《:第一行代码》(第一章)
  2. 基于Android FrameLayout的使用详解
  3. 使用ProgressBar实现加载进度条
  4. Android的安全体系
  5. Android 软键盘
  6. Android Display System --- Surface Fli
  7. android MediaPlayer详解
  8. Android基础UI篇------TextView及其子类
  9. [整理]学习Android的博客和网站
  10. android 谷歌地图开发