1.事件分发的顺序
即事件传递的顺序:Activity->ViewGroup->View
事件分发过程由哪些方法协作完成
答:dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()
dispatchTouchEvent():分发点击事件,当点击事件能够传递给当前View,该方法就会调用
onTouchEvent():处理点击事件,在dispatchTouchEvent()内部调用
onInterceptTouchEvent():判断是否拦截了某个事件,只存在于ViewGroup中,普通的View无法使用,在ViewGroup的dispatchTouchEvent()内部调用
本着知其然,知其所以然的追求,我从源码分析,一步一步解开谜团
源码分析
Activity.dispatchTouchEvent()
Activity.dispatchTouchEvent里会执行getWindow().superDispatchTouchEvent()方法,如果此方法为true,说明ViewGroup/子View有消费DOWN事件,那么dispatchTouchEvent()也返回true,那么流程结束,如果getWindow.superDispatchTouchEvent为false,说明ViewGroup/子View都不消费,那么执行Activity的onTouchEvent(),这也就是说Activity消不消费事件要看ViewGroup/View两位大佬的脸色。
产生的问题
1.getWindow()获取的对象是什么?
2.Activity.onTouchEvent()里面是什么?
Activity.onTouchEvent()
public boolean onTouchEvent(MotionEvent event) {
// 当一个点击事件未被Activity下任何一个View接收 / 处理时
// 应用场景:处理发生在Window边界外的触摸事件
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
// 即 只有在点击事件在Window边界外才会返回true,一般情况都返回false,分析完毕
}
onTouchEvent()并没有做太多的事情,只是进行了边界外的触摸事件判断,不管onTouchEvent()返回true还是false,Activity.dispatchTouchEvent()都会结束事件
getWindow.superDispatchTouchEvent()
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
// mDecor = 顶层View(DecorView)的实例对象
}
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
// 调用父类的方法 = ViewGroup的dispatchTouchEvent()
// 即 将事件传递到ViewGroup去处理,详细请看ViewGroup的事件分发机制
}
首先要说明,getWindow()获取的是Window类的对象,Window类是抽象类,其唯一实现类是PhoneWindow类;所以getWindow()实际获取的是PhoneWindow类对象;然后调用了mDecor.superDispatchTouchEvent(),这里要说明,mDecor是顶层DecorView的实例对象,DecorView类是phoneWindow类的一个内部类,DecorView继承自FrameLayout,是所有界面的父类,FrameLayout是ViewGroup的子类,所以DecorView的间接父类是ViewGroup。在mDecor.superDispatchTouchEvent()中又调用了super.dispatchTouchEvent(),也就是父类ViewGroup的方法,由此可知,事件传递由Activity传递到了ViewGroup中。
产生的问题
1.ViewGroup.dispatchTouchEvent()是什么?
ViewGroup.dispatchTouchEvent()
ViewGroup.dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
... // 仅贴出关键代码
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
// 判断值1:disallowIntercept = 是否禁用事件拦截的功能(默认是false),可通过调用requestDisallowInterceptTouchEvent()修改
// 判断值2: !onInterceptTouchEvent(ev) = 对onInterceptTouchEvent()返回值取反
// a. 若在onInterceptTouchEvent()中返回false(即不拦截事件),就会让第二个值为true,从而进入到条件判断的内部
// b. 若在onInterceptTouchEvent()中返回true(即拦截事件),就会让第二个值为false,从而跳出了这个条件判断
// c. 关于onInterceptTouchEvent() ->>分析1
ev.setAction(MotionEvent.ACTION_DOWN);
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;
final int count = mChildrenCount;
// 通过for循环,遍历了当前ViewGroup下的所有子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是不是正在点击的View,从而找到当前被点击的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()
// 即 实现了点击事件从ViewGroup到子View的传递(具体请看下面的View事件分发机制)
if (child.dispatchTouchEvent(ev)) {
mMotionTarget = child;
return true;
// 调用子View的dispatchTouchEvent后是有返回值的
// 若该控件可点击,那么点击时,dispatchTouchEvent的返回值必定是true,因此会导致条件判断成立
// 于是给ViewGroup的dispatchTouchEvent()直接返回了true,即直接跳出
// 即把ViewGroup的点击事件拦截掉
}
}
}
}
}
}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
final View target = mMotionTarget;
// 若点击的是空白处(即无任何View接收事件) / 拦截事件(手动复写onInterceptTouchEvent(),从而让其返回true)
if (target == null) {
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
return super.dispatchTouchEvent(ev);
// 调用ViewGroup父类的dispatchTouchEvent(),即View.dispatchTouchEvent()
// 因此会执行ViewGroup的onTouch() ->> onTouchEvent() ->> performClick() ->> onClick(),即自己处理该事件,事件不会往下传递(具体请参考View事件的分发机制中的View.dispatchTouchEvent())
// 此处需与上面区别:子View的dispatchTouchEvent()
}
...
}
/**
作用:是否拦截事件
说明:
a. 返回true = 拦截,即事件停止往下传递(需手动设置,即复写onInterceptTouchEvent(),从而让其返回true)
b. 返回false = 不拦截(默认)
*/
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
在ViewGroup.dispatchTouchEvent()中,需要调用onInterceptTouchEvent()来询问是否拦截事件,首先要说明,onInterceptTouchEvent()是ViewGroup特有的方法,普通的View是没有的。从源码可知onInterceptTouchEvent()默认是返回false的,也就是说默认是不拦截的。程序继续执行,会遍历当前ViewGroup的所有子View,判断是否存在当前点击的子View,如果存在会执行child.dispatchTouchEvent()方法,也就是子View.dispatchTouchEvent(),这也就是事件分发从ViewGroup向View的传递;如果child.dispatchTouchEvent()返回true,那么ViewGroup.dispatchTouchEvent()返回true;如果child.dispatchTouchEvent()返回false,说明不消费,ViewGroup执行super.dispatchTouchEvent(),也就是View.dispatchTouchEvent(),实际在super.dispatchTouchEvent中会执行onTouch->onTouchEvent->onClick,下面会分析。由此可知,ViewGroup是否可以消费事件,还要看子View这位大佬的脸色啊。
产生的问题
1.ViewGroup调用的super.dispatchTouchEvent(),也就是View.dispatchTouchEvent()是什么?
View.dispatchTouchEvent()
/**
*/
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
// 说明:只有以下3个条件都为真,dispatchTouchEvent()才返回true;否则执行onTouchEvent()
// 1. mOnTouchListener != null
// 2. (mViewFlags & ENABLED_MASK) == ENABLED
// 3. mOnTouchListener.onTouch(this, event)
// 下面对这3个条件逐个分析
/**
条件1:mOnTouchListener != null
说明:mOnTouchListener变量在View.setOnTouchListener()方法里赋值
*/
public void setOnTouchListener(OnTouchListener l) {
mOnTouchListener = l;
// 即只要我们给控件注册了Touch事件,mOnTouchListener就一定被赋值(不为空)
}
/**
条件2:(mViewFlags & ENABLED_MASK) == ENABLED
说明:
a. 该条件是判断当前点击的控件是否enable
b. 由于很多View默认enable,故该条件恒定为true
*/
/**
条件3:mOnTouchListener.onTouch(this, event)
说明:即 回调控件注册Touch事件时的onTouch();需手动复写设置,具体如下(以按钮Button为例)
*/
button.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return false;
}
});
// 若在onTouch()返回true,就会让上述三个条件全部成立,从而使得View.dispatchTouchEvent()直接返回true,事件分发结束
// 若在onTouch()返回false,就会使得上述三个条件不全部成立,从而使得View.dispatchTouchEvent()中跳出If,执行onTouchEvent(event)
在View.dispatchTouchEvent()中,首先会执行判断控件是否注册了OnTouchListener,如果没有注册就执行View.onTouchEvent();如果注册了,还要判断onTouch方法里面返回的是true还是false,如果是true不会继续往下执行View.onTouchEvent(),如果是false会继续执行View.onTouchEvent();所以由此看出,控件注册了OnTouchListener监听,并且实现的方法onTouch()返回了true,是不会执行的View.onTouchEvent()的。
产生的问题
1.假如没有注册OnTouchListener监听器或者onTouch()返回了false,执行了View.onTouchEvent(),那么该方法会是什么呢?
View.onTouchEvent()
/**
*/
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
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)) {
switch (event.getAction()) {
// a. 若当前的事件 = 抬起View(主要分析)
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
...// 经过种种判断,此处省略
// 执行performClick() ->>分析1
performClick();
break;
// b. 若当前的事件 = 按下View
case MotionEvent.ACTION_DOWN:
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPrivateFlags |= PREPRESSED;
mHasPerformedLongPress = false;
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
// c. 若当前的事件 = 结束事件(非人为原因)
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
removeTapCallback();
break;
// d. 若当前的事件 = 滑动View
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)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
// Need to switch from pressed to not pressed
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
// 若该控件可点击,就一定返回true
return true;
}
// 若该控件不可点击,就一定返回false
return false;
}
public boolean performClick() {
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
// 只要我们通过setOnClickListener()为控件View注册1个点击事件
// 那么就会给mOnClickListener变量赋值(即不为空)
// 则会往下回调onClick() & performClick()返回true
}
return false;
}
在View.onTouchEvent()中,若控件可点击一定返回true,不可点击就一定返回false,并且在switch语句中执行了performClick(),在performClick()方法中执行onClick方法,由此可以看出整个事件对于是否消费,View的决定权是最大的,如果View不消费会让ViewGroup去决定是否消费,ViewGroup如果不消费再由Activity去决定是否消费。
总结:
1.一个事件后,首页传递给Activity,然后一层一层往下传,从上往下调用dispatchTouchEvent方法传递事件
2.如果事件传递给最下层的View还没有被消费,就会按照反方向传给Activity,从下往上调用onTouchEvent方法,最后回到Activity的onTouchEvent方法,如果Activity也没有消费处理这个事件,这个事件就会被抛弃
3.dispatchTouchEvent方法用于事件的分发,Android中所有的事件都必须经过这个方法的分发,然后决定是自身消费还是继续往下分给子控件处理,返回true表示不继续分发,事件没有被消费;返回false则继续往下分发,如果是ViewGroup则分发给onInterceptTouchEvent方法进行判断是否拦截该事件
4.onTouchEvent方法用于事件的处理,返回true表示消费当前事件,返回false则不处理,交给子控件进行继续分发
5.onInterceptTouchEvent是ViewGroup中才有的方法,普通View是没有的,它的作用是负责事件的拦截,返回true的时候表示拦截当前事件,不继续往下分发,交给自身的onTouchEvent进行处理。返回false则不拦截,继续往下传。这是ViewGroup特有的方法,因为ViewGroup中可能还有子View,而Android子View是不能再包含子View的
6.上层View既可以直接拦截该事件,自己处理,也可以先询问子View,如果子View需要就交给子View处理,如果子View不需要还能继续交给上层View处理,既保证了事件的有序性,又非常的灵活
7.事件由父View传递给子View,ViewGroup可以通过onInterceptTouchEvent方法对事件拦截,停止其向子View传递
8.如果View没有对ACTION_DOWN进行消费,之后的其他事件不会传递过来,也就是ACTION_DOWN必须是true,之后的事件才会传递进来