Android 事件分发机制View篇

这两天研究了一下Android中的事件分发机制,在这里和大家分享下学习成果。首先我们先看一个简单的例子:

1.button.setOnClickListener(new OnClickListener() {  
2.    @Override  
3.    public void onClick(View v) {  
4.        Log.d("TAG", "onClick execute");  
5.    }  
6.});  
很显然这是一个button被设置了点击事件,只要一点击这个button就会触发onClick方法,就会打印这log。可是如果我们同时给button加上一个touch事件的监听,重写一下ontouch方法,那么结果又会如何呢。
1.button.setOnTouchListener(new OnTouchListener() {  
2.    @Override  
3.    public boolean onTouch(View v, MotionEvent event) {  
4.        Log.d("TAG", "onTouch execute, action " + event.getAction());  
5.        return false;  
6.    }  
7.});  
大家想想一下,是onclick先执行还是ontouch先执行?下面让我们看下结果。


可以看到是ontouch先执行,而且是执行两次的。

那么我们先得到一个简单的结论:ontouch是先于onclick执行的。

下面让我们改下代码,把ontouch中的返回值改成true,那么结果会怎样呢?

可以看到,onclick方法没有执行,这是怎么回事呢,让我们简单拔一下源码,便知分晓。

首先我们知道,一个当我们触摸到了一个控件一定会先调用他的dispatchTouchEvent方法,那么我们就从这个方法看起。我们进入button的源码发现没有找到dispatchTouchEvent这个方法,找寻他的父类textview依然没有,最终在他的爷爷view中找到了。

1.public boolean dispatchTouchEvent(MotionEvent event) {  
2.    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
3.            mOnTouchListener.onTouch(this, event)) {  
4.        return true;  
5.    }  
6.    return onTouchEvent(event);  
}  
我们看这个方法很简单,首先看if中的条件,在if中有三个条件,第一个条件在view这个类中可以找到 setOnTouchListener这个方法对mOnTouchListener进行了赋值,所以第一个条件一定是成立的;那么我们看第二个条件,这是判断当前控件是不是可用的,这个很显然也成立;那么就看第三个条件了,第三个条件是判断的ontouch方法的返回值,第一次我们没有修改他的返回值,返回的是false,所以导致if不成立。调用了onTouchEvent方法,然后onclick调用了,由此可以看出是onTouchEvent中调用了onclick方法。那么我们来看下第二次我们改变了ontouch方法中的返回值为true,这时if条件全部成立,返回了true,所以onTouchEvent方法没有执行,所以onclick方法也就没有执行。

如果我们把当前的button改成textview,再去为textview设置onclick方法和ontouch方法,这时结果又会怎样呢?

1.textView.setOnClickListener(new OnClickListener() {  
2.    @Override  
3.    public void onClick(View v) {  
4.        Log.d("TAG", "onClick execute");  
5.    }  
6.});  

1.textView.setOnTouchListener(new OnTouchListener() {  
2.    @Override  
3.    public boolean onTouch(View v, MotionEvent event) {  
4.        Log.d("TAG", "onTouch execute, action " + event.getAction());  
5.        return false;  
6.    }  
});  

我们注意ontouch返回的是false,大家看下结果:


我们发现只打印了一条log,而且是只执行了一次ontouch方法,这是怎么回事呢?按照我们上面的分析ontouch方法返回false,dispatchTouchEvent方法中的if条件不成立,应该执行ontouchEvent方法,并会执行onclick方法,为何onclick方法没有执行呢?

让我们在看下ontouchEvent的源码,就可以得到答案。

