Android 点击事件分发

      • Activity中对事件的处理
      • ViewGroup是如何进行事件处理的
      • View的dispatchTouchEvent相当重要,让我们继续look
      • 总结

Activity中对事件的处理

Activity事件分发方法,返回true表示事件被消费掉了

public boolean dispatchTouchEvent(MotionEvent ev) {        if (ev.getAction() == MotionEvent.ACTION_DOWN) {        //不知道干嘛用的,空实现            onUserInteraction();        }        if (getWindow().superDispatchTouchEvent(ev)) {            return true;        }        return onTouchEvent(ev);    }
  1. 调用window的事件分发,点击去,发现抽象方法,找到Window的实现类PhoneWindow的superDispatchTouchEvent方法
@Override    public boolean superDispatchTouchEvent(MotionEvent event) {        return mDecor.superDispatchTouchEvent(event);    }
  1. 调用了DecorView的superDispatchTouchEvent方法,在追进去就是ViewGroup的事件分发(DecorView继承的FrameLayout)
public boolean superDispatchTouchEvent(MotionEvent event) {        return super.dispatchTouchEvent(event);    }

ViewGroup是如何进行事件处理的

  1. 先进来的down事件,做一些重置操作
// 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                // due to an app switch, ANR, or some other state change.                cancelAndClearTouchTargets(ev);                resetTouchState();            }
  1. 判断是不是拦截,如果disallowIntercept = true禁止拦截, intercepted = false,否则的话会调用自身的onInterceptTouchEvent方法是不是需要拦截事件,默认返回false。
// Check for interception.            final boolean intercepted;            if (actionMasked == MotionEvent.ACTION_DOWN                    || mFirstTouchTarget != null) {                    //当我们调用getParent().requestDisallowInterceptTouchEvent()方法时,改变的就是FLAG_DISALLOW_INTERCEPT这个值                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;            }
  1. 如果ViewGroup不拦截事件
if (!canceled && !intercepted) {...                    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.                        //先把所有的子view按照Z轴,Z值越小排在集合的前面,也就是从最底层的view开始事件处理                        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 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 (!child.canReceivePointerEvents()                                    || !isTransformedTouchPointInView(x, y, child, null)) {                                ev.setTargetAccessibilityFocus(false);                                continue;                            }//得到处理事件的孩子,newTouchTarget为 null                            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);                            //把位置分发给处理事件的子孩子                            //dispatchTransformedTouchEvent把MotionEvent的坐标转换到子View的坐标空间,这不仅仅是x,y的偏移,还包括根据子View自身矩阵的逆矩阵对坐标进行变换(这就是使用setTranslationX,setScaleX等方法调用后,子View的点击区域还能保持和自身绘制内容一致的原因                         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经过add,不为null                                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;                    }                }            }
  1. 接下来进入dispatchTransformedTouchEvent这个方法
  • 处理cancel事件
...  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;        }        ...
  • 主要的逻辑,如果dispatchTransformedTouchEvent这个方法传给他child参数为null(如果事件被拦截,父类自己处理事件,调用此方法传递的child为null),会调用父类View的dispatchTouchEvent方法。不为null,计算偏移量,调用子类的dispatchTouchEvent方法。
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);                }

View的dispatchTouchEvent相当重要,让我们继续look

