android 事件分发机制源码解析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/alcoholdi/article/details/53871197

首先我重新写了这了几个控件类,只是加了点打印日志,来观察里面的事件分发机制.

然后写了个布局,如图.

MainActivity有dispatchTouchEvent, onTouchEvent方法

MyRelativieLayout有dispatchTouchEvent, onInterceptTouchEvent, onTouchEvent方法

TextView和Button有dispatchTouchEvent, onTouchEvent方法.

这里拿MyRelativeLayout1的代码来举例,如下

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e(TAG, "MyRelativeLayout1 dispatchTouchEvent begin >> "
                + (ev.getAction() == MotionEvent.ACTION_DOWN ? "down" : ev.getAction() == MotionEvent.ACTION_UP ? "up" : ev.getAction() == MotionEvent.ACTION_MOVE ? "move" : "other"));
        boolean flag = super.dispatchTouchEvent(ev);
        Log.e(TAG, "MyRelativeLayout1 dispatchTouchEvent end " + flag + " >> "
                + (ev.getAction() == MotionEvent.ACTION_DOWN ? "down" : ev.getAction() == MotionEvent.ACTION_UP ? "up" : ev.getAction() == MotionEvent.ACTION_MOVE ? "move" : "other"));
        return flag;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e(TAG, "MyRelativeLayout1 onInterceptTouchEvent "
                + (ev.getAction() == MotionEvent.ACTION_DOWN ? "down" : ev.getAction() == MotionEvent.ACTION_UP ? "up" : ev.getAction() == MotionEvent.ACTION_MOVE ? "move" : "other"));
        boolean flag = super.onInterceptTouchEvent(ev);
        Log.e(TAG, "MyRelativeLayout1 onInterceptTouchEvent end " + flag + " >> "
                + (ev.getAction() == MotionEvent.ACTION_DOWN ? "down" : ev.getAction() == MotionEvent.ACTION_UP ? "up" : ev.getAction() == MotionEvent.ACTION_MOVE ? "move" : "other"));
        return flag;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        Log.e(TAG, "MyRelativeLayout1 onTouchEvent "
                + (ev.getAction() == MotionEvent.ACTION_DOWN ? "down" : ev.getAction() == MotionEvent.ACTION_UP ? "up" : ev.getAction() == MotionEvent.ACTION_MOVE ? "move" : "other"));
        boolean flag = super.onTouchEvent(ev);
        Log.e(TAG, "MyRelativeLayout1 onTouchEvent end " + flag + " >> "
                + (ev.getAction() == MotionEvent.ACTION_DOWN ? "down" : ev.getAction() == MotionEvent.ACTION_UP ? "up" : ev.getAction() == MotionEvent.ACTION_MOVE ? "move" : "other"));
        return flag;
    }
扫描二维码关注公众号,回复: 3027951 查看本文章

然后点击MyRelativeLayout2区域时打印出

MainActivity: MainActivity dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent end false >> down
MyRelativeLayout2: MyRelativeLayout2 dispatchTouchEvent begin >> down
MyRelativeLayout2: MyRelativeLayout2 onInterceptTouchEvent begin >> down
MyRelativeLayout2: MyRelativeLayout2 onInterceptTouchEvent end false >> down
MyRelativeLayout2: MyRelativeLayout2 onTouchEvent begin >> down
MyRelativeLayout2: MyRelativeLayout2 onTouchEvent end false >> down
MyRelativeLayout2: MyRelativeLayout2 dispatchTouchEvent end false >> down
MyRelativeLayout1: MyRelativeLayout1 onTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onTouchEvent end false >> down
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent end false >> down
MainActivity: MainActivity onTouchEvent begin >> down
MainActivity: MainActivity onTouchEvent end false >> down
MainActivity: MainActivity dispatchTouchEvent end false >> down
MainActivity: MainActivity dispatchTouchEvent begin >> up
MainActivity: MainActivity onTouchEvent begin >> up
MainActivity: MainActivity onTouchEvent end false >> up
MainActivity: MainActivity dispatchTouchEvent end false >> up

这里能看到. down事件走了这么多步,up事件才走了4步.先来张图展示一下流程.

其实事件传递,在 MainActivity 和 MyRelativeLayout1 之间,还会经过 PhoneWindow 这层,然后是 DecorView这层,接着是DecorView 的子View ——一个LinearLayout, 这个LinearLayout的子View.....一直到这个我们需要观测的子View——我们的MyRelativeLayout1。

为什么ACTION_UP的流程才走了这么一点?下面看源码回答

点击左边的MyTextView

