Android 事件分发机制

前言

我们都知道给一个控件注册点击事件是只需要setOnClickListener,传入一个实现了onClick方法的匿名内部类,注册触摸事件只需要setOnTouchListener,传入一个实现了onTouch方法的匿名内部类,但是你会发现onTouch不同于onClick的一点是它是具有返回值的,这个返回值有什么用呢,onTouch和onClick方法谁先被调用呢?他们都是在那个函数中被调用的呢?下面我们就来看一看!

基础

//
button.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                System.out.println("onTouch");
                return false;
            }
        });
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                System.out.println("onClick");
            }
        });
//
 09:37:30.432 6758-6758/com.example.administrator.mydemo I/System.out: onTouch
 09:37:30.538 6758-6758/com.example.administrator.mydemo I/System.out: onTouch
 09:37:30.541 6758-6758/com.example.administrator.mydemo I/System.out: onTouch
 09:37:30.548 6758-6758/com.example.administrator.mydemo I/System.out: onClick

 

我们可以看到onTouch是先于onClick被调

用的,那么我们在手都抛一个异常来看一下他的函数调用栈

//
at com.example.administrator.mydemo.MainActivity$1.onTouch(MainActivity.java:29)
at android.view.View.dispatchTouchEvent(View.java:11730)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2963)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2593)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2963)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2593)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2963)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2593)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2963)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2593)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2963)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2593)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2963)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2593)
at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:448)
at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1828)
at android.app.Activity.dispatchTouchEvent(Activity.java:3360)
//

这个只是截取了一部分,我们只看到追溯到Activity即可,所以对于onTouch方法,它是这样被调用的 … … -> Activity.dispatchTouchEvent -> PhoneWindow.superDispatchTouchEvent -> DecorView.superDisparchTouchEvent -> ViewGroup.dispatchTouchEvent – > ViewGroup.dispatchTransformedTouchEvent -> View.dispatchTouchEvent 那我们就可以按照这个顺序来看一看正常情况下的事件流的流向和相关函数代码

同时我们也可以看出对Touch事件它是由Activty->ViewGroup->View三层结构由外到内,Android5.0后代码变得更复杂了,但是原理没有变,我们就看一看便于理解的老代码

在开始之前为了第一次接触的人便于理解,我就简单的把情况设置为这个Activity中只有一个Layout,Layout中只有一个Button,所以只有一个Activty,一个ViewGroup,一个View,但是你要明白的是在安卓里ViewGroup也是一个View,View拥有dispatch方法,而ViewGroup作为它的子类重写了该方法,在后面提到时你要区分它调用的是作为Layout(ViewGroup)的dispatch方法,还是Layout作为一个View的dispatch方法!!!另外你还要区分ViewGroup是View的子类,Button是ViewGroup的子view

Acitivty

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

onUserInteraction是一个空方法,用户可以去overwrite,来实现自己的需求,这里我们就直接跳过,按照上面的函数栈默认情况下是没有调用onTouchEvent的,那一定是上面的if成立,我们就来看一看if的判断条件

getWindow()Window是抽象

类,其唯一实现为PhoneWindow,所以在上面的函数栈出现了PhoneWindow.superDispatchTouchEvent

PhoeWindow

 //
 public boolean superDispatchTouchEvent(MotionEvent event) {

        return mDecor.superDispatchTouchEvent(event);
 }
//

DecorView是PW类的一个内部类

DecorView

//
public boolean superDispatchTouchEvent(MotionEvent event) {

        return super.dispatchTouchEvent(event);
}
//

DecorView是PW类的一个内部类

,DecorView继承于FrameLayout,FrameLayout并没有重写该方法,所以调用了它的父类ViewGroup的dispatch方法

下面我们就正式进入了ViewGroup的dispatch方法

ViewGroup

每次调用dispatch方法时都会进行一个判断

//
if (disallowIntercept || !onInterceptTouchEvent(ev)) { 
不拦截...  ...
} 
拦截...
//

disallowIntercept  是否禁用事件拦截 默认为false

onInterceptTouchEvent(ev)

//
 public boolean onInterceptTouchEvent(MotionEvent ev) {  

    return false;

  }
//

默认情况下return false,也就是不拦截,可以通过手动overwrite实现拦截;综上在默认条件下会已进入if代码块,也就是将事件继续向下传递,我们来看一下不拦截的代码

//
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,进入if内部
        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;  

            if (child.dispatchTouchEvent(ev))  { //调用了子view的dispatch方法

                 mMotionTarget = child;  
                 return true; //是否返回true取决于子view的dispatch返回值
                       
            }  
        }  
    }  
}
... ...  
//

子view就是Button,而Button继承于TextView,但他们都没有overwrite dispatch方法,所以直接就追溯到了View.dispatchTouchEvent方法在

PS:在ViewGroup内还有一个dispatchTransformedTouchEvent,它的作用是将坐标转换为子view的坐标

View

//
public boolean dispatchTouchEvent(MotionEvent event) {  

    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
          mOnTouchListener.onTouch(this, event)) {  
          return true;  
    } 
    return onTouchEvent(event);  
}
//

令人激动!我们终于找到了注册的onTouch方法,Touch事件是经过层层传递,最后在Button的dispatch(继承于View)的方法内被处理了,我们在来看一下它这个if的判断条件

mOnTouchListener 只要你setListner就一定为true

(mViewFlags & ENABLED_MASK) == ENABLED   该控件可点击则为 true

