一、基础知识
1.分发对象
事件:Touch事件相关细节(发生触摸的位置、时间、历史记录、手势动作等)被封装成MotionEvent对象
2.事件
主要发生的Touch事件大致分为以下四种:
-
MotionEvent.ACTION_DOWN:按下事件(所有事件的开始)
-
MotionEvent.ACTION_MOVE:滑动事件
-
MotionEvent.ACTION_CANCEL:非人为原因结束本次事件
-
MotionEvent.ACTION_UP:抬起事件(与DOWN对应)
3.大体流程
4.主要方法
5.传递对象
一个点击事件产生后,传递顺序是:Activity(Window) -> ViewGroup -> View
二、流程介绍
文字叙述太繁琐,图更好理解
主要方法
主体流程
三、源码分析
1.Activity的事件分发
当一个点击事件发生时,事件最先传到Activity的dispatchTouchEvent()进行事件分发
public boolean dispatchTouchEvent(MotionEvent ev) {
//一般事件列开始都是DOWN,所以这里基本是true
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//关注点1
onUserInteraction();
}
//关注点2
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//关注点3
return onTouchEvent(ev);
}
关注点1
onUserInteraction:每当Key,Touch,Trackball事件分发到当前Activity就会被调用。如果你想当你的Activity在运行的时候,能够得知用户正在与你的设备交互,你可以override该方法。
关注点2
getWindow()可以得到一个Window对象,Window类是抽象类,且PhoneWindow是Window类的唯一实现类
superDispatchTouchEvent(ev)是抽象方法,通过PhoneWindow类中看一下superDispatchTouchEvent()的作用
@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
//mDecor是DecorView的实例
//DecorView是视图的顶层view,继承自FrameLayout,是所有界面的父类
}
接下来看mDecor.superDispatchTouchEvent(event):
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
//DecorView继承自FrameLayout
//那么它的父类就是ViewGroup,而super.dispatchTouchEvent(event)方法,其实就应该是ViewGroup的dispatchTouchEvent()
}
从这开始ViewGroup的事件分发了
关注点3
当viewGroup分发事件失败,Activity将会自己处理
2.ViewGroup的事件分发
ViewGroup的dispatchTouchEvent()源码分析,该方法比较复杂,截取几个重要的逻辑片段进行介绍,来解析整个分发流程。
// 发生ACTION_DOWN事件或者已经发生过ACTION_DOWN,并且将mFirstTouchTarget赋值,才进入此区域,主要功能是拦截器
final boolean intercepted;
if(actionMasked==MotionEvent.ACTION_DOWN||mFirstTouchTarget!=null){
//disallowIntercept:是否禁用事件拦截的功能(默认是false),即不禁用
//可以在子View通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改,不让该View拦截事件
final boolean disallowIntercept=(mGroupFlags&FLAG_DISALLOW_INTERCEPT)!=0;
//默认情况下会进入该方法
if(!disallowIntercept){
//调用拦截方法
intercepted=onInterceptTouchEvent(ev);
ev.setAction(action);
}else{
intercepted=false;
}
}else{
// 当没有触摸targets,且不是down事件时,开始持续拦截触摸。
intercepted=true;
}
这一段的内容主要是为判断是否拦截。如果当前事件的MotionEvent.ACTION_DOWN,则进入判断,调用ViewGroup onInterceptTouchEvent()方法的值,判断是否拦截。如果mFirstTouchTarget != null,即已经发生过MotionEvent.ACTION_DOWN,并且该事件已经有ViewGroup的子View进行处理了,那么也进入判断,调用ViewGroup onInterceptTouchEvent()方法的值,判断是否拦截。如果不是以上两种情况,即已经是MOVE或UP事件了,并且之前的事件没有对象进行处理,则设置成true,开始拦截接下来的所有事件。这也就解释了如果子View的onTouchEvent()方法返回false,那么接下来的一些列事件都不会交给他处理。如果VieGroup的onInterceptTouchEvent()第一次执行为true,则mFirstTouchTarget = null,则也会使得接下来不会调用onInterceptTouchEvent(),直接将拦截设置为true。
当ViewGroup不拦截事件的时候,事件会向下分发交由它的子View或ViewGroup进行处理。
/* 从最底层的父视图开始遍历, ** 找寻newTouchTarget,即上面的mFirstTouchTarget ** 如果已经存在找寻newTouchTarget,说明正在接收触摸事件,则跳出循环。 */
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 (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
//如果view不可见,或者触摸的坐标点不在view的范围内,则跳过本次循环
if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
// 已经开始接收触摸事件,并退出整个循环。
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
//如果触摸位置在child的区域内,则把事件分发给子View或ViewGroup
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 获取TouchDown的时间点
mLastTouchDownTime = ev.getDownTime();
// 获取TouchDown的Index
if (preorderedList != null) {
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
//获取TouchDown的x,y坐标
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//添加TouchTarget,则mFirstTouchTarget != null
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//表示以及分发给NewTouchTarget
alreadyDispatchedToNewTouchTarget = true;
break;
}
dispatchTransformedTouchEvent()方法实际就是调用子元素的dispatchTouchEvent()方法。 其中dispatchTransformedTouchEvent()方法的重要逻辑如下:
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
由于其中传递的child不为空,所以就会调用子元素的dispatchTouchEvent()。 如果子元素的dispatchTouchEvent()方法返回true,那么mFirstTouchTarget就会被赋值,同时跳出for循环。
//添加TouchTarget,则mFirstTouchTarget != null。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//表示以及分发给NewTouchTarget
alreadyDispatchedToNewTouchTarget = true;
其中在addTouchTarget(child, idBitsToAssign);内部完成mFirstTouchTarget被赋值。 如果mFirstTouchTarget为空,将会让ViewGroup默认拦截所有操作。 如果遍历所有子View或ViewGroup,都没有消费事件。ViewGroup会自己处理事件。
3.View的事件分发
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
第一个条件:mOnTouchListener!= null
//mOnTouchListener是在View类下setOnTouchListener方法里赋值的
public void setOnTouchListener(OnTouchListener l) {
//即只要我们给控件注册了Touch事件,mOnTouchListener就一定被赋值(不为空)
mOnTouchListener = l;
}
第二个条件:(mViewFlags & ENABLED_MASK) == ENABLED
-
该条件是判断当前点击的控件是否enable
-
由于很多View默认是enable的,因此该条件恒定为true
第三个条件:mOnTouchListener.onTouch(this, event)
-
如果在onTouch方法返回true,就会让上述三个条件全部成立,从而整个方法直接返回true。
-
如果在onTouch方法里返回false,就会去执行onTouchEvent(event)方法。
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
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));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//如果该控件是可以点击的就会进入到下两行的switch判断中去;
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
//如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
// 在经过重重判断之后,会执行到performClick()方法。
if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (!mHasPerformedLongPress) {
removeLongPressCallback();
if (!focusTaken) {
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)) {
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPrivateFlags |= PREPRESSED;
mHasPerformedLongPress = false;
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();
int slop = mTouchSlop;
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
removeLongPressCallback();
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
//如果该控件是可以点击的,就一定会返回true
return true;
}
//如果该控件是不可以点击的,就一定会返回false
return false;
}
performClick()的源码分析
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}
只要mOnClickListener不为null,就会去调用onClick方法;
那么,mOnClickListener又是在哪里赋值的呢?
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
mOnClickListener = l;
}
四、总结
android事件产生后的传递过程是从Activity--->Window--->View的,即隧道式传递,而View又分为不包含子 View的View以及包含子View的ViewGroup,事件产生之后首先传递到Activity上面,而Activity接着会传递到 PhoneWindow上,PhoneWindow会传递给RootView,而RootView其实就是DecorView了,接下来便是从 DecorView到View上的分发过程了,具体就可以分成ViewGroup和View的分发两种情况了;
对于ViewGroup而言,当事件分发到当前ViewGroup上面的时候,首先会调用他的dispatchTouchEvent方法,在 dispatchTouchEvent方法里面会调用onInterceptTouchEvent来判断是否要拦截当前事件,如果要拦截的话,就会调用 ViewGroup自己的onTouchEvent方法了,如果onInterceptTouchEvent返回false的话表示不拦截当前事件,那么 事件将会继续往当前ViewGroup的子View上面传递了,如果他的子View是ViewGroup的话,则重复ViewGroup事件分发过程,如 果子View就是View的话,则转到下面的View分发过程;
对于View而言,事件传递过来首先当然也是执行他的dispatchTouchEvent方法了,如果我们为当前View设置了 onTouchListener监听器的话,首先就会执行他的回调方法onTouch了,这个方法的返回值将决定事件是否要继续传递下去了,如果返回 false的话,表示事件没有被消费,还会继续传递下去,如果返回true的话,表示事件已经被消费了,不再需要向下传递了;如果返回false,那么将 会执行当前View的onTouchEvent方法,如果我们为当前View设置了onLongClickListener监听器的话,则首先会执行他的 回调方法onLongClick,和onTouch方法类似,如果该方法返回true表示事件被消费,不会继续向下传递,返回false的话,事件会继续 向下传递,为了分析,我们假定返回false,如果我们设置了onClickListener监听器的话,则会执行他的回调方法onClick,该方法是 没有返回值的,所以也是我们事件分发机制中最后执行的方法了;可以注意到的一点就是只要你的当前View是clickable或者 longclickable的,View的onTouchEvent方法默认都会返回true,也就是说对于事件传递到View上来说,系统默认是由 View来消费事件的,但是ViewGroup就不是这样了;
上面的事件分发过程只是正常情况下的,如果有这样一种情况,比如事件传递到最里层的View之后,调用该View的oonTouchEvent方法返回了 false,那么这时候事件将通过冒泡式的方式向他的父View传递,调用它父View的onTouchEvent方法,如果正好他的父View的 onTouchEvent方法也返回false的话,这个时候事件最终将会传递到Activity的onTouchEvent方法了,也就是最终就只能由 Activity自己来处理了;
事件分发机制需要注意的几点:
(1):如果说除Activity之外的View都没有消费掉DOWN事件的话,那么事件将不再会传递到Activity里面的子View了,将直接由Activity自己调用自己的onTouchEvent方法来处理了;
(2):一旦一个ViewGroup决定拦截事件,那么这个事件序列剩余的部分将不再会由该ViewGroup的子View去处理了,即事件将在此 ViewGroup层停止向下传递,同时随后的事件序列将不再会调用onInterceptTouchEvent方法了;
(3):如果一个View开始处理事件但是没有消费掉DOWN事件,那么这个事件序列随后的事件将不再由该View来处理,通俗点讲就是你自己没能力就别瞎BB,要不以后的事件就都不给你了;
(4):View的onTouchEvent方法是否执行是和他的onTouchListener回调方法onTouch的返回值息息相关 的,onTouch返回true,onTouchEvent方法不执行;onTouch返回false,onTouchEvent方法执行,因为 onTouchEvent里面会执行onClick,所以造成了onClick是否执行和onTouch的返回值有了关系;