在android触屏事件中,我们经常会碰到onclick(),onTouch(),onTouchEven()等方法,那谁会先执行,执行顺序又是怎么样呢?

View的触屏事件处理

为弄清除上面那些,首先从源码入手,看看其整个触屏事件分发的过程.
先从dispatchTouchEvent()分析:

/**     * Pass the touch screen motion event down to the target view, or this     * view if it is the target.     *     * @param event The motion event to be dispatched.     * @return True if the event was handled by the view, false otherwise.     */    public boolean dispatchTouchEvent(MotionEvent event) {        // If the event should be handled by accessibility focus first.        if (event.isTargetAccessibilityFocus()) {            // We don't have focus or no virtual descendant has it, do not handle the event.            if (!isAccessibilityFocusedViewOrHost()) {                return false;            }            // We have focus and got the event, then use normal event dispatch.            event.setTargetAccessibilityFocus(false);        }        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)) {            //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;            }        }               if (!result && mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);        }        // Clean up after nested scrolls if this is the end of a gesture;        // also cancel it if we tried an ACTION_DOWN but we didn't want the rest        // of the gesture.        if (actionMasked == MotionEvent.ACTION_UP ||                actionMasked == MotionEvent.ACTION_CANCEL ||                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {            stopNestedScroll();        }        return result;    }

分析这段源码,便知道有些细节地方,如果目标View不能获取焦点,那么就不会处理触屏事件,而是直接返回false ,否则判断目标View是否满足注册了onTouchListener()监听以及可点击的条件,满足则会调用onTouch(),如果onTouch()返回true,那么就不会再执行onTouchEvent()了,如果前面onTouch()返回了false,那么就会由onTouchEvent()的返回值来确定最终的结果值了,因此遵从onTouch()==true? true:onTouchEvent()规则.在这段源码中,可以看出其执行顺序的优先级是dispatchOnTouchEvent()>>>>onTouch()>>>>onTouchEvent()
再来看看onTouchEvent()在View.java源码中有的实现:

/**     * Implement this method to handle touch screen motion events.     * 

* If this method is used to detect click actions, it is recommended that * the actions be performed by implementing and calling * {@link #performClick()}. This will ensure consistent system behavior, * including: *

    *
  • obeying click sound preferences *
  • dispatching OnClickListener calls *
  • handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when * accessibility features are enabled *
* * @param event The motion event. * @return True if the event was handled, false otherwise. */
public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; final int action = event.getAction(); 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); } if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return 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); } //如果onLongClick()返回true,就不会执行onClick() 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); } 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; } return false; } private void checkForLongClick(int delayOffset) { if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) { mHasPerformedLongPress = false; if (mPendingCheckForLongPress == null) { mPendingCheckForLongPress = new CheckForLongPress(); } mPendingCheckForLongPress.rememberWindowAttachCount(); postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout() - delayOffset); } }private final class CheckForTap implements Runnable { public float x; public float y; @Override public void run() { mPrivateFlags &= ~PFLAG_PREPRESSED; setPressed(true, x, y); checkForLongClick(ViewConfiguration.getTapTimeout()); } }private final class CheckForLongPress implements Runnable { private int mOriginalWindowAttachCount; @Override public void run() { if (isPressed() && (mParent != null) && mOriginalWindowAttachCount == mWindowAttachCount) { //返回true的话,就不执行onclick() if (performLongClick()) { mHasPerformedLongPress = true; } } } public void rememberWindowAttachCount() { mOriginalWindowAttachCount = mWindowAttachCount; } } private final class PerformClick implements Runnable { @Override public void run() { performClick(); } }