1.public boolean onTouchEvent(MotionEvent event) {  
2.    final int viewFlags = mViewFlags;  
3.    if ((viewFlags & ENABLED_MASK) == DISABLED) {  
4.        // A disabled view that is clickable still consumes the touch  
5.        // events, it just doesn't respond to them.  
6.        return (((viewFlags & CLICKABLE) == CLICKABLE ||  
7.                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));  
8.    }  
9.    if (mTouchDelegate != null) {  
10.        if (mTouchDelegate.onTouchEvent(event)) {  
11.            return true;  
12.        }  
13.    }  
14.    if (((viewFlags & CLICKABLE) == CLICKABLE ||  
15.            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
16.        switch (event.getAction()) {  
17.            case MotionEvent.ACTION_UP:  
18.                boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
19.                if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  
20.                    // take focus if we don't have it already and we should in  
21.                    // touch mode.  
22.                    boolean focusTaken = false;  
23.                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  
24.                        focusTaken = requestFocus();  
25.                    }  
26.                    if (!mHasPerformedLongPress) {  
27.                        // This is a tap, so remove the longpress check  
28.                        removeLongPressCallback();  
29.                        // Only perform take click actions if we were in the pressed state  
30.                        if (!focusTaken) {  
31.                            // Use a Runnable and post this rather than calling  
32.                            // performClick directly. This lets other visual state  
33.                            // of the view update before click actions start.  
34.                            if (mPerformClick == null) {  
35.                                mPerformClick = new PerformClick();  
36.                            }  
37.                            if (!post(mPerformClick)) {  
38.                                performClick();  
39.                            }  
40.                        }  
41.                    }  
42.                    if (mUnsetPressedState == null) {  
43.                        mUnsetPressedState = new UnsetPressedState();  
44.                    }  
45.                    if (prepressed) {  
46.                        mPrivateFlags |= PRESSED;  
47.                        refreshDrawableState();  
48.                        postDelayed(mUnsetPressedState,  
49.                                ViewConfiguration.getPressedStateDuration());  
50.                    } else if (!post(mUnsetPressedState)) {  
51.                        // If the post failed, unpress right now  
52.                        mUnsetPressedState.run();  
53.                    }  
54.                    removeTapCallback();  
55.                }  
56.                break;  
57.            case MotionEvent.ACTION_DOWN:  
58.                if (mPendingCheckForTap == null) {  
59.                    mPendingCheckForTap = new CheckForTap();  
60.                }  
61.                mPrivateFlags |= PREPRESSED;  
62.                mHasPerformedLongPress = false;  
63.                postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
64.                break;  
65.            case MotionEvent.ACTION_CANCEL:  
66.                mPrivateFlags &= ~PRESSED;  
67.                refreshDrawableState();  
68.                removeTapCallback();  
69.                break;  
70.            case MotionEvent.ACTION_MOVE:  
71.                final int x = (int) event.getX();  
72.                final int y = (int) event.getY();  
73.                // Be lenient about moving outside of buttons  
74.                int slop = mTouchSlop;  
75.                if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
76.                        (y < 0 - slop) || (y >= getHeight() + slop)) {  
77.                    // Outside button  
78.                    removeTapCallback();  
79.                    if ((mPrivateFlags & PRESSED) != 0) {  
80.                        // Remove any future long press/tap checks  
81.                        removeLongPressCallback();  
82.                        // Need to switch from pressed to not pressed  
83.                        mPrivateFlags &= ~PRESSED;  
84.                        refreshDrawableState();  
85.                    }  
86.                }  
87.                break;  
88.        }  
89.        return true;  
90.    }  
91.    return false;  
}  
从第14行我们可以看出只要控件是可点击的,就会进入到第三个if中,而且当手指抬起是会调用一个叫performClick的方法,所以如果控件是button会执行performClick这个方法,而TextView是不会执行这个方法的因为TextView是不可点击的,所以textview会直接返回91行的false,一个触摸事件的完整流程是DOWN开始,UP结束,只有当中返回值一直是true才会从DOWN走到UP,如果当中出现false就会中断这个点击事件,因为TextView在ontouchEvent中返回了false,所以只会在触摸的时候响应一下,所以只打印出了一条日志。

差点忘了去执行performClick方法的button,我们看下performClick的源码

1.public boolean performClick() {  
2.    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  
3.    if (mOnClickListener != null) {  
4.        playSoundEffect(SoundEffectConstants.CLICK);  
5.        mOnClickListener.onClick(this);  
6.        return true;  
7.    }  
8.    return false;  
9.}  
可以看到onclick方法在这里面执行了,button会将触摸事件响应到底,从DOWN到UP。

好了,我们说了这么多简单做下总结:把 touch 事件分发给 View 是通过调用 View 的 dispatchTouchEvent 方法来实现,View 的 dispatchTouchEvent 方法对事件进行了处理,主要做了两步:
- 第一步把事件给 View 的 mOnTouchListener 来处理,通过调用 mOnTouchListener.onTouch  方法来处理事件
- 第二步如果 mOnTouchListener 没有把事件消耗掉,就继续把事件给 View 的 onTouchEvent 方法来处理

View 的 onTouchEvent 方法的作用主要有两个,一个是作用把 touch 事件转换为 click 事件,产生 click 调用;另外一个作用是处理 view 按压状态的变化和焦点的分。touch 事件转换为 click 事件是在 motionEvent 为 up 类型的时候,调用了 performClick 方法,在 performClick 方法中执行 mOnClickListener 的 onclick 方法来执行点击事件。










  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值