View的事件分发机制

什么是事件

从用户手指点击屏幕到抬起之间的一系列动作就是事件。一般包含:

ACTION_DOWN
ACTION_UP
ACTION_MOVE
ACTION_CANCEL

具体查看MotionEvent类。事件分发就是MotionEvent分发的过程,当系统产生一个MotionEvent后,系统需要把这个事件传递到具体的View,这就是事件分发过程。

分发过程

事件分发过程由三个重要的方法完成。

public boolean dispatchTouchEvent(MotionEvent ev)

如果一个MotionEvent传递给了View,那么dispatchTouchEvent方法一定会被调用!
返回值:表示是否消费了当前事件。可能是View本身的onTouchEvent方法消费,也可能是子ViewdispatchTouchEvent方法中消费。返回true表示事件被消费,本次的事件终止。返回false表示View以及子View均没有消费事件,将调用父ViewonTouchEvent方法。

public boolean onInterceptTouchEvent(MotionEvent ev)

事件拦截,当一个ViewGroup在接到MotionEvent事件序列时候,首先会调用此方法判断是否需要拦截。特别注意,这是ViewGroup特有的方法,View并没有拦截方法
返回值:是否拦截事件传递,返回true表示拦截了事件,那么事件将不再向下分发而是调用View本身的onTouchEvent方法。返回false表示不做拦截,事件将向下分发到子ViewdispatchTouchEvent方法。如果当前view拦截了该某个事件,那么同一个事件序列当中,该方法不会被再次调用。在dispatchTouchEvent中调用。

public boolean onTouchEvent(MotionEvent ev)

真正对MotionEvent进行处理或者说消费的方法。在dispatchTouchEvent进行调用。
返回值:返回true表示事件被消费,本次的事件终止。返回false表示事件没有被消费,将调用父ViewonTouchEvent方法。如果不消耗,那么同一事件序列中,当前view无法再次收到事件。

三者关系

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean consume = false;
        if (onInterceptTouchEvent(ev)) {
            consume = onTouchEvent(ev);
        } else {
            consume = child.dispatchTouchEvent(MotionEvent ev);
        }
        return consume;
    }

传递流程

在这里插入图片描述

还需注意:

  1. 子View可以通过requestDisallowInterceptTouchEvent方法干预父View的事件分发过程(ACTION_DOWN事件除外)。阻止父view拦截事件。
  2. 对于View(注意!ViewGroup也是View)而言,如果设置了onTouchListener,那么OnTouchListener方法中的onTouch方法会被回调。onTouch方法返回true,则onTouchEvent方法不会被调用(onClick事件是在onTouchEvent中调用)所以三者优先级是onTouch->onTouchEvent->onClick
  3. ViewonTouchEvent 方法默认都会消费掉事件(返回true),除非它是不可点击的(clickablelongClickable同时为false),View的longClickable默认为false,clickable需要区分情况,如Button的clickable默认为true,而TextView的clickable默认为false

源码分析下

系统产生事件最先传递到的是当前的Activity, 由其dispatchTouchEvent进行事件分发。

    /**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     *
     * @param ev The touch screen event.
     *
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) { //用户点击屏幕交互
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
        	// 传递到window, 如果消费掉事件,直接返回
            return true;
        }
        // 没有消费,Activity自己消费
        return onTouchEvent(ev);
    }

getWindow其实就是获取android.view.PhoneWindow这是Window的唯一实现。

/**
 * Abstract base class for a top-level window look and behavior policy.  An
 * instance of this class should be used as the top-level view added to the
 * window manager. It provides standard UI policies such as a background, title
 * area, default key processing, etc.
 *
 * <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */
public abstract class Window {
}
    // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;
    //调用的是decorview的superDispatchTouchEvent.
    // DecorView就是Window的顶级View,我们通过setContentView设置的View是它的子View				(Activity的setContentView,最终是调用PhoneWindow的setContentView
    
    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

DecorViewviewGroup,因此进入View的事件分发流程。

//DecorView
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }
//viewGroup
public boolean dispatchTouchEvent(MotionEvent ev) {


        // Handle an initial down.
        //  并且重置mFirstTouchTarget = null
        // mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
       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();
       }
        // Check for interception.
        // 只有actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null 采取判断是否拦截事件,
        // mFirstTouchTarget表示成功处理事件的子元素,也就是收如果mFirstTouchTarget == null表示该事件由自己处理
        // 后续事件自己拦截处理。

		// 对于FLAG_DISALLOW_INTERCEP通过requestDisallowInterceptTouchEvent方法设置,一旦设置后可以看到intercepted == true,
		//父view就不会拦截事件,但是不会对ACTION_DOWN生效,因为actionMasked == MotionEvent.ACTION_DOWN会重置mGroupFlags状态。
         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;
         }


		//看viewGroup分发给子view
		// 遍历所有子view
		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;
            }
            // 如果点击坐标没在这个子view内或者子view在播放动画那么找下一个子view
            if (!child.canReceivePointerEvents()
                    || !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);
            // 同时满足上面两个条件传递给子view处理
           // 如果返回true也就是被当前子view处理了,那么mFirstTouchTarget在addTouchTarget(child, idBitsToAssign);被赋值。跳出循环。
           // 如果返回false那么循环下一个子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 = 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);
        }




        // Dispatch to touch targets.
        // 没有找到自己处理进入view的分发流程
        if (mFirstTouchTarget == null) {
            // No touch targets so treat this as an ordinary view.
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
}



View的分发流程

  public boolean dispatchTouchEvent(MotionEvent event) {

        boolean result = false;

		...

        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            // 首先判断是否设置了OnTouchListener, 如果设置了就调用其onTouch方法,如果返回true, 那么
            // onTouchEvent就不会被调用, 否则调用onTouchEvent
            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;
    }
public boolean onTouchEvent(MotionEvent event) { 

        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

		// 不可用的view也会消耗事件
        if ((viewFlags & ENABLED_MASK) == DISABLED
                && (mPrivateFlags4 & PFLAG4_ALLOW_CLICK_WHEN_DISABLED) == 0) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return clickable;
        }

		//只要是可点击就消耗事件
		if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
           switch (action) {
               case MotionEvent.ACTION_UP:
                   mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                   if ((viewFlags & TOOLTIP) == TOOLTIP) {
                       handleTooltipUp();
                   }
				.....
				{
                  // 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();
                      }
                  }
              }
            ...
            return true
		}

	reture false
}


 public boolean performClick() {
        // We still need to call this method to handle the cases where performClick() was called
        // externally, instead of through performClickInternal()
        notifyAutofillManagerOnClick();

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

public void setOnClickListener(@Nullable OnClickListener l) {
    if (!isClickable()) {
        setClickable(true);
    }
    getListenerInfo().mOnClickListener = l;
}

public void setOnTouchListener(OnTouchListener l) {
   getListenerInfo().mOnTouchListener = l;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值