关于Android中View的分发机制的学习总结(View篇)

来源:互联网 发布:php租房管理源码 编辑:程序博客网 时间:2024/05/22 17:49

这是我自己参考了网上许多关于View 的分发机制的文章之后的总结,可能有错误,也有解释的不对的地方,请指正。我尽量做到不打错别字,不造成阅读障碍。

说明:源码已变,但是结构思想未变,仍具有参考价值,你也可以找低的API版本。


先从设置点击事件开始

要借助一个小例子了

现在只有一个Activity,并且Activity中只有一个按钮。你应该已经知道,如果想要给这个按钮注册一个点击事件,只需要调用:

  1. button.setOnClickListener(new OnClickListener() {  
  2.     @Override  
  3.     public void onClick(View v) {  
  4.         Log.d("TAG""onClick execute");  
  5.     }  
  6. }); 
这样在onClick方法里面写实现,就可以在按钮被点击的时候执行。你应该也已经知道,如果想给这个按钮再添加一个touch事件,只需要调用:

  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. });  
onTouch方法里能做的事情比onClick要多一些,比如判断手指按下、抬起、移动等事件。那么如果我两个事件都注册了,哪一个会先执行呢?我们来试一下就知道了,运行程序点击按钮,打印结果如下:


可以看到,onTouch是优先于onClick执行的,并且onTouch执行了两次,一次是ACTION_DOWN,一次是ACTION_UP(你还可能会有多次ACTION_MOVE的执行,如果你手抖了一下)。因此事件传递的顺序是先经过onTouch,再传递到onClick。

接下来,你会发现onTouch是有返回值的,你在onTouch方法里返回true试试!结果:


厉害了!onClick不执行了,就是说OnClickListener的回调没有执行,肯定是被某个判断给抹杀了呗,而且就在onTouch返回值的判断上,没错,机智!

ViewGroup中说过,简单总结一下,你点击了button会执行button外LinearLayout(或RelativeLayout之类的)继承的ViewGroup的dispatchTouchEvent方法,然后在其中经过一系列操作会执行button的dispatchTouchEvent方法,可惜button没有这个方法,但是button继承TextView,TextView 也没有这个方法,而TextView 继承View,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);  
  7. }  
蛮简单的几行代码,1.先判断mOnTouchListener 是不是空,这个看名字就能联想到什么,看代码:

  1. public void setOnTouchListener(OnTouchListener l) {  
  2.     mOnTouchListener = l;  
  3. }  
这不是我刚才调用的
  1. button.setOnTouchListener(new OnTouchListener() {...});
的方法嘛!原来我自己设置了mOnTouchListener2.(mViewFlags & ENABLED_MASK) == ENABLED成不成立,这个一般默认是成立的,也就是可点击的,在button中不是有一个属性可以设置不可点击吗?就是这个道理啦。3.mOnTouchListener.onTouch(this, event)方法返回的值是true还是false?这就由我们重写的onTouch方法决定了,所以在本例中我们的onTouch回调方法(也就是你重写的方法)决定了这个if的执行与否。
接下了,如果你在onTouch返回的是false,if判断不成立,就执行return onTouchEvent(event);吧,还有什么犹豫的?
在看onTouchEvent(event)到底要干嘛?上代码:
  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. }  
我的天!虽然有点长,但也还可以接受。前半部分的代码看名字也能理解不少,何况还有注释。看重点吧,前面的代码会执行的,然后到14行,判断是否可点击或者是否可长按,然后执行方法,根据你的Action走不同case方法,首先是什么?肯定是MotionEvent.ACTION_DOWN啦。直接看重点——postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());将mPendingCheckForTap放到消息队列中去,并延时执行这个消息,mPendingCheckForTap是个Runable对象,在这之前的代码是用来判断用户是一次点击还是长按的,根据TAP_TIMEOUT时间 。接下来看看CheckForTap的内容里面,直接看run方法,其中有一个 postCheckForLongClick(ViewConfiguration.getTapTimeout());  方法,
  1. private void postCheckForLongClick(int delayOffset) {  
  2.     mHasPerformedLongPress = false;  
  3.   
  4.     // 实例化CheckForLongPress对象   
  5.     if (mPendingCheckForLongPress == null) {  
  6.         mPendingCheckForLongPress = new CheckForLongPress();  
  7.     }  
  8.     mPendingCheckForLongPress.rememberWindowAttachCount();  
  9.     // 调用PostDelayed函数发送长按事件的异步延迟消息   
  10.     postDelayed(mPendingCheckForLongPress,  
  11.             ViewConfiguration.getLongPressTimeout() - delayOffset);  
  12. }  
有一个mPendingCheckForLongPress对象,再看CheckForLongPress类里的是干什么的,它也是实现Runable接口的类,直接看run方法:
  1.   public void run() {  
  2.         // 进入该函数,说明检测到了长按操作   
  3.         if (isPressed() && (mParent != null)  
  4.                 && mOriginalWindowAttachCount == mWindowAttachCount) {  
  5.             if (performLongClick()) {   
  6.                 mHasPerformedLongPress = true;  
  7.             }  
  8.         }  
  9.     } 
重点是performLongClick()方法,看看里面是什么:
  1. public boolean performLongClick() {  
  2.     sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);  
  3.   
  4.     boolean handled = false;  
  5.     if (mOnLongClickListener != null) {  
  6.         // 回调用户实现的长按操作监听函数(OnLongClickListener)   
  7.         handled = mOnLongClickListener.onLongClick(View.this);  
  8.     }  
  9.     if (!handled) {  
  10.         // 如果OnLongClickListener的onLongClick返回false   
  11.         // 则需要继续处理该长按事件,这里是显示上下文菜单   
  12.         handled = showContextMenu();  
  13.     }  
  14.     if (handled) {  
  15.         // 长按操作事件被处理了,此时应该给用户触觉上的反馈   
  16.         performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);  
  17.     }  
  18.     return handled;  
  19. }  
有注释,简单易懂,当然这是长按的操作,只点击一下的话是会走MotionEvent.ACTION_UP分支的,在你UP之后会执行很多代码,直接看重点的performClick()方法:
public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }


        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }
if判断中只要li不为null,li.mListenerInfo不为null就会执行onClick方法,mOnClickListener是我们设置的(button.setOnClickListener(new OnClickListener(){...})),肯定不是null,li是由mListenerInfo赋值的,一般也不为null,这样就很清楚了!
不过别高兴的太早,现在还没结束,还有一个很重要的知识点需要说明,就是touch事件的层级传递。我们都知道如果给一个控件注册了touch事件,每次点击它的时候都会触发一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件。这里需要注意,如果你在执行ACTION_DOWN的时候返回了false,后面一系列其它的action就不会再得到执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。
  1. if (((viewFlags & CLICKABLE) == CLICKABLE ||  
  2.             (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {...}
会返回true,第89行,也就是你不管走了哪个分支最后都会返回true,以确保可以走其他分支。

参考博客:http://blog.csdn.net/guolin_blog/article/details/9097463/
         http://minjie.tech/2016/09/03/基于源码来了解Android的事件分发机制/
         http://blog.csdn.net/kakaxi1o1/article/details/41790979

1 0
原创粉丝点击