MainActivity: MainActivity dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent end false >> down
MyTextView: MyTextView dispatchTouchEvent begin >> down
MyTextView: MyTextView onTouchEvent begin >> down
MyTextView: MyTextView onTouchEvent end false >> down
MyTextView: MyTextView dispatchTouchEvent end false >> down
MyRelativeLayout1: MyRelativeLayout1 onTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onTouchEvent end false >> down
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent end false >> down
MainActivity: MainActivity onTouchEvent begin >> down
MainActivity: MainActivity onTouchEvent end false >> down
MainActivity: MainActivity dispatchTouchEvent end false >> down
MainActivity: MainActivity dispatchTouchEvent begin >> up
MainActivity: MainActivity onTouchEvent begin >> up
MainActivity: MainActivity onTouchEvent end false >> up
MainActivity: MainActivity dispatchTouchEvent end false >> up

图和上面的差不多,差别是TextView不是ViewGroup.没有onInterceptTouchEvent事件

点击右边的MyButton

MainActivity: MainActivity dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent end false >> down
MyButton: MyButton dispatchTouchEvent begin >> down
MyButton: MyButton onTouchEvent begin >> down
MyButton: MyButton onTouchEvent end true >> down
MyButton: MyButton dispatchTouchEvent end true >> down
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent end true >> down
MainActivity: MainActivity dispatchTouchEvent end true >> down
MainActivity: MainActivity dispatchTouchEvent begin >> up
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent begin >> up
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent begin >> up
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent end false >> up
MyButton: MyButton dispatchTouchEvent begin >> up
MyButton: MyButton onTouchEvent begin >> up
MyButton: MyButton onTouchEvent end true >> up
MyButton: MyButton dispatchTouchEvent end true >> up
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent end true >> up
MainActivity: MainActivity dispatchTouchEvent end true >> up

当点击了Button这种clickable控件,就会在onTouchEvent中返回true.

之后的代码其实用处就不大了,因为返回true后会绕过后面MyRelativeLayout1和Activity的onTouchEvent.

意思是拦截了当前事件的继续派发,自己处理和消化.

所以down流程走完了,up流程会跟down流程走同样的方法路径.

看了上面log和图,接着来分析下源码.(源码基于android-23)

先看Activity的dispatchTouchEvent方法

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

很简单.getWindow().superDispatchTouchEvent(ev)意思就是调用子View的dispatchTouchEvent方法.

如果子View没有拦截返回false,就执行Activity的onTouchEvent方法.拦截了就不执行.

getWindow().superDispatchTouchEvent(ev)会把事件传给第一个View——DecorView
 

然后来看看看ViewGroup的dispatchTouchEvent.几乎所有原生的安卓布局都没有重写这个方法,而是用的他们父类ViewGroup的.

整个diapatchTouchEvent太长了,以下截取部分重要代码...

public boolean dispatchTouchEvent(MotionEvent ev) {
	
		final boolean intercepted;//这是2103行
		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;
		}//这是2117行
	
	
		if (!canceled && !intercepted) {//这是2133行
			...
			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;
			}
			...
		}//这是2235行
}


点击非clickable控件流程

1.down流程

如果是点击的非clickable控件,那么down事件会执行intercepted = onInterceptTouchEvent(ev);

intercepted人如其名,表示是否拦截掉事件交给,交给自己的onTouchEvent处理. 不拦截就交给子View处理.

现在讨论的是正常流程,所以默认布局的onInterceptTouchEvent会返回false, intercepted 为false.

然后2133行的 !canceled && !intercepted 判断为真(canceled不在本文讨论范围)

然后会执行里面最重要的依据dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign), 这个就是把事件派发给子View,交由子View处理的方法.

假设子View都没有处理,就会返回false.跳过了里面的逻辑.

2.up流程

up事件因为actionMasked不等于ACTION_DOWN.所以跑了else的部分,使的intercepted = true;

就不会跑2133那里的if代码段,也就不会派发事件给子View.

所以就解释了上面的问题"为什么ACTION_UP的流程才走了这么一点?"

解释一下设计原理就是,我down事件都派发过给子View了,但是儿子们都没处理,所以后续的up事件(包括中间如果有的move事件)都不会再派发到子类了.

省时省力!

所以小总结一句,当1.自己重写onInterceptTouchEvent拦截,或者2.不是down事件并且子View又没一个给力的能兜住事件, 这两种情况就会自己消费了.

点击clickable控件流程

1.down流程

如果是点击的是clickable控件,流程像上面那样,走到了dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)

然后clickable控件返回了true.执行了里面的addTouchTarget(child, idBitsToAssign)

这个方法大意就是,如果子View有clickable控件,就让当前这个View(父View)持有这个TouchTarget.

