事件分发(上篇)

上篇:探究View的事件分发(下篇探究ViewGroup的事件分发)

// 我们为一个按钮注册一个点击事件(onClick将会被回调)
button.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        Log.d("TAG", "onClick execute");
    }
});
// 我们再为这个按钮注册一个触摸事件(onTouch将会被回调)
button.setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        Log.d("TAG", "onTouch execute, action " + 
        event.getAction());
        return false; // 这里我们返回false(默认)
    }
});

运行
程序点击按钮,打印结果如下所示:

... onTouch execute, action 0
... onTouch execute, action 1
... onClick execute

根据 Log 可知,onTouch是优先于onClick执行的,并且执行了两次。

一次点击事件是指:从 ACTION_DOWN 到 ACTION_UP (可能还会有多次 ACTION_MOVE 的执行)。

因此事件传递的顺序是先经过onTouch,再传递到onClick

注意到onTouch方法是有返回值的,这里我们返回的是false,
如果我们尝试把onTouch方法里的返回值改成true,再运行一次:

... onTouch execute, action 0
... onTouch execute, action 1

我们发现,onClick方法不再执行了!为什么会这样呢?

我们可以先理解成当onTouch方法返回true时点击事件被onTouch方法消耗掉了,因而不会再继续向下传递。

我们知道,只要点击了某个控件,该控件的dispatchTouchEvent方法就会被调用。

然而控件本身并没有这个方法,于是最终找到其父类View的dispatchTouchEvent方法:

public boolean dispatchTouchEvent(MotionEvent event) {
    if (mOnTouchListener != null 
    && (mViewFlags & ENABLED_MASK) == ENABLED 
    && mOnTouchListener.onTouch(this, event)) {
        return true;
    }
    return onTouchEvent(event); // 否则
} 

在这个方法内,进行了一个if判断,如果这个if判断中的三个条件都为true,就返回true,否则就去执行onTouchEvent方法并返回其值。(爆料:该值由控件是否可点击决定)

很显然onTouch方法总是优先于其他方法被执行。

另外注意:只有当if判断中的前两个条件都为true,onTouch方法才会被执行。

因此如果你有一个控件是disable的,给它注册touch事件,onTouch方法将永远不会被执行。

对于这一类控件,如果想要监听它的touch事件,就必须重写该控件的onTouchEvent方法。

继续分析,先看第一个条件:mOnTouchListener != null。该变量是在哪里被赋值的呢?

public void setOnTouchListener(OnTouchListener l) {
    mOnTouchListener = l;
}

最终我们发现这个变量正是在View的setOnTouchListener方法中被赋值的。

也就是说只要我们给控件注册了touch事件,mOnTouchListener就会被赋值(非空)。

再看第二个条件:(mViewFlags & ENABLED_MASK) == ENABLED。
判断当前点击的控件是否是enable的,按钮默认都是enable的,因此这个条件恒定为true。

再看第三个条件:OnTouchListener.onTouch(this, event)。
关键条件。就是去回调该控件注册touch事件时的onTouch方法。
很显然如果我们在onTouch方法里返回true,就会使全部条件成立,从而使整个方法返回true。
否则就会导致onTouchEvent方法被执行。

以上的分析恰恰验证了:onTouch优先于onClick执行。
并且如果onTouch方法返回true,那么dispatchTouchEvent方法也将直接返回true,
程序不会再继续向下执行,onClick方法也就不会被执行了。

以上的分析还透漏出了:onClick方法的调用肯定是发生在onTouchEvent方法中的!
我们来验证一下。请看onTouchEvent方法:

public boolean onTouchEvent(MotionEvent event) {
    // ...
    // Button默认都是clickable可点击的
    if (((viewFlags & CLICKABLE) == CLICKABLE || 
    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        switch (event.getAction()) {  
            case MotionEvent.ACTION_UP:
                // 经过各种if判断...
                performClick(); 
            break;
            // ...
        }
        return true; // 第89行
    }
    return false; // 不可点击
}

onTouchEvent方法复杂了很多,不过没关系,我们只挑重点看。
首先在第14行:如果该控件是可点击的,就会进入到第16行的switch判断中。
而如果当前的事件是ACTION_UP,则会进入到对应的case之中。
然后经过各种if判断,最终会执行到第38行performClick()方法,于是我们进入到这个方法中查看:

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

可以看到,这里的mOnClickListener只要不为空,就会去调用它的onClick方法,
那么mOnClickListener又是在哪里被赋值的呢?
于是我们找到setOnClickListener方法:

public void setOnClickListener(OnClickListener l) {
    if (!isClickable()) {
        setClickable(true);
    }
    mOnClickListener = l;
}

很显然,只要我们给控件注册了click事件,就会给mOnClickListener赋值。
每当控件被点击时,都会在performClick()方法里回调该控件注册click事件时的onClick方法。

还有一个重要的知识点需要说明:点击事件的层级传递。
如果给一个控件注册了点击事件,每次点击都会触发ACTION_DOWN,ACTION_MOVE,ACTION_UP系列事件。
如果在执行ACTION_DOWN的时候返回了false,后面一系列其它的ACTION就不会再被执行。
也就是说,dispatchTouchEvent在进行事件分发的时候,只有前一个ACTION返回true,才会触发后一个ACTION。

View(可点击的)的onTouchEvent方法默认都会消耗事件(返回true),
因此这个View的dispatchTouchEvent方法默认返回true。该结论将会在下篇被用到。

我们回头看看,如果在onTouch方法中返回了false,进入到onTouchEvent方法中,
由于按钮默认都是可点击的,导致该方法总是会执行到第89行,返回true。
于是ACTION_DOWN后面的ACTION仍会被执行。
如果将按钮替换成ImageView(默认是不可点击的)则会符合我们所期望的那样。
ImageView的布局文件中增加android:clickable=”true”的属性,即变成可点击的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值