...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;            //如果我们外面设置了mOnTouchListener,并重写onTouch方法返回true了,那么就不会走onTouchEvent方法了。            if (li != null && li.mOnTouchListener != null                    && (mViewFlags & ENABLED_MASK) == ENABLED                    && li.mOnTouchListener.onTouch(this, event)) {                result = true;            }//如果我们没有设置mOnTouchListener,走到了onTouchEvent            if (!result && onTouchEvent(event)) {                result = true;            }        }        ...
  1. 接下来是onTouchEvent()方法进行事件的处理,可以看到所有的事件都是在这个方法内部处理掉的。
 if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {            switch (action) {                case MotionEvent.ACTION_UP:                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;                    if ((viewFlags & TOOLTIP) == TOOLTIP) {                        handleTooltipUp();                    }                    if (!clickable) {                        removeTapCallback();                        removeLongPressCallback();                        mInContextButtonPress = false;                        mHasPerformedLongPress = false;                        mIgnoreNextUpEvent = false;                        break;                    }                    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)) {                                //回调点击事件                                    performClickInternal();                                }                            }                        }                        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:                    if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {                        mPrivateFlags3 |= PFLAG3_FINGER_DOWN;                    }                    mHasPerformedLongPress = false;                    if (!clickable) {                        checkForLongClick(                                ViewConfiguration.getLongPressTimeout(),                                x,                                y,                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);                        break;                    }                    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(                                ViewConfiguration.getLongPressTimeout(),                                x,                                y,                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);                    }                    break;                case MotionEvent.ACTION_CANCEL:                    if (clickable) {                        setPressed(false);                    }                    removeTapCallback();                    removeLongPressCallback();                    mInContextButtonPress = false;                    mHasPerformedLongPress = false;                    mIgnoreNextUpEvent = false;                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;                    break;                case MotionEvent.ACTION_MOVE:                    if (clickable) {                        drawableHotspotChanged(x, y);                    }                    final int motionClassification = event.getClassification();                    final boolean ambiguousGesture =                            motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;                    int touchSlop = mTouchSlop;                    if (ambiguousGesture && hasPendingLongPressCallback()) {                        final float ambiguousMultiplier =                                ViewConfiguration.getAmbiguousGestureMultiplier();                        if (!pointInView(x, y, touchSlop)) {                            // The default action here is to cancel long press. But instead, we                            // just extend the timeout here, in case the classification                            // stays ambiguous.                            removeLongPressCallback();                            long delay = (long) (ViewConfiguration.getLongPressTimeout()                                    * ambiguousMultiplier);                            // Subtract the time already spent                            delay -= event.getEventTime() - event.getDownTime();                            checkForLongClick(                                    delay,                                    x,                                    y,                                    TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);                        }                        touchSlop *= ambiguousMultiplier;                    }                    // Be lenient about moving outside of buttons                    if (!pointInView(x, y, touchSlop)) {                        // Outside button                        // Remove any future long press/tap checks                        removeTapCallback();                        removeLongPressCallback();                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {                            setPressed(false);                        }                        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;                    }                    final boolean deepPress =                            motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;                    if (deepPress && hasPendingLongPressCallback()) {                        // process the long click action immediately                        removeLongPressCallback();                        checkForLongClick(                                0 /* send immediately */,                                x,                                y,                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);                    }                    break;            }//如果这个view 是可点击的clickable为true,则处理事件            return true;        }   //此方法默认值false,不消费事件   return false;

总结

  1. ViewGroup包涵多个子view的时候,按Z轴(也就是层)我们是从最底层遍历,判断当前view是否可以点击,然后分发给需要处理的子view。
  2. 我们可以在onInterceptTouchEvent中进行事件拦截。
  3. 我们可以发现ViewGroup没有onTouchEvent事件,说明他的处理逻辑和View是一样的。
  4. 子view如果消耗了事件,那么ViewGroup就不会在接受到事件了。

更多相关文章

  1. android install faild insufficient storage错误的解决方法
  2. [Android]ListView & ViewPager & GridView 常见问题解决方法
  3. Android中数据存储的几种方法
  4. Android退出时关闭所有Activity的方法
  5. 2011.07.01——— android GridView 长按事件不管用
  6. Android中判断Intent是否存在的方法

随机推荐

  1. Android下的PVPlayer的实现
  2. android 4.2修改设置菜单的背景颜色
  3. 推荐几本可以深入了解android系统应用开
  4. android与WEB服务器交互时,如何保证在同一
  5. Android Shape详解
  6. Android各层推荐开发书籍及参考资料
  7. [android]android自动化测试七之动态AVD
  8. Android系统中TextView实现滚动效果
  9. Android版本和API Level
  10. android个人学习笔记——RatingBar