从MyRelativelayout1执行这个持有方法,持有MyButton

到FrameLayout执行这个持有方法,持有MyRelativelayout1

到LinearLayout持有FrameLayout

到DecorView持有LinearLayout

一步步赋值(持有)上去.

所有ViewGroup都会有一个变量mFirstTouchTarget, 它是存放该ViewGroup中能消费事件的子View(既可clickable的控件). 好根据它来用来判断后面的流程要怎么走.

ps:明明父布局就是MyRelativeLayout1,为什么会有多出来这么多其他控件.因为手机本质是这样的布局.给个图但是不展开讲了

2.up流程

然后在up事件分发的时候,就在mFirstTouchTarget != null判断为真,执行给intercepted赋值为假的逻辑.

然后在2133代码判断后,up流程就会继续派发事件给子View.

以此达到down,move,up所有事件都最终交给子View处理.

当时研究我想,那么问题来了,如果知道是Button的onTouch事件消费了,能不能下次直接从MainActivity直接绕过中间没用的MyRelativeLayout1等等这些没用的东西到达Button呢.

从打印信息看来,目前还没这么智能.

以上是正常流程..

下面讲讲根据自己的需要改写流程的情况.

以下情景是点击右边的MyButton

1.把dispatchTouchEvent改写成直接返回false

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e(TAG, "MyRelativeLayout1.java - dispatchTouchEvent() ---------- return false");
        return false;
    }

就会打印log
MainActivity: MainActivity dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1.java - dispatchTouchEvent() ---------- return false
MainActivity: MainActivity onTouchEvent begin >> down
MainActivity: MainActivity onTouchEvent end false >> down
MainActivity: MainActivity dispatchTouchEvent end false >> down
MainActivity: MainActivity dispatchTouchEvent begin >> up
MainActivity: MainActivity onTouchEvent begin >> up
MainActivity: MainActivity onTouchEvent end false >> upMainActivity: MainActivity dispatchTouchEvent end false >> up
意思是不会交给子View处理,自己也不处理.
返回false也不拦截,所以就会把事件处理交给Activity的onTouchEvent.
 

2.把dispatchTouchEvent改成返回true

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e(TAG, "MyRelativeLayout1.java - dispatchTouchEvent() ---------- return true");
        return true;
    }

就会打印log
MainActivity: MainActivity dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1.java - dispatchTouchEvent() ---------- return true
MainActivity: MainActivity dispatchTouchEvent end true >> down
MainActivity: MainActivity dispatchTouchEvent begin >> up
MyRelativeLayout1: MyRelativeLayout1.java - dispatchTouchEvent() ---------- return trueMainActivity: MainActivity dispatchTouchEvent end true >> up
这样改写就把事件留住,不再分发子View,然后可以把业务逻辑写在这个方法里面
但是这样不规范,一般想要实现这种效果,都是用下面的方法.

3.在onInterceptTouchEvent中返回true, 在onTouchEvent也返回true,并在其中写处理逻辑

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e(TAG, "MyRelativeLayout1 onInterceptTouchEvent >> "
                + (ev.getAction() == MotionEvent.ACTION_DOWN ? "down" : ev.getAction() == MotionEvent.ACTION_UP ? "up" : ev.getAction() == MotionEvent.ACTION_MOVE ? "move" : "other"));
        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        Log.e(TAG, "MyRelativeLayout1 onTouchEvent >> "
                + (ev.getAction() == MotionEvent.ACTION_DOWN ? "down" : ev.getAction() == MotionEvent.ACTION_UP ? "up" : ev.getAction() == MotionEvent.ACTION_MOVE ? "move" : "other"));
        Log.e(TAG, "MyRelativeLayout1.java - onTouchEvent() ---------- 写处理逻辑");
        return true;
    }

MainActivity: MainActivity dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent >> down
MyRelativeLayout1: MyRelativeLayout1 onTouchEvent >> down
MyRelativeLayout1: MyRelativeLayout1.java - onTouchEvent() ---------- 写处理逻辑
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent end true >> down
MainActivity: MainActivity dispatchTouchEvent end true >> down
MainActivity: MainActivity dispatchTouchEvent begin >> up
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent begin >> up
MyRelativeLayout1: MyRelativeLayout1 onTouchEvent >> up
MyRelativeLayout1: MyRelativeLayout1.java - onTouchEvent() ---------- 写处理逻辑
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent end true >> up
MainActivity: MainActivity dispatchTouchEvent end true >> up

这是重写事件处理中最常用的方法了.
onInterceptTouchEvent中返回true使得不会再把事件派发到子View,然后回转到onTouchEvent执行自己的业务代码
使用场景经常是一个布局文件里面有很多子View,但是却不想分发自己处理,就会用这种写法.


