上一篇文章分析了input event怎么传递到activity中,下面接着分析在activity中的具体传递,也是event事件传递最重要的地方。
接上回,先看看activity中收到event后:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction(); //这里是空函数,什么都不干
}
if (getWindow().superDispatchTouchEvent(ev)) { //这里传给phoneWindow
return true;
}
return onTouchEvent(ev);
}
所以会传递到phoneWindow的superDispatchTouchEvent()方法,这里可以看出,如果phoneWindow把传递的事件down, move,up拦截了,即返回了true,那么activity的onTouchEvent()将不会获得到这个事件,相反如果phoneWindow不处理这个事件,那么这个事件都会传给activity,同时也知道activity的onTouchEvent()也是最后处理event事件的,可以看下面phoneWindow代码的分析。
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event); //phoneWindow的内部类DecorView
}
事件传递给DecorView:
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Callback cb = getCallback();
return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
: super.dispatchTouchEvent(ev);
}
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event); //这里传给了DecorView父亲,即ViewGroup
}
}
由于DecorView本身是一个FrameLayout,FrameLayout 继承于ViewGroup,那么事件最终传递到ViewGroup里面:
public boolean dispatchTouchEvent(MotionEvent ev){
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
//一个down事件到来,就会认为是一个新的touch事件到来,
//所以如果mFirstTouchTarget != null,就会给child view传递cancel事件
cancelAndClearTouchTargets(ev);
resetTouchState(); //设置mFirstTouchTarget == null
}
final boolean intercepted;
//只有在down事件下才进行判断是否拦截 ,
//mFirstTouchTarget != null 表示有child view要处理事件
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) { //默认viewgroup都是能拦截事件的
intercepted = onInterceptTouchEvent(ev);
//判断当前ViewGroup有没有拦截触摸事件, 注意拦截不代表消费,
//比如若一个viewGroup在这里onInterceptTouchEvent的down事件返回了ture,
//就会后面调用它自己的onTouchEvent(),看看自己是否消费此事件。
//这里还有一种情况,不是down事件,比如move事件,而viewgroup的child view
//消费了down事件,viewgroup中途对事件进行了拦截move事件,这里置intercepted==true,
//还是会导致它的child view收不到以后的事件
} 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; //如果不是down事件,
//且mFirstTouchTarget == null,即没有child View 对前面的down返回true,
//则直接设置intercepted = true,
//注意最顶层的View是PhoneWindow的DecorView,
//所以如果它的child view没有把事件拦截了,
//则以后所有的move,up事件都不会传给它下面的child View了,
//这样activity里的view group或view,都有机会得到down事件,
//但是如果不消费down事件,则以后无法得到后续的事件,
//因为activity的最顶层DecorView就把事件给拦截了。
}
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
//如果此VierGruop 没有拦截了down事件,事件也不是cancel事件
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) { //如果是down事件
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) { //如果child view个数不是0
//x , y是down事件相对于此viewgroup的点击位置
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) { //循环遍历child view
//判断点击的位置是否在此child View上
//canViewReceivePointerEvents是判断view是否可以接受点击事件,
//只有visible和动画中的view可以接受点击事件,
//isTransformedTouchPointInView是对点击的x,y换算,
//判断点击事件是否在此child view上
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue; //如果没有点击在此child view上,则进入下一次循环
}
//这里是最关键的方法,把down事件传递给当前被点击的child View ,
//注意这里必须是此有child View的的子view在dispatchTouchEvent,
//OnTouchListener.onTouch或onTouchEvent(event)返回了true,
//那么dispatchTransformedTouchEvent方法才会返回true,后面会单独分析它
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//这里设置mFirstTouchTarget = child
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
}
}
//当前viewGroup的child view都不处理这次down事件,那么mFirstTouchTarget == null
//不处理是指其Child View 的OnTouchListener.onTouch
//或onTouchEvent(event)返回了flase 或 调用的super方法
if (mFirstTouchTarget == null) {
//这里又传递给了dispatchTransformedTouchEvent方法,但是注意这里第三个参数,传递了null,
//第三个参数表示child view,传递null,其实就是传递给viewgroup自己,看看自己有没有处理。
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//如果有child View处理点击事件,则down , move , up都进入这里
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget; // mFirstTouchTarget 就是被点中的child view
while (target != null) {
final TouchTarget next = target.next; //这里单指测试时next总为null
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true; //如果是down 事件,已经传递过,就会进入这里
} else {
//如果move事件突然被viewgroup拦截了,即intercepted==true,则导致cancelChild==true
final boolean cancelChild = resetCancelNextUpFlag(target.child)||intercepted;
// 非down 事件会进入这里, 又递归传递了,
//这里每个被传递过down事件的ViewGroup的mFirstTouchTarget都不为null,
//这样递归传递直到非down事件被传到最终被点击的View上
//注意这里面如果整个activity的child view 返回了false,
//则 down 和 up 事件又会传递给activity 的 onTouchEvent()
//注意如果是viewgroup中途拦截了move或up事件,这里会给child view传递一个cancel事件,
//并且后面会设置mFirstTouchTarget = null,这样后续事件将不会传递给child view
if (dispatchTransformedTouchEvent(ev, cancelChild,target.child,
target.pointerIdBits)) {
handled = true;
}
if (cancelChild) { //如果取消传递给child view
if (predecessor == null) {
//循环直到设置mFirstTouchTarget == null,
//那么下次事件再到来时就不会传递给child view了
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
return handled;
}
下面看看上面函数里出现过三次的dispatchTransformedTouchEvent()方法,这个方法是实现传递的关键:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
//上面dispatchTouchEvent函数第三次调用本函数时,
//若viewgroup中途拦截了事件时,cancel 会为true
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL); //把事件设为取消事件
if (child == null) {
//调用到了自己父类,因为viewgroup继承于view,最终调用到view的dispatchTouchEvent
handled = super.dispatchTouchEvent(event);
} else {//child不为空
//传递给child view上,这里实现了递归一级级传递
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) { //手指触摸数没有变化
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
//上面dispatchTouchEvent函数第二次调用本函数时,即没用child处理down事件时,
//传下来的child为null,这就实现了事件在viewgroup中自己本身的传递
//调用自己父类,因为viewgroup继承于view,最终调用view的dispatchTouchEvent
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
//传递给child view上,这里实现了递归一级级传递,
//上面dispatchTouchEvent函数第一次调用本函数时,走这里
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// Done.
transformedEvent.recycle();
return handled;
}
由上面可知事件最终都会传递到view上,并首先传递到view的dispatchTouchEvent上:
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
ListenerInfo li = mListenerInfo;
//先会调用OnTouchListener的监听方法,看看onTouch方法是否返回true
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//OnTouchListener返回了true,这里就不会再调用onTouchEvent()方法了
if (!result && onTouchEvent(event)) {
result = true;
}
}
public boolean onTouchEvent(MotionEvent event) {
switch (action) {
case MotionEvent.ACTION_UP:
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick(); //执行onclick方法
}
break
}
}
public boolean performClick() {
playSoundEffect(SoundEffectConstants.CLICK);//点击声音
li.mOnClickListener.onClick(this); //执行onClickListener 点击回调
}
到此input event事件源码分析就完了,可以看出源码实现了一个精妙,但也很复杂的事件传递逻辑。
总结
event 事件传递流程:
phoneWindow —> Activity.dispatchTouchEvent() —> DecorView —> contentLayout(系统默认加载的activity父控件) —> activity layout —> ViewGroup —> View —> Activity.onTouchEvent()
event事件传递的函数流程:
dispatchTouchEven() —> onInterceptTouchEvent() (ViewGroup才有此方法) —> OnTouchListener.onTouch() –> onTouchEvent() —> OnClickListener.onClick()
注意:若是OnTouchListener.onTouch() 对事件返回了true,则事件不会走后面onTouchEvent和onClick。
down事件永远会从DecorView 一级级传递到被点击的view上,但若是在这个传递过程中,没有DecorView 下的child view对这个down事件消费,则后续的move, up事件都不会传给这些child view了。但是activity自己能拿到这些事件。
down事件传递过程中,如果某个ViewGroup对事件在onInterceptTouchEvent()中进行了拦截,则事件不会传递给它的child view,但是如果此ViewGroup 不在它自己onTouchEvent()对事件进行消费,DecorView 还是会认为没有child view消费触摸事件。
调用super方法或return false 都是不消费事件,只有return true才算消费事件。
如果child view消费了down事件后,在后续的move事件中被它的父类拦截了事件,则这个child view会收到一个cancel事件。后续事件就只会传递给它的父类。
完
更多精彩Android技术可以关注我们的微信公众号,扫一扫下方的二维码或搜索关注公共号:
Android老鸟