mOnTouchListener.onTouch(this, event)  这个就是overwrite onTouch方法时的返回值,默认是false,所以该view的返回值就是onTouchEvent的返回值,也可以说onTouchEvent是否能执行,取决于onTouch的返回值

既然默认if条件不成立,就一定会执行这个button的onTouchEvent(),那么一直为从露面的onClick方法一定就在这里

View.onTouchEvent

//
public boolean onTouchEvent(MotionEvent event) {  
    ...  ...
    if (((viewFlags & CLICKABLE) == CLICKABLE ||  
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  

           switch (event.getAction()) { 
           //抬起手指,完成点击
                 case MotionEvent.ACTION_UP:  
                        boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
                            ... ...
                        performClick();  
                        break;  
                   ... ...
           }  
           // 若该控件可点击,就一定返回true
           return true;  
     }  
 // 若该控件不可点击,就一定返回false
 return false;  
}
    
//

该方法返回值取决于if判断,如果可点击进入if执行相关操作,返回true,否则跳过if返回发生了,对于我设定的情况button肯定是可点击的,进入if,对action进行switch判断,如果为up也就是抬起手指,调用了pe

rformClick方法

performClick

//
public boolean performClick() {  
        if (mOnClickListener != null) {  
            playSoundEffect(SoundEffectConstants.CLICK);  
            mOnClickListener.onClick(this);  
            return true;  
        }  
        return false;  
    } 
//

激动!我们终于发现注册的onClick方法了!是在Button的onTouchEvent中的performClick中被调用的;另外只要set了Listener返回值就为true,但是在onTouchEvent中没有对其做判断,它的返回值并不起什么作用

返回

现在我们已经找到最内层函数,现在要开始一层层向上返回,

当执行完performClick后

,Button的onTouchEvent方法返回了true

Button的dispatch方法return onTouchEvent 也返回了true

ViewGroup中的 if (child.dispatchTouchEvent(ev))条件成立,ViewGroup.dispatch也返回了true

DecorView的dispatch 返回super.dispatchTouchEvent(event) 即ViewGroup.dispatch 也返回true

PhoneWindow的dispatch 返回mDeco

r.superDispatchTouchEvent(event) 也返回true

Activity的dispatch if条件成立,也返回true,到此事件分发结束

我们可以把DecorView和PhoneWindow忽略掉,实际上他们并没有作什么有用的操作,所以实际事件流只经过了三层Activity->ViewGroup->View,最后层层返回

复杂

(一)

我们从最后开始向前找第一个出现分支的地方,在View的onTouchEvent方法

//
//
public boolean onTouchEvent(MotionEvent event) {  
    ...  ...
    if (((viewFlags & CLICKABLE) == CLICKABLE ||  
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
    ...  ...
           // 若该控件可点击,就一定返回true
           return true;  
     }  
 // 若该控件不可点击,就一定返回false
 return false;  
}
//

假如这个Button你将Clickable设置为false,该方法将返回false,performClick方法将不会被调用,同时对于View.dispatch方法也将返回fals

e

//
public boolean dispatchTouchEvent(MotionEvent event) {  

    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
          mOnTouchListener.onTouch(this, event)) {  
          return true;  
    } 
    return onTouchEvent(event);  
}
//

在ViewGroup内if条件也不再成立

//
 if (child.dispatchTouchEvent(ev))  { //调用了子view的dispatch方法
       mMotionTarget = child;  
       return true; //是否返回true取决于子view的dispatch返回值                   
 }  
//

我们来看一下ViewGroup内,该if不成立它所执行

的代码

//
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  
                    (action == MotionEvent.ACTION_CANCEL);  
            if (isUpOrCancel) {  
                mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
            }  
            final View target = mMotionTarget;  

        if (target == null) {  
         ... ...
            return super.dispatchTouchEvent(ev);
        } 
//

如果target为null也就是点击空白或者没有子view处理

事件,ViewGroup将调用super.dispatchTouchEvent,注意ViewGroup的父类是View,也就是说现在这个Layout将不再作为一个ViewGroup,而是作为一个View,它自身也可以被Click被Touch

而对于一个View的dispatch方法我们在上面已经讲过了,他会先执行onTouch,如果返回false即该事件没有被消费掉,就会执行onTouchEvent,进一步执行onClick,情况就类似于上面我们所分析的那样

(二)

另一个出现分支的地方,ViewGroup的onInterceptTouchEvent,假如我们让他成立,即拦截事件,不会进入if,但是它实际上也是执行这段代码,也就是说,如果没有子view处理事件,或者在dispatch时就被拦截,他都会执行这段代码,以一个View而不是ViewGroup的身份来调用dispatch

//
//
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  
                    (action == MotionEvent.ACTION_CANCEL);  
            if (isUpOrCancel) {  
                mGroupFlags &= ~FLA
G_DISALLOW_INTERCEPT;  
            }  
            final View target = mMotionTarget;  

        if (target == null) {  
         ... ...
            return super.dispatchTouchEvent(ev);
} 
//

 

我们来看一下假如没有注册任何listener,没有任何拦截它会是什么样的一个流程

我们可以把虚线部分忽略掉,实际函数的执行顺序是一个U型,先下后上,任何一个方法返回true,事件都会被消费,任何一个方法返回false,最终都会执行到上一层view的onTouchEvent方法

这篇文章先讲到这里,有空再更新

 

Android 事件分发机制》有89个想法

匿名进行回复 取消回复

邮箱地址不会被公开。 必填项已用*标注