稍微讲解一下这个流程的源码

public boolean dispatchTouchEvent(MotionEvent ev) {
	
	final boolean intercepted;//这是2103行
	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;
	}//这是2117行


	if (!canceled && !intercepted) {//这是2133行
		...
		newTouchTarget = addTouchTarget(child, idBitsToAssign);//这个方法会给成员变量mFirstTouchTarget赋值
		...
	}//这是2235行
	
	if (mFirstTouchTarget == null) {//这是2238行
		// No touch targets so treat this as an ordinary view.
		handled = dispatchTransformedTouchEvent(ev, canceled, null,
				TouchTarget.ALL_POINTER_IDS);
	} else {
		...
	}
		
}

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,//这是2511行
            View child, int desiredPointerIdBits) {
	...
	handled = super.dispatchTouchEvent(event);//这是2547
	...
}//这是2581行

前面讲过,onInterceptTouchEvent如果返回true,会给变量intercepted赋值.

接着在2133行判断中为假,跳过里面派发给子View的代码.

没子View什么事,这时mFirstTouchTarget也会为null

然后在经过2238的判断里,进入dispatchTransformedTouchEvent方法

这个方法里面又会调用到父类,既View的dispatchTouchEvent方法,

public boolean dispatchTouchEvent(MotionEvent event) {
	...
	boolean result = false;
	...
            if (!result && onTouchEvent(event)) {//9294行
                result = true;
            }//9296行
	...
	return result;
}

里面就会执行熟悉的onTouchEvent方法.

我曾经想着事件都留到这了,这方法应该不需要返回true.事实

事实是假设返回false还是会让事件最终又回到MainActivity的onTouchEvent方法里.

毕竟全部方法都遵循返回false表示不拦截,事件继续往下一层流转.返回true表示在这里处理,不分发.

关于setOnTouchListener和setOnClickListener是在什么时候调用?他们的优先级是怎么样?

public boolean dispatchTouchEvent(MotionEvent event) {
	...
	boolean result = false;
	...
	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;
		}
	}
	...
	return result;
}

View类里面有这么一个判断。

li.mOnTouchListener!=null && 中间省略 && li.mOnTouchListener.onTouch(this, event)

所以假设我们给某个控件执行了setOnTouchListener方法,就会在这里执行onTouch方法。

并且重要的一点是,如果onTouch方法中返回true。result置为true,就不会进入onTouchEvent方法了。

ps:如果是ViewGroup,则是在dispatchTransformedTouchEvent中会调用super.onDispatchTouchEvent,同样会进入到这个流程。

public boolean onTouchEvent(MotionEvent event) {
	...
	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:
				...
					performClick();
				...
				break;

			case MotionEvent.ACTION_DOWN:
				...
				break;

			case MotionEvent.ACTION_CANCEL:
				...
				break;

			case MotionEvent.ACTION_MOVE:
				...
				break;
		}

		return true;
	}

	return false;
}

在View的onTouchEvent中,里面会判断这个View是不是CLICKABLE和LONG_CLICKABLE。Button这种默认CLICKABLE,TextView默认不是CLICKABLE。所有的View都不是LONG_CLICKABLE的。

然后我们可以通过xml属性或者java代码,或者更直接的,一旦调用了setOnClickListener或setOnLongClickListener方法,就会把对应的这两个CLICKABLE和LONG_CLICKABLE设为true。

进入这个判断里面,里面会根据各种传来MotinEvent事件进行判断,如果符合判断就会调用performClick方法,里面有执行mOnClickListener.onClick这个回调的方法。

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

ps:无意中发现有一个View的onTouch方法中有一个TouchDelegate,人如其名,是用来给View设置一个点击代理的。代理返回true的话,就会跳过下面的逻辑

所以得出OnTouchListener的优先级是高于onTouchEvent方法的,自然也就高于onClick,onLongClick这些方法。

对于View.onTouchEvent,如果自己重写这个方法,那么给他们设置的onClickListener和onLongClickListener监听单击和长按事件就作废了。

想要两者兼得,请根据业务逻辑好好构思怎么写。

ps:

View中有一个很重要的方法requestDisallowInterceptTouchEvent,可以在子控件中控制父控件是否拦截。一般用法如下

if (getCurrentItem() != 0) {
	getParent().requestDisallowInterceptTouchEvent(true);// 用getParent去请求,// 不拦截
} else {// 如果是第一个页面,需要显示侧边栏, 请求父控件拦截
	getParent().requestDisallowInterceptTouchEvent(false);// 拦截
}

猜你喜欢

转载自blog.csdn.net/alcoholdi/article/details/53871197