zoukankan      html  css  js  c++  java
  • 事件分发机制源代码分析

    一.view

    1、dispatchTouchEvent:

    问题1:onTouch还是onTouchEvent先执行?

    public boolean dispatchTouchEvent(MotionEvent event){  
        boolean result = false;  
        //如果有事件监听器,先让监听器处理事件。  
        if (mOnTouchListener.onTouch(event)) {  
            //如果监听器成功处理了该事件,处理结果设置为true。  
            result = true;  
        }  
        //如果没有监听器,就调用自身的onTouchEvent方法来处理事件。  
        if (!resutlt && onTouchEvent(event)) {  
            //如果自身的onTouchEvent成功处理事件,处理结果设置为true。  
            result = true;  
        }  
        return result;  
    }  

    ①onTouch优先于onTouchEvent执行,如果在onTouch方法中通过返回true将事件消费掉,onTouchEvent将不会再执行。

    ②onTouch能够得到执行需要两个前提条件,第一mOnTouchListener的值不能为空,第二当前点击的控件必须是enable的。

    问题2:onTouch先执行,还是onClick执行?

    有一个Button 按钮,要想为该按钮设置onClick事件和OnTouch事件

    mTestButton.setOnClickListener(new View.OnClickListener() {  
               @Override  
               public void onClick(View view) {  
                   Log.d(TAG, "onClick execute");  
               }  
    });  
    mTestButton.setOnTouchListener(new View.OnTouchListener() {  
               @Override  
               public boolean onTouch(View view, MotionEvent motionEvent) {  
                   Log.d(TAG, "onTouch execute, action event " + motionEvent.getAction());  
                   return false;  
               }  
    }); 
    View Code

    View源码中dispatchTouchEvent()方法的具体实现:

    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)方法源码中,接下来分析一下该函数的源码:

    2.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;  
    92 }
    View Code

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

    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方法。

     经验之谈:

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

    问题:onInterceptTouchEvent与onTouchEvent默认返回值?

      其中Layout里的onInterceptTouchEvent默认返回值是false,这样touch事件会传递到View控件,Layout里的onTouch默认返回值是false, View里的onTouch默认返回值是true,当我们手指点击屏幕时候,先调用ACTION_DOWN事件,当onTouch里返回值是true的时候,onTouch回继续调用ACTION_UP事件,如果onTouch里返回值是false,那么onTouch只会调用ACTION_DOWN而不调用ACTION_UP.

    二.ViewGroup

    1、onInterceptTouchEvent

    /** 默认实现是返回false,也就是默认不拦截任何事件 */

    public boolean onInterceptTouchEvent(MotionEvent ev);

    2、dispatchTouchEvent

    /** 根据内部拦截状态,向其child或者自己分发事件 */

    public boolean dispatchTouchEvent(MotionEvent ev);

     1 public boolean dispatchTouchEvent(MotionEvent ev) {  
     2     if (ACTION_DOWN事件 || 没有事件处理对象) {  
     3         if (允许拦截事件,该标志位由child调用requestDisallowInterceptTouchEvent<span style="font-family:微软雅黑;font-size:14px;">设置</span>) {  
     4             //查询拦截机制的结果,根据该结果来判断是否需要拦截  
     5             intercepted = onInterceptTouchEvent(ev);  
     6         } else {  
     7             //不允许拦截,那么不拦截  
     8             intercepted = false;  
     9         }  
    10     } else {   
    11         //不是DOWN,并且有处理对象,允许拦截,中断事件传递  
    12         intercepted = true;  
    13     }  
    14    
    15     if (不取消 && 不拦截) {  
    16         if (ACTION_DOWN) { //找寻接收事件序列的对象,其他事件不需要再计算事件产生对象,试想一下滑动一个ListView,当手指滑动出ListView的范围时,依然还是ListView响应后续事件。  
    17             for (遍历所有childView) {  
    18                 if (触摸点不在childView内部) {  
    19                     continue;  
    20                 }  
    21                 if (childView.dispatchTouchEvent(event)) {  
    22                     保存处理该事件的View,后续事件直接传递到该View,不要重新计算;  
    23                 }  
    24             }  
    25         }  
    26    
    27         if (还没有事件处理对象) {  
    28             //当前View树中没找到合适的child处理对象,把事件给自己处理,View.dispatchTouchEvent()就是把事件分发给自己  
    29             super.dispatchTouchEvent(event);  
    30         } else {  
    31             //传递给child  
    32             childView.dispatchTouchEvent(event);  
    33         }  
    34     } else if (拦截) {  
    35         //拦截事件,把事件给自己处理,View.dispatchTouchEvent()就是把事件分发给自己  
    36         super.dispatchTouchEvent(event);  
    37     }  
    38    
    39     return 处理结果;  
    40 }  

    3、requestDisallowInterceptTouchEvent

    /** 干涩parent的事件分发机制,通知parent,是否拦截后续事件,如果设置为true,parent就不会拦截该事件,不管什么状态。设置为false,parent走正常的拦截流程 */

    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {  
        if (已经是当前要设置的状态) {  
            // 已经处于这个状态, 假设我们的parent也是这个状态  
            return;  
        }  
        设置该状态;  
        // 传递给parent  
        if (有父容器) {  
            设置父容器的拦截状态;  
        }  
    }  
  • 相关阅读:
    回流和重绘
    php 异常捕获的坑
    每周散记 20180806
    转: Linux mount/unmount命令
    python http 请求 响应 post表单提交
    每周散记 20180723
    优惠劵产品分析
    c++ 软件版本比较函数
    每周散记
    转: 系统问题排查思路
  • 原文地址:https://www.cnblogs.com/ganchuanpu/p/6974853.html
Copyright © 2011-2022 走看看