Android中ViewGroup、View事件分发机制源码分析总结(雷惊风)

1.概述

        很长时间没有回想Android中的事件分发机制了,打开目前的源码发现与两三年前的实现代码已经不一样了,5.0以后发生了变化,更加复杂了,但是万变不离其宗,实现原理还是一样的,在这里将5.0以前的时间分发机制做一下源码剖析及总结。会涉及到几个方法,dispatchTouchEvent()表示事件开始分发方法,在ViewGroup与View中都有,onInterCeptTouchEvent()表示是否拦截当前事件,比如ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCLE,只在ViewGroup中存在,因为一个View的子类已经是最内层View,而ViewGroup的子类还会包含子View,所以它需要可以设置是否拦截当前事件传递到子View中,onToutchEvent()表示消费当前事件,OnToutchListener,OnClickListener,OnLongClickListener。

2.源码剖析

        我会将详细的代码注释加在源码中,首先说一下在Activity与Window中的分发过程。当我们点击屏幕上的一个View时,事件的分发就开始了,首先会调用当前Activity的dispatchTouchEvent(MotionEvent ev)方法,我们打开Activity类看一下它的实现:

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
只挑主要的说,可以看到调用了getWindow().superDispatchTouchEvent()方法,如果这个方法返回true,则表示事件被消费,直接返回true,如果返回false,会执行自己的onTouchEvent()表明如果View树中都没有消费事件,当前Activity消费事件。我们知道在Phone上边PhoneWindow是Window的唯一子类,那我们就去PhoneWindow中找找相关方法:

//phoneWindow
public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
在PhoneWindow中可以看到调用了mDocor的super***方法,大家应该都知道mDecor为window中的最顶级View类,他是PhoneWindow的内部类,继承了FrameLayout。我们进入mDecor的内部看看它的实现:

//DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
            return super.dispatchTouchEvent(event);
        }
在DecorView中又调用了父类方法,我们最终在GroupView中找到了相关实现,这样我们就完成了一个点击事件从Activity到ViewGroup的过程。无论是 ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCLE任何一个都是如此的一直传递流程。

总结:点击--->Activity.dispatchTouchEvent(event)--->PhoneWindow.superDispatchTouchEvent(event)--->DecorView.superDispatchTouchEvent(event)--->ViewGroup.dispatchTouchevent(event);

说完了一个事件从Activity到ViewGroup的传递流程,下边说一下一个事件在ViewGroup中是如何分发的,看一下源码:

//ViewGroup中;
public boolean dispatchTouchEvent(MotionEvent ev) {  
    final int action = ev.getAction();  
    final float xf = ev.getX();  
    final float yf = ev.getY();  
    final float scrolledXFloat = xf + mScrollX;  
    final float scrolledYFloat = yf + mScrollY;  
    final Rect frame = mTempRect;  
    boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
	//ViewGroup中按下事件处理;
    if (action == MotionEvent.ACTION_DOWN) { 
		//如果mMotionTarget保存了View,因为是从新开始的DOWN,所以清空;
        if (mMotionTarget != null) {  
            mMotionTarget = null;  
        }  
		//是否禁用拦截事件功能,如果禁用了拦截功能(不拦截)或者onInterceptTouchEvent()返回false,说明不拦截事件。
        if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
            ev.setAction(MotionEvent.ACTION_DOWN);  
            final int scrolledXInt = (int) scrolledXFloat;  
            final int scrolledYInt = (int) scrolledYFloat;  
            final View[] children = mChildren;  
            final int count = mChildrenCount; 
			//循环每一个子View;
            for (int i = count - 1; i >= 0; i--) {  
                final View child = children[i];  
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
                        || child.getAnimation() != null) {  
                    child.getHitRect(frame);
					//判断当前子View是否包含点击区域;
                    if (frame.contains(scrolledXInt, scrolledYInt)) {  
                        final float xc = scrolledXFloat - child.mLeft;  
                        final float yc = scrolledYFloat - child.mTop;  
                        ev.setLocation(xc, yc);  
                        child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
						//调用子View的dispatchTouchEvent() ,向子事件分发事件;
						//1.如果返回true,说明子类或者子类的某个子类中消费了事件,将这个child记录到mMotionTarget中,每一个ViewGroup都记录了
						//消费了这个事件的子View,像链式,最后当前ViewGroup返回true,返回给调用这个ViewGroup的外层ViewGroup,一直到Activity中
						//的dispatchTouchEvent()中,最后Activity中也返回true;
						//2.如果返回false,说明子类或者子类的子类中没有消费这个事件,所以mMotionTarget不会被赋值,会继续向下执行代码;
                        if (child.dispatchTouchEvent(ev))  {  
                            mMotionTarget = child;  
                            return true;  
                        }  
                    }  
                }  
            }  
        }  
    }  
    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  
            (action == MotionEvent.ACTION_CANCEL);  
    if (isUpOrCancel) {  
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
    }  
    final View target = mMotionTarget;
    //ACTION_DOWN事件时,被拦截,或者子类没有消费ACTION_DOWN,调用自己的onTouchEvent()执行onTouch、onClick()方法等;
	//ACTION_UP时,子类没有消费ACTION_DOWN;
    if (target == null) {  
        ev.setLocation(xf, yf);  
        if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
            ev.setAction(MotionEvent.ACTION_CANCEL);  
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
        }
		//调用super方法即调用到View中的dispatchTouchEvent()方法,还是调用的当前View,
		//在View中会调用自己的onTouchEvent()执行onTouch、onClick()方法等处理;
		//这里两种情况:1.自身没有处理返回false;2.自身处理了返回true,本身处理了,不会保存到mMotionTarget中,而它的外层ViewGroup会保存他;
		//true/false都会一直返回到Activity中。
        return super.dispatchTouchEvent(ev);  
    }  
	//ACTION_UP或者MOVE时,子类中消费了ACTION_DOWN事件,没有禁用拦截功能(可以拦截),并且拦截事件;
	// 无论 target 是否为 null ,ACTION_DOWN事件的处理都不能走到这里,在之下都是ACTION_MOVE和ACTION_UP的逻辑
	// 如果执行到这里,说明有响应ACTION_DOWN事件的view对象,这就看我们是否被允许拦截和要不要拦截了
	// 如果允许拦截并且拦截了ACTION_MOVE和ACTION_UP事件,则将ACTION_CANCEL事件分发给target
	// 然后直接返回true,表示已经响应了该次事件
    if (!disallowIntercept && onInterceptTouchEvent(ev)) {  
        final float xc = scrolledXFloat - (float) target.mLeft;  
        final float yc = scrolledYFloat - (float) target.mTop;  
        mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
        ev.setAction(MotionEvent.ACTION_CANCEL);  
        ev.setLocation(xc, yc);
		//将子类事件重置为ACTION_CANCEL;
        if (!target.dispatchTouchEvent(ev)) {  
        }  
        mMotionTarget = null;  
        return true;  
    }  
    if (isUpOrCancel) {  
        mMotionTarget = null;  
    }  
    final float xc = scrolledXFloat - (float) target.mLeft;  
    final float yc = scrolledYFloat - (float) target.mTop;  
    ev.setLocation(xc, yc);  
    if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
        ev.setAction(MotionEvent.ACTION_CANCEL);  
        target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
        mMotionTarget = null;  
    }
    // 如果没有拦截ACTION_MOVE和ACTION_UP事件,则直接派发给target	
    return target.dispatchTouchEvent(ev);  
} 
注释加了不少,看几遍就能明白。
事件分发机制在ViewGroup中的派发流程:
在ViewGroup的dispatchTouchEvent()中,
1.ACTION_DOWN:首先判断拦截相关设置,不拦截:获取每一个子类判断点击区域是否在当前子类上,
在的话调用其dispatchTouchEvent()方法,方法返回true说明当前View处理当前事件序列,记录到mMotionTarget中返回true,
表明找到了处理当前事件的View,停止事件分发。方法返回false,说明当前子View及其子孙View不处理当前事件,如果所有子View都不处理,
后续代码会调用当前ViewGroup的super.dispatchTouchEvent(ev),即执行自己的OnTouch(),OnClick()等(自己处理),如果返回了true,
表明自己处理当前事件,如果返回false,自己不处理当前事件。无论返回true还是false最终都是返回到Activity中。
2.ACTION_MOVE:判断拦截相关:不拦截,调用target.dispatchTouchEvent(ev)进行事件分发;拦截:将target的MotionEvent置为Cancel,
mMotionTarget赋值为null,返回true,表明自己消费了事件。这次不会调用自己的super.dispatchTouchEvent(ev)。
再次MOVE分发时由于mMotionTarget为空了,所以会调用自己的super.dispatchTouchEvent(ev),即自身处理。
3.ACTION_CANCEL或ACTION_UP:回复状态默认值,判断是否拦截,拦截返回true;不拦截向下派发事件。