这段代码稍微多一点,但也有好多细节要注意的,如果有目标View有传入过TouchDelegate对象,那么一切来到这里的触屏事件都会交给其TouchDelegate.onTouchEvent()处理并返回true,在ACTION_DOWN阶段中,如果顶层容器不是滚动类的容器,那么直接通知目标View转变按压视图状态,并且开始通过postDelayed()来设置CheckForLongPress()对象,一旦长按时间超过了ViewConfiguration.getLongPressTimeout()CheckForLongPress()对象就会执行对应长按回调方法,如果顶层容器是滚动类的容器,那么会通过postDelayed()传入CheckForTap()对象,直到ViewConfiguration.getTapTimeout()时候才让View转变按压视图状态,而不是立刻改变,同时CheckForTap()对象也创建对应CheckForLongPress()对象.同时还带有prepressed时期标记,在ACTION_UP阶段中,如果目标View没有长按(既不会执行CheckForLongPress()对象里的方法),同时不能获取到焦点并返回false,那么便会执行PerformClick()对应方法,从而调用OnClickListener.onclick(),还有最后一点的细节就是,如果目标View满足CLICKABLE,LONG_CLICKABLE,CONTEXT_CLICKABLE其中一个条件,那么onTouchEvent()就会返回true.在经过一些列的分析,可以看出其执行的顺序优先级:`onTouchEvent()>>>>TouchDelegate.onTouchEvent()>>>>onLongClick()>>>>onClick().

ViewGroup的触屏分发实现

上面的那段是基于View.java源码分析,那么来分析一下其ViewGroup.java源码,看看其又是如何分发触屏事件的.

/**     * Transforms a motion event into the coordinate space of a particular child view,     * filters out irrelevant pointer ids, and overrides its action if necessary.     * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.     */     //用来派发给子View触屏事件    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);            //如果child为空,则会回调父类的dispatchTouchEvent()            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 {                   //如果触屏事件设置偏移量以和支持单位矩阵变换的子view一致                   //如子View做一些平移等动画操作时的点击                    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()) {                //恢复原来触屏事件发生坐标,即相当于撤销了offsetLocation()作用                transformedEvent.transform(child.getInverseMatrix());            }            handled = child.dispatchTouchEvent(transformedEvent);        }        // Done.        transformedEvent.recycle();        return handled;    }

在触屏事件分发给子View的逻辑中可以知道一个细节,如果child为空,那么会回调父类的dispatchTouchEvent()方法,否则就调用子View的dispatchTouchEvent(),执行顺序的优先级:当前ViewGroup的dispatchTouchEvent()>>>>>childView!=null?dispatchTouchEvent():super.dispatchTouchEvent().

 /* Describes a touched view and the ids of the pointers that it has captured.     *     * This code assumes that pointer ids are always in the range 0..31 such that     * it can use a bitfield to track which pointer ids are present.     * As it happens, the lower layers of the input dispatch pipeline also use the     * same trick so the assumption should be safe here...     */    private static final class TouchTarget {        private static final int MAX_RECYCLED = 32;        private static final Object sRecycleLock = new Object[0];        private static TouchTarget sRecycleBin;        private static int sRecycledCount;        public static final int ALL_POINTER_IDS = -1; // all ones        // The touched child view.        public View child;        // The combined bit mask of pointer ids for all pointers captured by the target.        public int pointerIdBits;//通过位标记来记录节点在链表的位置        // The next target in the target list.        public TouchTarget next;        private TouchTarget() {        }        public static TouchTarget obtain(View child, int pointerIdBits) {            final TouchTarget target;            synchronized (sRecycleLock) {                if (sRecycleBin == null) {                    target = new TouchTarget();                } else {                    target = sRecycleBin;                    sRecycleBin = target.next;                     sRecycledCount--;                    target.next = null;                }            }            target.child = child;            target.pointerIdBits = pointerIdBits;            return target;        }        public void recycle() {            synchronized (sRecycleLock) {                if (sRecycledCount < MAX_RECYCLED) {                    next = sRecycleBin;                    sRecycleBin = this;                    sRecycledCount += 1;                } else {                    next = null;                }                child = null;            }        }    }

通过链表方式来维护触屏事件的,但在这里有个很巧妙的地方,也通过sRecycleBin指针来复用对象,很多地方都用类似的技巧来复用对象,如listView创建item对象,具体过程如图所示(假设初始化时,不串联对象):

dispatchTouchEvent()方法中,由于这段代码还比较长,很多细节,因此我把代码分成两个阶段去分析.

//第一阶段 拦截事件时,触屏事件的分发处理//即某段时刻intercepted会返回true. /**     * {@inheritDoc}     */    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);        }        // If the event targets the accessibility focused view and this is it, start        // normal event dispatch. Maybe a descendant is what will handle the click.        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;            // Handle an initial down.            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                // 在ACTION_DOWN会丢弃以前的触屏事件和清除触屏状态                cancelAndClearTouchTargets(ev);                resetTouchState();            }            // Check for interception.            final boolean intercepted;            //在ACTION_DOWN时候会执行或有目标子View的时候            //如果没有找到处理触屏事件的View那么其他的            //后续触屏事件会被当前的容器拦截,不再分发给子View            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;            }            // If intercepted, start normal event dispatch. Also if there is already            // a view that is handling the gesture, do normal event dispatch.            if (intercepted || mFirstTouchTarget != null) {                ev.setTargetAccessibilityFocus(false);            }            // Check for cancelation.            final boolean canceled = resetCancelNextUpFlag(this)                    || actionMasked == MotionEvent.ACTION_CANCEL;            // Update list of touch targets for pointer down, if needed.            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;            TouchTarget newTouchTarget = null;            boolean alreadyDispatchedToNewTouchTarget = false;             if (!canceled && !intercepted){              //省略另一阶段的代码.............................                }                                // Dispatch to touch targets.            //拦截事件且没有子View处理触屏事件的时候,即回调父类的dispatchTransformedTouchEvent()            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.                //会把在拦截事件前所捕获到的触屏事件交给子View去处理,拦截事件后的触屏事件交给自己去                处理                TouchTarget predecessor = null;//前个触屏事件节点                TouchTarget target = mFirstTouchTarget;//当前触屏事件节点                while (target != null) {                    final TouchTarget next = target.next;                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {                        handled = true;                    } else {                        //派发拦截事件前的触屏事件给子View,如果拦截(intercepted==true)                        //则触屏事件交给父类dispatchOnTouchEvent()                        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;    }

在分析上面第一阶段时,注意到一些细节处理,如果ViewGroup没有嵌套子View的话,那么就会回调父类的dispatchTouchEvent(),但如果ViewGroup有嵌套子View,但没有一开始拦截的话,那就会将拦截前的触屏事件交给子ViewdispatchTouchEvent()去处理,拦截后的触屏事件交给父类的dispatchTouchEvent()去处理.还有一点就是如果某个子View在dispatchTouchEvent()onTouch()onTouchEvent()都不处理触屏事件ACTION_DOWNACTON_MOVE等阶段,即都返回false,那子View就再也接受不到触屏事件的后续阶段了(如ACTION_UP).

//第二阶段不拦截触屏事件 /**     * {@inheritDoc}     */    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {            //省略代码............................................            if (!canceled && !intercepted) {                // If the event is targeting accessiiblity focus we give it to the                // view that has accessibility focus and if it does not handle it                // we clear the flag and dispatch the event to all children as usual.                // We are looking up the accessibility focused host to avoid keeping                // state since these events are very rare.                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()                        ? findChildWithAccessibilityFocus() : null;                if (actionMasked == MotionEvent.ACTION_DOWN                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {                    final int actionIndex = ev.getActionIndex(); // always 0 for down                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)                            : TouchTarget.ALL_POINTER_IDS;                    // Clean up earlier touch targets for this pointer id in case they                    // have become out of sync.                    removePointersFromTouchTargets(idBitsToAssign);                    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 = buildOrderedChildList();                        final boolean customOrder = preorderedList == null                                && isChildrenDrawingOrderEnabled();                        final View[] children = mChildren;                        for (int i = childrenCount - 1; i >= 0; i--) {                            final int childIndex = customOrder                                    ? getChildDrawingOrder(childrenCount, i) : i;                            final View child = (preorderedList == null)                                    ? children[childIndex] : preorderedList.get(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();                    }                    if (newTouchTarget == null && mFirstTouchTarget != null) {                        // Did not find a child to receive the event.                        // Assign the pointer to the least recently added target.                        newTouchTarget = mFirstTouchTarget;                        while (newTouchTarget.next != null) {                            newTouchTarget = newTouchTarget.next;                        }                        newTouchTarget.pointerIdBits |= idBitsToAssign;                    }                }            }        //省略代码.........................................................        return handled;    }

这段代码主要处理ACTION_DOWN阶段,只在ACTION_DOWN阶段仅一次或0次调用onInterceptTouchEvent()来决定要不要拦截触屏事件,如果不拦截,那就找消费触屏事件的子View,即子View处理触屏事件的最终结果返回true,然后做一些记录操,最后用链表串联触屏对象.在结合第一段阶段的代码,可以看出其触屏事件执行顺序的优先级:当前容器的dispatchTouchEvent()>>>>>onInterceptTouchEvent()>>>>>childView!=null?dispatchTouchEvent():super.dispatchTouchEvent().

触屏事件处理DEMO

上面围绕一大堆源码分析后,可能很容易被搞混,为印象更加深刻,基于上面理论实践一个demo来验证,并画出对应的处理流程图.

public class MyButton extends Button implements View.OnClickListener,View.OnTouchListener,View.OnLongClickListener{    private static final String TAG = MyButton.class.getName();    public MyButton(Context context, AttributeSet attrs) {        super(context, attrs);        setOnClickListener(this);        setOnTouchListener(this);        setOnLongClickListener(this);        Button button=new Button(context){            @Override            public boolean onTouchEvent(MotionEvent event) {                Log.i(TAG,"OtherButton>>>>>>>>>>>onTouchEvent()");                return true;            }        };        DisplayMetrics displayMetrics=context.getResources().getDisplayMetrics();        setTouchDelegate(new TouchDelegate(new Rect(0,0,displayMetrics.widthPixels,displayMetrics.heightPixels),button));    }    @Override    public boolean onLongClick(View view) {        Log.i(TAG,"MyButton>>>>>>>>>>>onLongClick");        return false;    }    @Override    public boolean onTouch(View view, MotionEvent motionEvent) {        Log.i(TAG,"MyButton>>>>>>>>>>>onTouch");        return false;    }    @Override    public void onClick(View view) {        Log.i(TAG,"MyButton>>>>>>>>>>>onClick");    }    @Override    public boolean onTouchEvent(MotionEvent event) {        Log.i(TAG,"MyButton>>>>>>>>>>>onTouchEvent");        return super.onTouchEvent(event);    }    @Override    public boolean dispatchTouchEvent(MotionEvent event) {        Log.i(TAG,"event_action>>>>>>>>>>>"+event.getAction());        Log.i(TAG,"MyButton>>>>>>>>>>>DispatchTouchEvent");        return super.dispatchTouchEvent(event);    }}


将代理的button的onTouch()返回false.其短按和长按事件处理流程如图:
短按:

长按:

对于View处理触屏事件的其他的情况,我就不再一一测试了,总的来说,View处理触屏事件流程就如图所示:

//子布局public class MyButton extends Button implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener {  private static final String TAG = MyButton.class.getName();  public MyButton(Context context, AttributeSet attrs) {    super(context, attrs);      }  @Override  public boolean dispatchTouchEvent(MotionEvent event) {    switch (event.getAction()){      case MotionEvent.ACTION_DOWN:        Log.i(TAG, "ACTION_DOWN>>>>>>>>>>>DispatchTouchEvent");        break;      case MotionEvent.ACTION_MOVE:        Log.i(TAG, "ACTION_MOVE>>>>>>>>>>>DispatchTouchEvent");        break;      case MotionEvent.ACTION_UP:        Log.i(TAG, "ACTION_UP>>>>>>>>>>>DispatchTouchEvent");        break;    }    return false;  }}//父布局,该布局会嵌套MyButtonpublic class MyRelativeLayout extends RelativeLayout {  private static final String TAG = MyRelativeLayout.class.getName();  public MyRelativeLayout(Context context, AttributeSet attrs) {    super(context, attrs);    //setClickable(true);  }  @Override  public boolean dispatchTouchEvent(MotionEvent event) {    switch (event.getAction()){      case MotionEvent.ACTION_DOWN:        Log.i(TAG, "ACTION_DOWN>>>>>>>>>>>dispatchTouchEvent");        break;      case MotionEvent.ACTION_MOVE:        Log.i(TAG, "ACTION_MOVE>>>>>>>>>>>dispatchTouchEvent");        break;      case MotionEvent.ACTION_UP:        Log.i(TAG, "ACTION_UP>>>>>>>>>>>dispatchTouchEvent");        break;    }    return super.dispatchTouchEvent(event);  }  @Override  public boolean onInterceptTouchEvent(MotionEvent ev) {    switch (ev.getAction()){      case MotionEvent.ACTION_DOWN:        Log.i(TAG, "ACTION_DOWN>>>>>>>>>>>onInterceptTouchEvent");        break;      case MotionEvent.ACTION_MOVE:        Log.i(TAG, "ACTION_MOVE>>>>>>>>>>>onInterceptTouchEvent");        break;      case MotionEvent.ACTION_UP:        Log.i(TAG, "ACTION_UP>>>>>>>>>>>onInterceptTouchEvent");        break;    }    return false;  }  @Override  public String toString() {    return "MyRelativeLayout";  }  @Override  public boolean onTouchEvent(MotionEvent event) {    switch (event.getAction()){      case MotionEvent.ACTION_DOWN:        Log.i(TAG, "ACTION_DOWN>>>>>>>>>>>onTouchEvent");        break;      case MotionEvent.ACTION_MOVE:        Log.i(TAG, "ACTION_MOVE>>>>>>>>>>>onTouchEvent");        break;      case MotionEvent.ACTION_UP:        Log.i(TAG, "ACTION_UP>>>>>>>>>>>onTouchEvent");        break;    }    return true;  }}


日志可以看出子View没有消费触屏事件的话,会回传给父容器去处理触屏事件的,并且只在ACTION_DOWN阶段调用onInterceptTouchEvent(),再把MyRelativeLayout.onInterceptTouchEvent()改为true.

再把MyRelativeLayout.onTouchEvent()返回false,那么又会怎么样呢?

public class MainActivity extends AppCompatActivity {  private static final String TAG = MainActivity.class.getName();  @Override  protected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);  }  @Override  public boolean dispatchTouchEvent(MotionEvent ev) {    switch (ev.getAction()){      case MotionEvent.ACTION_DOWN:        Log.i(TAG, "ACTION_DOWN>>>>>>>>>>>dispatchTouchEvent");        break;      case MotionEvent.ACTION_MOVE:        Log.i(TAG, "ACTION_MOVE>>>>>>>>>>>dispatchTouchEvent");        break;      case MotionEvent.ACTION_UP:        Log.i(TAG, "ACTION_UP>>>>>>>>>>>dispatchTouchEvent");        break;    }    return super.dispatchTouchEvent(ev);  }  @Override  public boolean onTouchEvent(MotionEvent event) {    switch (event.getAction()){      case MotionEvent.ACTION_DOWN:        Log.i(TAG, "ACTION_DOWN>>>>>>>>>>>onTouchEvent");        break;      case MotionEvent.ACTION_MOVE:        Log.i(TAG, "ACTION_MOVE>>>>>>>>>>>onTouchEvent");        break;      case MotionEvent.ACTION_UP:        Log.i(TAG, "ACTION_UP>>>>>>>>>>>onTouchEvent");        break;    }    return true;  }}


子View再也没有收到触屏事件了吧,如果最顶层容器onTouchEvent()返回false不消费触屏事件的话,那么回回传给Actvitiy的onTouchEvent()去处理,即使Activitiy的onTouchEvent()返回false,也会能接受到触屏事件的后续阶段.到这里,我画个总的流程图来概况一下:

更多相关文章

  1. UI篇--布局问题
  2. 调用android系统相机拍照并保存图片
  3. Android(安卓)Httpclient重定向问题
  4. Android(安卓)解析如何获取SDCard 内存
  5. Android(安卓)广播事件机制
  6. android之listener
  7. Android解析如何获取SDCard 内存
  8. Android防止过快点击造成多次事件
  9. Android(安卓)Intent Activity 跳转几种情况所使用的不同方法

随机推荐

  1. android按键移植
  2. Android架构组件(三)——ViewModel
  3. Android(安卓)运行底层linux外部命令的实
  4. 相对布局
  5. 如何获得Android手机的软件安装列表
  6. Android(安卓)学习笔记【基础扫盲篇】
  7. 去掉ListView滑动的黑色背景和点击ListVi
  8. 远程调测:Chrome on Android之三 调测WebV
  9. 【Android开发基础】应用界面主题Theme使
  10. android中的UI控制(二)