Android事件传递机制-View篇

来源:互联网 发布:mac怎么用ps 编辑:程序博客网 时间:2024/05/21 08:47

Android事件传递机制绝对不是三言两语就能说得清的,在网上查了相关资料,觉得大部分都没有讲的很清楚透彻,写本文的目的就是让更多的开发者进从FrameWork层到Application层一步步深入Android事件传递机制的原理,今天先小小试牛刀,主要是讲View的事件传递机制原理,下一篇会将更复杂的控件ViewGroup事件的传递机制。

比如一个Activity页面有一个Button 按钮,要想为该按钮设置onClick事件,只需简单的使用下面几句代码即可:

[java] view plaincopyprint?
  1. mTestButton.setOnClickListener(new View.OnClickListener() {  
  2.            @Override  
  3.            public void onClick(View view) {  
  4.                Log.d(TAG, "onClick execute");  
  5.            }  
  6.        });  


 

       有了上面click事件,点击Button时就会执行上述onClick方法中的具体实现,这个我们都知道,但是如果我再为button添加一个OnTouchListener,代码实现也很简单,如下:

[java] view plaincopyprint?
  1. mTestButton.setOnTouchListener(new View.OnTouchListener() {  
  2.            @Override  
  3.            public boolean onTouch(View view, MotionEvent motionEvent) {  
  4.                Log.d(TAG, "onTouch execute, action event " + motionEvent.getAction());  
  5.                return false;  
  6.            }  
  7.        });  

     此时,我们现在分析一下,是onTouch先执行,还是onClick执行,我想一般人都能立即回答出,肯定是onTouch事件先执行,但是为什么会这样呢?其中的原理是什么,接下来我从FrameWork 源码去探寻一下整个事件的执行流程和原理:

  我们知道 Button ,TextView 等基础控件的基类都是View,只要你触摸到了任何一个控件,就一定会调用该控件的dispatchTouchEvent方法。那当我们去点击按钮的时候,就会去调用Button类(实际上是基类View)里的dispatchTouchEvent方法,所以接下来看View源码中dispatchTouchEvent()方法的具体实现:

[java] view plaincopyprint?
  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);  
  7. }  

    分析上述代码,第2行 如果三个条件都为真的话,就返回true,否则执行onTouchEvent,先看第一个条件mOnTouchListener!=null,这个条件就是如果设置了OnTouchListener就会为true,否则是false; 第二个条件(mViewFlags & ENABLED_MASK) == ENABLED是判断当前点击的控件是否是enable的,按钮默认都是enable的,因此这个条件恒定为true;第三个条件就比较复杂了,mOnTouchListener.onTouch(this, event),这个其实就是去回调控件注册touch事件时的onTouch方法。也就是说如果我们在onTouch方法里返回true,就会让这三个条件全部成立,从而整个方法直接返回true。如果我们在onTouch方法里返回false,就会再去执行onTouchEvent(event)方法。onTouchEvent(MotionEvent event)方法同样也是在view中定义的一个方法,主要是处理传递到view 的手势事件,包括ACTION_DOWN,ACTION_MOVE,ACTION_UP,ACTION_CANCEL四种事件。

   接下来我们结合上面的具体例子,来分析一下这个过程,首先会执行dispatchTouchEvent(MotionEvent event) ,所以onTouch方法肯定是早于onClick方法的,如果在onTouch里返回false,就会出现下面的现象:

10-20 18:57:49.670: DEBUG/MainActivity(20153): onTouch execute, action event 0
10-20 18:57:49.715: DEBUG/MainActivity(20153): onTouch execute, action event 1
10-20 18:57:49.715: DEBUG/MainActivity(20153): onClick execute

    即先执行了onTouch,再执行了onClick事件,而且onTouch执行了两次,一个是action_down,一个是action_up事件;

如果onTouch里返回true,则出现下面的现象:

10-20 19:01:59.795: DEBUG/MainActivity(21010): onTouch execute, action event 0
10-20 19:01:59.860: DEBUG/MainActivity(21010): onTouch execute, action event 1

   结果是onClick事件没有执行了,原因是如果onTouch返回true的话,则dispatchEvent(MotionEvent event)方法直接返回true了,相当于不往下传递事件了,所以onClick不会执行,相反如果onTouch返回false的话(此时会执行onClick方法),则会执行 onTouchEvent(MotionEvent event)方法,由此可以得出这样一个结论,onClick事件的具体调用执行肯定是在onTouchEvent(MotionEvent event)方法源码中,接下来分析一下该函数的源码:

[java] view plaincopyprint?
  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;  
  92. }  


  虽然源码有点多,但是我们只重点关注关键代码,在38行我们看到了代码:performClick();这个方法从名字表义来看就是OnClick方法的调用,我们进入到该方法中去看一探究竟,是否执行了OnClick方法呢?

[java] view plaincopyprint?
  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. }  


从上述代码可以看到,只要mOnClickListener不是null,就会去调用它的onClick方法,那mOnClickListener又是在哪里赋值的呢?经过分析后找到如下方法:

[java] view plaincopyprint?
  1. public void setOnClickListener(OnClickListener l) {  
  2.     if (!isClickable()) {  
  3.         setClickable(true);  
  4.     }  
  5.     mOnClickListener = l;  
  6. }  


   而上述这个方法就是我们在Application层经常使用的方法,即我们给button 设置点击事件的时候就会调用该方法了,分析到这了,我们知道了OnClick方法确实是在OnTouchEvent方法中,那么除了要设置 OnClickListener,调用onClick的条件又是什么呢?我们从38行代码往前推,从第14行可以分析出:

    只要该控件是可点击的或者是长按类型的,则会进入到MotionEvent.ACTION_UP这个分支当中 ,然后经过各种条件判断,则会进入到38行的performClick()方法中。

     至此,一切都清晰明白了!当我们通过调用setOnClickListener方法来给控件注册一个点击事件时,就会给mOnClickListener赋值。然后每当控件被点击时或者长按时,都会在performClick()方法里回调被点击控件的onClick方法。

  经验之谈:

  关于OnTouchEvent(MotionEvent事件)事件的层级传递。我们都知道如果给一个控件注册了touch事件,每次点击它的时候都会触发一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件。这里需要注意,如果你在执行ACTION_DOWN的时候返回了false,后面一系列其它的action就不会再得到执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。

  

   那我们可以换一个控件,将按钮替换成ImageView,然后给它也注册一个touch事件,并返回false。如下所示:

0 0
原创粉丝点击