Android 事件分发机制View篇

来源:互联网 发布:阿迪达斯网络授权书 编辑:程序博客网 时间:2024/05/29 00:35

这两天研究了一下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
原创粉丝点击