下边看一下在Viewgroup的dispatchTouchEvent方法中继续向下分发事件代码if(child.dispatchTouchEvent(ev))执行后如果child也是一个ViewGroup子类那么跟上边的逻辑是一样的,那么如果child是一个View的子类,比如Button、TextView、ImageView等时,是如何分发事件的,即事件在View类中的传递流程,接着看一下源码:

//View中的dispatchTouchEvent;
public boolean dispatchTouchEvent(MotionEvent event) {  
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
            mOnTouchListener.onTouch(this, event)) {  
        return true;  
    }  
    return onTouchEvent(event);  
}
重要源码就这些,在这里可以看到首先判断我们有没有给当前View设置OnTouchListener事件,并且当前View是enabled状态的,如果前两个条件都满足,则会调用我们 OnTouchListener中的onTouch()方法,如果上边三个条件都为true,则直接返回true,表明当前View消费当前事件,如果我们在onTouch()方法中返回false,那么就会跳过判断执行自己的onTouchEvent()方法,这里先提前说一下,调用onTouchEvent()方法也有继续判断当前View是否消费当前事件的作用,在其内部会根据我们点击在控件上停留时间判断是否执行OnClick,OnLongClick事件。下边看一下onTouchEvent()源码:

//View中onTouchEvent();
public boolean onTouchEvent(MotionEvent event) {  
    final int viewFlags = mViewFlags;
	//当前View是Disabled状态且是可点击则会消费掉事件	
    if ((viewFlags & ENABLED_MASK) == DISABLED) {  
        // 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));  
    }  
	//将事件交给代理者处理,直接return true
    if (mTouchDelegate != null) {  
        if (mTouchDelegate.onTouchEvent(event)) {  
            return true;  
        }  
    }  
	//View可以点击或者长按,只要进入判断,一定反回true,即消费事件;
    if (((viewFlags & CLICKABLE) == CLICKABLE ||  
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
        switch (event.getAction()) {  
            case MotionEvent.ACTION_UP:  
                boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; 
				//100ms内或者以后都会进入代码块;
                if ((mPrivateFlags & 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();  
                    }
				    //如果是在0到500ms之间或者500秒之后执行了LongClick事件但返回的false(不拦截事件);
                    if (!mHasPerformedLongPress) {  
                        // This is a tap, so remove the longpress check
					    //取消长按事件任务监听;
                        removeLongPressCallback();  
                        // Only perform take click actions if we were in the pressed state
						//在pressed状态,只执行Click事件;
                        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) {  
                        mPrivateFlags |= PRESSED;  
                        refreshDrawableState();  
                        postDelayed(mUnsetPressedState,  
                                ViewConfiguration.getPressedStateDuration());  
                    } else if (!post(mUnsetPressedState)) {  
                        // If the post failed, unpress right now  
                        mUnsetPressedState.run();  
                    }  
                    removeTapCallback();  
                }  
                break;  
            case MotionEvent.ACTION_DOWN:  
                if (mPendingCheckForTap == null) {  
                    mPendingCheckForTap = new CheckForTap();  
                }
				//设置被点击状态;
                mPrivateFlags |= PREPRESSED;
				//没有触发长按事件;
                mHasPerformedLongPress = false;
				//发送100毫秒延时消息,执行CheckForTap类中方法。
                postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
                break;  
            case MotionEvent.ACTION_CANCEL:  
                mPrivateFlags &= ~PRESSED;  
                refreshDrawableState();  
                removeTapCallback();  
                break;  
            case MotionEvent.ACTION_MOVE:  
                final int x = (int) event.getX();  
                final int y = (int) event.getY();  
                // Be lenient about moving outside of buttons  
                int slop = mTouchSlop;
                if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
                        (y < 0 - slop) || (y >= getHeight() + slop)) {  
                    // Outside button 
					//手指滑出了View范围,取消100ms计时任务;					
                    removeTapCallback(); 
					//判断是否包含PRESSED标识,如果包含,移除长按的检查任务;					
                    if ((mPrivateFlags & PRESSED) != 0) {  
                        // Remove any future long press/tap checks  
                        removeLongPressCallback();  
                        // Need to switch from pressed to not pressed  
                        mPrivateFlags &= ~PRESSED;  
                        refreshDrawableState();  
                    }  
                }  
                break;  
        }  
        return true;  
    }  
    return false;  
} 
有些相对来说重要的类或者代码我会在下边贴出来,帮助大家更好地理解,在这里代码就不详细的讲了,在最后我还会总结,相关代码:

//点击状态变为PRESSED类;
private final class CheckForTap implements Runnable {  
      public void run() {  
          mPrivateFlags &= ~PREPRESSED;
		  //设置PRESSED标识
          mPrivateFlags |= PRESSED;
		  //刷新背景
          refreshDrawableState(); 
		  //如果View支持长按事件,调用postCheckForLongClick()方法;
          if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {  
              postCheckForLongClick(ViewConfiguration.getTapTimeout());  
          }  
      }  
  }
//ViewConfiguration.getLongPressTimeout() 为500ms;再次发送500-100=400ms延时消息
private void postCheckForLongClick(int delayOffset) {  
       mHasPerformedLongPress = false;  
  
       if (mPendingCheckForLongPress == null) {  
           mPendingCheckForLongPress = new CheckForLongPress();  
       }  
       mPendingCheckForLongPress.rememberWindowAttachCount();  
       postDelayed(mPendingCheckForLongPress,  
               ViewConfiguration.getLongPressTimeout() - delayOffset);  
   } 
//长按事件发生处理器;
class CheckForLongPress implements Runnable {  
  
        private int mOriginalWindowAttachCount;  
  
        public void run() {  
            if (isPressed() && (mParent != null)  
                    && mOriginalWindowAttachCount == mWindowAttachCount) {  
				//执行LongClick监听;
                if (performLongClick()) {  
                    mHasPerformedLongPress = true;  
                }  
            }  
        }  
   }
总结:也就是说如果用户设置了LongClickListener,从用户触发ACTION_DOWN开始500ms才能触发事件,如果达不到500ms,视为点击事件;
如果longClickListener中返回true,mHasPerformedLongPress就是true;
//还在100ms内,取消监听;
   private void removeTapCallback() {  
       if (mPendingCheckForTap != null) {  
           mPrivateFlags &= ~PREPRESSED;  
           removeCallbacks(mPendingCheckForTap);  
       }  
   } 

//取消长按事件任务监听;
   private void removeLongPressCallback() {
        if (mPendingCheckForLongPress != null) {
          removeCallbacks(mPendingCheckForLongPress);
        }
    }
总结:主要就是判断是否移出当前View,如果移出,根据时间移出相关计时任务;
private final class UnsetPressedState implements Runnable {
        public void run() {
            setPressed(false);
        }
    }
 public void setPressed(boolean pressed) {
        if (pressed) {
            mPrivateFlags |= PRESSED;
        } else {
            mPrivateFlags &= ~PRESSED;
        }
        refreshDrawableState();
        dispatchSetPressed(pressed);
    }
清除PRESSED,刷新背景,向下传递pressed;


事件分发在View中的流程总结:
调用View的dispatchTouchEvent(),判断是否设置了OnTouchListener并且View为Enable的,如果都满足,调用OnTouchListener.onTouch(this, event),如果返回true,
直接返回true,表明当前View处理当前事件,不会调用自己的onTouchEvent(),如果onTouth()方法返回false,则调用onTouchEvent()。
在onTouchEvent()中如果当前View为可点击或者可长按则一定返回true,否则返回false。
1.ACTION_DOWN:初始化状态为PREPRESSED,设置mHasPerformedLongPress为flase,发送100ms任务,如果100ms到后,还没有UP或者移出当前View,
则将标志置为PRESSED,如果支持长按事件,发送另一个400ms计时任务,如果到时间后依然没有UP,LongClickListener不为空,调用执行,返回true,将
mHasPerformedLongPress设置为true。
2.ACTION_MOVE:检测用户有没有移出View,并移除相关时间任务。
3.ACTION_UP:100ms内仍然是PREPRESSED状态,不会触发Click;在100到500ms之间,取消仍然运行的计时任务,执行 performClick去执行Onclick;
如果在500ms以后,没有设置onLongClickListener或者返回false,仍然会执行performClick;返回true,onClick不执行;LongClick在Click前执行。

       在事件分发过程中OnTouchListener优先OnClickListener与OnLongClickListener执行,如果返回false,OnClickListener与OnLongClickListener才有可能执行到,返回true不执行。在500ms内抬起手指不会执行OnLongClickListener,会执行OnClickListener,在500ms以后如果OnLongClickListener返回false,OnClickListener会执行,返回true不执行。这就是他们几个的执行顺序及规则,当然这是建立在我们注册了这三个监听。今天的总结就到这里,希望对初学者有帮助,如果哪里不对,欢迎大神拍砖,谢谢!

猜你喜欢

转载自blog.csdn.net/liuyonglei1314/article/details/68941640
今日推荐