Android事件分发机制——View(一)

     玉不琢,不成器;人不学,不知道。——《礼记·学记》
   
   转载请注明出处dmk877的博客http://blog.csdn.net/dmk877/article/details/48781845

   在阅读每一篇博客时一定要认真体会作者所想表达的意思,认真去揣摩,如果必要的话多读几遍,如果你读懂这篇文章,那么你对View的事件分发机制肯定会有一个深刻的了解。如有谬误欢迎大家批评指正我将在第一时间改正,如有疑问欢迎留言。知识只有拿过来分享讨论我们才能共同进步。

    在实际的开发中事件的分发可以说是经常用到的,比如自定义控件,或者处理界面中事件的冲突等等,当我们对android的事件的分发机制不了解时
遇到这种问题时你会感觉到无从下手,相反当你掌握了分发机制后你就会得心应手,因此熟练掌握android的事件分发机制是非常必要的,这也是面试
中经常被问到的。废话不多说进入正题

  通过本篇博客你将学到:
  ①android中View的事件分发机制过程
  ②View的点击事件和长按事件的执行的控制
  ③View的事件分发机制中常用的知识点

    在详细讲解事件分发机制之前首先我们来看例子,这几个例子可能大家会有疑惑,如果有的话你看完后就非常清楚是怎么回事了。

案例一
首先我们来一下第一个例子,这个例子非常简单,就是对布局中的ImageView和Button 设置setOntouchListener监听器代码如下
            btnTest.setOnTouchListener( new OnTouchListener() {
                
                 @Override
                 public boolean onTouch(View v, MotionEvent event) {
                     
                     System. out.println( "btnTest onTouch "+event.getAction());
                      return false;
                }
           });
           
            ivLanucher.setOnTouchListener( new OnTouchListener() {
                
                 @Override
                 public boolean onTouch(View v, MotionEvent event) {
                     
                     System. out.println( "ivLanucher onTouch "+event.getAction());
                      return false;
                }
           });

大家是否知道日志会怎么打印?可能有的人不清楚,那么我现在先把打印结果写出来(注意:①点击动作最好用模拟器测试,因为如果用真机的话,很容易触发控件的Move时间②0表示down事件,1表示Up事件,2表示move事件)
①当我们点击ImageView时的日志如下

②当我们点击Button时的日志如下

案例二
把上述的setOnTouchListener的返回值都设置为True此时代码如下
            btnTest.setOnTouchListener( new OnTouchListener() {
                
                 @Override
                 public boolean onTouch(View v, MotionEvent event) {
                     
                     System. out.println( "btnTest onTouch "+event.getAction());
                      return true;
                }
           });
           
            ivLanucher.setOnTouchListener( new OnTouchListener() {
                
                 @Override
                 public boolean onTouch(View v, MotionEvent event) {
                     
                     System. out.println( "ivLanucher onTouch "+event.getAction());
                      return true;
                }
           });
①当我们点击ImageView时的日志如下

②当我们点击Button时的日志如下

案例三、自定义一个Button
自定义的Button代码如下
package com.example.dispatchtoucheventpractice;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.Button;

public class MyButton extends Button {

     public MyButton(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
     }

     public MyButton(Context context, AttributeSet attrs) {
            super(context, attrs);
     }
     
     @Override
     public boolean dispatchTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
            case MotionEvent. ACTION_DOWN:
                System. out.println( "dispatchTouchEvent ACTION_DOWN");
                 break;
            case MotionEvent. ACTION_MOVE:
                System. out.println( "dispatchTouchEvent ACTION_MOVE");
                 break;
            case MotionEvent. ACTION_UP:
                System. out.println( "dispatchTouchEvent ACTION_UP");
                 break;
           }
            return super.dispatchTouchEvent(event);
     }

     @Override
     public boolean onTouchEvent(MotionEvent event) {

            switch (event.getAction()) {
            case MotionEvent. ACTION_DOWN:
                System. out.println( "onTouchEvent ACTION_DOWN");
                 break;
            case MotionEvent. ACTION_MOVE:
                System. out.println( "onTouchEvent ACTION_MOVE");
                 break;
            case MotionEvent. ACTION_UP:
                System. out.println( "onTouchEvent ACTION_UP");
                 break;
           }
     
            return super.onTouchEvent(event);
     }
}
MainActivity的代码如下
             btnTest.setOnTouchListener( new OnTouchListener() {
                
                 @Override
                 public boolean onTouch(View v, MotionEvent event) {
                      int action=event.getAction();
                      switch (action) {
                      case MotionEvent. ACTION_DOWN:
                           System. out.println( "onTouch ACTION_DOWN");
                            break;

                      case MotionEvent. ACTION_MOVE:
                           System. out.println( "onTouch ACTION_MOVE");
                            break;
                      case MotionEvent. ACTION_UP:
                           System. out.println( "onTouch ACTION_UP");
                            break;
                     }
                      return false;
                }
           });
我们点击按钮并移动一小小段距离打印结果如下

你能解释这个三个方法的执行顺序为什么是这样吗?

在此基础上我们把MainActivity中的自定义的Button的setOnTouchListener的返回值修改为true,点击按钮移动一段距离打印结果如下

为什么onTouchEvent方法没有执行?

好了这三个案例你是否都能从源码的角度对其分析,如果不能,看完此篇博客,你会对上述的案例非常清楚,好了我们开始事件分发机制的源码讲解
首先我们必须明白对于一个view的事件的分析首先要从dispatchTouchEvent这个方法入手,先上源码
    /**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     *
     * @param event The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
     */
    public boolean dispatchTouchEvent (MotionEvent event) {
        if (mOnTouchListener != null && ( mViewFlags & ENABLED_MASK) == ENABLED &&
                mOnTouchListener.onTouch( this, event)) {
            return true;
        }
        return onTouchEvent(event);
    }
在这里如果dispatchTouchEvent的返回值为true则表示,这个事件被view所消费,反之则不消费,从源码中看首先会进入一个if判断语句,判断的条件有三个
①mOnTouchListener!=null;
第一个条件mOnTouchListener这个监听器是什么?接着看源码
    /**
     * Register a callback to be invoked when a touch event is sent to this view.
     * @param l the touch listener to attach to this view
     */
    public void setOnTouchListener(OnTouchListener l) {
        mOnTouchListener = l;
    }
明白了吧,这个监听就是我们对View设置的监听

②(mViewFlags & ENABLED_MASK ) == ENABLED
第二个条件( mViewFlags & ENABLED_MASK ) == ENABLED
条件是判断View是否Enabled,一般的View都是enabled,除非你手动去设置,也就说第二个条件是满足的。

mOnTouchListener.onTouch(this,event)
第三个条件就是我们在activity设置setOnTouchListener后重写的onTouch()方法的返回值,也就是说这个ouTouch的返回值是我们自己设定的,假如我们给View设置了OnTouchListener,并且使ouTouch方法的返回值为true,从dispatchTouchEvent的源码中我们可以看出它就不会执行View的onTouchEvent(event)这个方法,并且此时dispatchTouchEvent的返回值为true,假如ouTouch方法的返回值为false此时View的dispatchTouchEvent的if语句的条件就为false那么就会执行View的onTouchEvent(event)这个方法,并且dispatchTouchEvent方法的返回值就是View的onTouchEvent(event)方法的返回值,到这里大家对onTouch和onTouchEvent这个两个方法的执行顺序清楚了吧。现在可以回过头来看看案例三打印的日志的顺序,可以自己分析出来了吧。
从dispatchTouchEvent方法中我们可以得出:
在进行事件分发时的执行顺序是dispatchTouchEvent--->OnTouchListener的onTouch方法--->onTouchEvent方法
到这里你应该能从源码的角度对案例三中的打印顺序进行解释,在脑子里回顾一下。。。。

接下来我们就来看onTouchEvent方法的源码
   /**
     * Implement this method to handle touch screen motion events.
     *
     * @param event The motion event.
     * @return True if the event was handled, false otherwise.
     */
    public boolean onTouchEvent(MotionEvent event) {
        final int viewFlags = mViewFlags;


        /**
         * 如果一个View是disabled, 并且该View是Clickable或者longClickable, 
         * onTouchEvent()就不执行下面的代码逻辑直接返回true, 表示该View就一直消费Touch事件
         */
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
        }
        /**
         * 如果此View有触碰事件处理代理,那么将此事件交给代理处理
         */
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
        /**
         * 如果不可点击(既不能单击,也不能长按)则直接返回false
         */
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
                    if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
                        // take focus if we don't have it already and we should in
                        // touch mode.
                        boolean focusTaken = false;
                        /**
                         * 是否需要获得焦点及用变量focusTaken设置是否获得了焦点.
                         * 如果我们还没有获得焦点,但是我们在触控屏下又可以获得焦点,那么则请求获得焦点
                         */
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }
                        /**
                         * 判断是否进行了长按事件的返回值情况,如果为false则移除长按的延迟消息并继续往下执行
                         */
                        if (!mHasPerformedLongPress) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();


                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }


                        /**
                         * 下面是判断有没有重新请求获得焦点,如果还没有新获得焦点,说明之前已经是按下的状态了.
                         * 派发执行点击操作的消息.这是为了在实际的执行点击操作时,让用户有时间再看看按下的效果.
                         * 之后就是派发消息来取消点击状态
                         */
                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }


                        if (prepressed) {
                            mPrivateFlags |= PRESSED;
                            refreshDrawableState();
                            /**
                             * ViewConfiguration.getPressedStateDuration() 获得的是按下效
                             * 果显示的时间,由PRESSED_STATE_DURATION常量指定,在2.2中为125毫秒
                             */
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }
                        removeTapCallback();
                    }
                    break;


                case MotionEvent.ACTION_DOWN:
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    mPrivateFlags |= PREPRESSED;
                    /**
                     * 给mHasPerformedLongPress设置初始值为false
                     */
                    mHasPerformedLongPress = false;
                    /**
                     * 发送一个延迟消息延迟时间为ViewConfiguration.getTapTimeout()在2.2的源码中此值为115毫秒
                     * 到达115毫秒后会执行CheckForTap()方法,如果在这115毫秒之间用户触摸移动了,则
                     * 删除此消息.否则执行按下状态,在CheckForTap()中检查长按.
                     */
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    break;


                case MotionEvent.ACTION_CANCEL:
                    mPrivateFlags &= ~PRESSED;
                    refreshDrawableState();
                    removeTapCallback();
                    break;


                case MotionEvent.ACTION_MOVE:
                    final int x = (int) event.getX();
                    final int y = (int) event.getY();


                    // Be lenient about moving outside of buttons
                    int slop = mTouchSlop;
                    /**
                     * 当手指在View上面滑动超过View的边界,
                     */
                    if ((x < 0 - slop) || (x >= getWidth() + slop) ||
                            (y < 0 - slop) || (y >= getHeight() + slop)) {
                        // Outside button
                    /**
                     * 如果手指滑动超过Vie的边界则移除DOWN事件中设置的检测
                     */
                        removeTapCallback();
                        if ((mPrivateFlags & PRESSED) != 0) {
                            // Remove any future long press/tap checks
                            removeLongPressCallback();


                            // Need to switch from pressed to not pressed
                            mPrivateFlags &= ~PRESSED;
                            refreshDrawableState();
                        }
                    }
                    break;
            }
            return true;
        }
        return false;
    }
下面我们来拆分一下上面的源码首先执行一个if判断语句
if ((viewFlags & ENABLED_MASK) == DISABLED) {
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE ));
        }
在这里要特别注意的是此方法中if(((viewFlags & CLICKABLE) == CLICKABLE ||(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE))它的范围这里把中间的代码省略如下:
public boolean onTouchEvent(MotionEvent event) {
        。。。。。。。。。。。
       	此处有省略
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
            。。。。。。。。。。。
            此处有省略
            }
            return true;
        }
        return false;
    }
从上面的简化代码中我们可以看出只要是进入了if判断语句则onTouchEvent一定会返回true即消费事件,并且进入此if语句的条件为
此View是可以点击的或者是可以长按的。

下面我们来拆分一下上面的源码首先执行一个if判断语句
if ((viewFlags & ENABLED_MASK) == DISABLED) {
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE ));
        }
在上面的注释中已经对其进行了说明,这里单独拿出来再强调一下-----如果一个View是disabled, 并且该View是Clickable或者longClickable, onTouchEvent()就不执行下面的代码逻辑直接返回true, 表示该View就一直消费Touch事件,这一点从上面的代码可以看出, 如果一个enabled的View,并且是clickable或者longClickable的,onTouchEvent()会执行下面的代码逻辑并返回true,这一点从上面的省略代码片段可以得出。
综上,一个clickable或者longclickable的View是一直消费Touch事件的,而一般的View既不是clickable也不是longclickable的(即不会消费Touch事件,只会执行ACTION_DOWN而不会执行ACTION_MOVE和ACTION_UP) Button是clickable的,可以消费Touch事件,但是我们可以通过setClickable()和setLongClickable()来设置View是否为clickable和longClickable。

接着我们来分析一下onTouchEvent中的事件上面的代码中有比较详细的注释,我在这里再分析一下
ACTION_DOWN:
             case MotionEvent.ACTION_DOWN:
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    mPrivateFlags |= PREPRESSED;
                    mHasPerformedLongPress = false;
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    break;
在这个方法中首先 给mPrivateFlags设置一个PREPRESSED的标识,然后设置为mHasPerformedLongPress设置一个初始值false,接着会执行一个延迟
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
在这里ViewConfiguration.getTapTimeout()的值为115毫秒(注意以上源码包括时间常量都是2.2源码中,其他源码可能会稍有不同)这个延迟有什么作用呢?

在给定的TapTimeout时间之内,用户的触摸没有移动,就当作用户是想点击,而不是滑动.具体的做法是,将 CheckForTap的实例mPendingCheckForTap添加时消息队例中,延迟执行。如果在这tagTimeout之间用户触摸移动了,则删除此消息.否则执行按下状态.然后检查长按。

经过115毫秒的延迟后会执行CheckForTap方法,这个方法是干什么的呢?来看下源码
       /**
        * ACTION_DOWN事件延迟115毫秒后调用
        */
     private final class CheckForTap implements Runnable {
        public void run() {
            mPrivateFlags &= ~PREPRESSED;
            mPrivateFlags |= PRESSED;
            refreshDrawableState();
            /**
             * 如果View支持长按事件即View是LONG_CLICKABLE的则发送一个长按事件的检测
             */
            if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
                postCheckForLongClick(ViewConfiguration.getTapTimeout());
            }
        }
    }
这个方法可以看到如果View是LONG_CLICKABLE的就是执行postCheckForLongClick(ViewConfiguration.getTapTimeout())这个方法来检测长按事件,但是一般的View不是LONG_CLICKABLE的,可能有的人会有疑问,如果View不是LONG_CLICKABLE的怎么执行长按事件啊?此时我们需要调用setOnLongClickListener实现OnLongClickListener接口
源码如下:
    /**
     * Register a callback to be invoked when this view is clicked and held. If this view is not
     * long clickable, it becomes long clickable.
     *
     * @param l The callback that will run
     *
     * @see #setLongClickable(boolean)
     */
    public void setOnLongClickListener (OnLongClickListener l) {
        if (!isLongClickable()) {
            setLongClickable( true);
        }
        mOnLongClickListener = l;
    }
从源码中我们可以看到设置了OnLongClickListener后如果这个View不是LONG_CLICKABLE的,那么就把它设置成LONG_CLICKABLE的。这样我们回到CheckForTap方法在View是LONG_CLICKABLE的情况下就会调用postCheckForLongClick方法,这个方法的源码如下
   private void postCheckForLongClick(int delayOffset) {
    /**
     * 设置mHasPerformedLongPress为false表示长按事件还未触发
     */
        mHasPerformedLongPress = false;
        if (mPendingCheckForLongPress == null) {
            mPendingCheckForLongPress = new CheckForLongPress();
        }
        mPendingCheckForLongPress.rememberWindowAttachCount();
        /**
         * 此delayOffset是从上面的CheckForTap类中传过来的值为ViewConfiguration.getTapTimeout()
         * ViewConfiguration.getLongPressTimeout()在2.2中为500毫秒,也就是经过500-115毫秒后会执行CheckForLongPress方··         * 法在CheckForLongPress方法中会调用执行长按事件的方法,由于在ACTION_DOWN事件中有一个延迟消息延迟115毫秒后          * 执行CheckForTap中的run方法所以这里500-115+115=500也就是说从按下起经过500毫秒会触发长按事件的执行
         */
        postDelayed(mPendingCheckForLongPress,ViewConfiguration.getLongPressTimeout() - delayOffset);
    }
在上面的方法会有一个延迟经过500-115毫秒后会执行CheckForLongPress方法。
class CheckForLongPress implements Runnable {


        private int mOriginalWindowAttachCount;
        /**
         * 因为等待形成长按的过程中,界面可能发生变化如Activity的pause及restart,这个时候,长按应当失效.
         * View中提供了mWindowAttachCount来记录View的attach次数.当检查长按时的attach次数与长按到形成时.
         * 的attach一样则处理,否则就不应该再当前长按. 所以在将检查长按的消息添加时队伍的时候,要记录下当前的windowAttach          *Count.
         */
        public void run() {
            if (isPressed() && (mParent != null)
                    && mOriginalWindowAttachCount == mWindowAttachCount) {
                if (performLongClick()) {
                /**
                 * 执行长按事件后返回值为true,设置mHasPerformedLongPress为true此时会屏蔽点击事件
                 */
                    mHasPerformedLongPress = true;
                }
            }
        }


        public void rememberWindowAttachCount() {
            mOriginalWindowAttachCount = mWindowAttachCount;
        }
    }
从CheckForLongPress的run方法中可以看到如果performLongClick()的返回值为true mHasPerformedLongPress才为true,那么我们看看performLongClick它的做了哪些动作呢?我们来看看源码
    /**
     * Call this view's OnLongClickListener, if it is defined. Invokes the context menu
     * if the OnLongClickListener did not consume the event.
     *
     * @return True there was an assigned OnLongClickListener that was called, false
     *         otherwise is returned.
     */
    public boolean performLongClick() {
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);

        boolean handled = false;
        /**
         * 到了重点可以看到在这里会执行我们为View设置的长按事件的回调,这里的mOnLongClickListener就是我们自己给View设置的长按的监听,
         * 从这里也可以得出一个结论即长按事件是在ACTION_DOWN中执行的
         */
        if (mOnLongClickListener != null) {
            handled = mOnLongClickListener.onLongClick(View.this);
        }
        if (!handled) {
            handled = showContextMenu();
        }
        if (handled) {
            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
        }
        return handled;
    }
终于来了个重点我们看到在其中有个判断
if (mOnLongClickListener != null) {
            handled = mOnLongClickListener.onLongClick(View.this);
        }
也就是说如果你设置了长按的监听,那么mOnLongClickListener!=null此时就会执行我们重写的onLongClick()方法, 这里我们也得出一个结论:
即长按事件是在ACTION_DOWN中执行的。

到这里我们可以总结一下:首先当执行ACTION_DOWN事件后会设置一个 PREPRESSED标识,如果这次点击持续115毫秒后就会发送一个检测长按的延迟任务,这个任务的延迟时间是500-115毫秒,这个115毫秒就是检测 PREPRESSED所经历的时间,所以这样算一下就可以知道当按钮从按下的那一刻起经历了500毫秒就会触发长按事件(注意这个Android 2.2中的源码,其它的系统的时间会稍有差异)
通过以上的分析我们还可以得出如下结论:

1、如果此时设置了长按的回调,则执行长按时的回调,且如果长按的回调返回true;才把mHasPerformedLongPress置为ture;

2、否则,如果没有设置长按回调或者长按回调返回的是false;则mHasPerformedLongPress依然是false;

一般的View默认是不消费touch事件的,我们要想执行点击事件必须要调用setOnClickListener()来设置OnClickListener接口,我们看看这个方法的源码就知道了
public void setOnClickListener (OnClickListener l) {
        if (!isClickable()) {
            setClickable( true);
        }
        mOnClickListener = l;
    }
看到没?当我们设置了onClickListener时如果isClickable()=false,就执行 setClickable(true)。
到这里我们就可以分析案例一和二:
在案例一中当Button和ImageView都返回false时dispatchTouchEvent 的if语句不成立就会执行onTouchEvent方法,而在onTouchEvent中有 if (((viewFlags & CLICKABLE) == CLICKABLE ||(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE ))这个if判断语句因为ImageView的CLICKABLE=fasle,Button的CLICKABLE=true。所以Button可以进入这个if语句而ImageView不行,从源码中可以看到(结合我们简化的代码看)只要进入这个if语句那么onTouchEvent一定返回true从而dispatchTouchEvent 返回true而消耗事件,如果没有进入这个if语句那么onTouchEvent一定返回false从而dispatchTouchEvent一定返回fasle不消耗事件。而当ImageView和Button都返回true时此时dispatchTouchEvent的第一个语句成立并且返回值为true表示消费所有事件。所以此时它俩的打印内容是一样的。

ACTION_MOVE:
当执行move事件时首先会拿到当前触摸的X,Y坐标然后判断当前触摸的点有没有移出当前的View,如果移出了当前的View可分为两种情况:
①ACTION_DOWN事件触发不到115毫秒时:首先执行removeTapCallback方法这个方法是移除在ACTION_DOWN方法中设置的PREPRESSED检测
  这也和我们在ACTION_DOWN中的CheckForTap延迟115毫秒后执行进行的说明相吻合。
ACTION_DOWN事件触发时间大于115毫秒时:此时已经触发了长按的事件,当前mPrivateFlags一定为PRESSED且发送了长按的检测,此时就会移除removeLongPressCallback()然后mPrivateFlags中PRESSED标识去除

ACTION_UP:
ACTION_UP在上面的注释已经很清楚在这里我想对我们需要重重重点了解的内容分析一下。
在ACTION_UP执行的过程中有这么一个判断
               if (!mHasPerformedLongPress) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }
我们首先不考虑判断条件看看这个if语句里进行了什么操作如果mPerformClick!=null的话就会执行performClick()方法我们看看此方法干了什么
    /**
     * Call this view's OnClickListener, if it is defined.
     *
     * @return True there was an assigned OnClickListener that was called, false
     *         otherwise is returned.
     */
    public boolean performClick() {
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        if (mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            mOnClickListener.onClick(this);
            return true;
        }

        return false;
    }
终于看到了,终于看到我们设置的监听执行了,可以看到在这里如果mOnclickListener!=null就会执行onClick(),这里的 mOnclickListener就是我们设置的监听从而我们可以得出一条结论:onClick()方法是在ACTION_UP中执行的。

接着反过来我们回过头看看上面那个if(! mHasPerformedLongPress) 在这个判断语句这个判断语句执行的条件就是mHasPerformedLongPress为false
看到mHasPerformedLongPress你是不是感觉很熟悉,这个 mHasPerformedLongPress不就是刚开始在ACTION_DOWN中我们给它设置个初始值false然后如果CheckForLongPress中的performLongClick()方法返回true则mHasPerformedLongPress为true,如果performLongClick()方法的返回值为false那么mHasPerformedLongPress为false与此同时如果mHasPerformedLongPress为false的话就不会进入if语句也就是说我们的onClick()方法就不会执行,我这样说你能明白mHasPerformedLongPress这个字段的作用吗?这个字段可以用来控制onLongClick()和onClick()的执行,这样说肯定会有好多人不明白怎么回事,不知道大家有没有一个疑问:对一个View它的OnLongClickListener和OnClickListener是否只能执行一个?怎样控制这两个方法的执行?
接下来通过一个例子你就会非常明白以上两个问题,下面这个例子需要结合我们再ACTION_DOWN中的分析中最后得出的两条结论。
           btnClick.setOnClickListener( new OnClickListener() {
                
                 @Override
                 public void onClick(View v) {
                     Toast. makeText(MainActivity.this,"点击事件",0) .show();
                }
           });
           
            btnClick.setOnLongClickListener( new OnLongClickListener() {
                
                 @Override
                 public boolean onLongClick(View v) {
                     Toast. makeText(MainActivity.this,"长按事件",0).sh ow();
                      return false;
                }
           });
运行后我们长按按钮发现会触发长按事件弹出“长按事件”,松开按钮后会触发点击事件弹出“点击事件”,
分析:
因为我们的setOnsetOnLongClickListener中的onLongClick返回值为false导致performLongClick方法的返回值为false此时虽然执行了长按的方法但是不会进入下面if语句也就是mHasPerformedLongPress仍然为false
       if (performLongClick()) {
                    mHasPerformedLongPress = true;
               }
mHasPerformedLongPress为false的时候就是在ACTION_UP中执行performClick()方法从而执行点击事件

当setOnLongClickListener的返回值为true时,当我们快速点击按钮时会触发点击事件弹出“点击事件”,但是当我们触发了长按事件后就不会触发点击事件,此时长按事件屏蔽了点击事件。
分析:
setOnLongClickListener返回值为true时,performLongClick方法的返回值也为true此时会从新给mHasPerformedLongPress赋值为true,当mHasPerformedLongPress为true的时候不会进入ACTION_UP中执行performClick()方法从而不会执行点击事件。
不知道通过上面的分析大家有没有理解,如果没有理解的话就多读几遍吧。

总结:
1.整个View的事件分发的流程是
View.dispatchEvent->View.setOnTouchListener->View.onTouchEvent

在dispatchTouchEvent中会进行OnTouchListener的判断,如果OnTouchListener不为null且返回true,则表示事件被消费,onTouchEvent不会被执行;否则执行onTouchEvent。

2.一个clickable或者longClickable的View会永远消费Touch事件,不管他是enabled还是disabled的

3.View的长按事件是在ACTION_DOWN中执行,要想执行长按事件该View必须是longClickable的,并且不能产生ACTION_MOVE

4.View的点击事件是在ACTION_UP中执行,想要执行点击事件的前提是消费了ACTION_DOWN和ACTION_MOVE,并且没有设置OnLongClickListener的情况下,如设置了OnLongClickListener的情况,则必须使onLongClick()返回false

5.如果View设置了onTouchListener了,并且onTouch()方法返回true,则不执行View的onTouchEvent()方法,也表示View消费了Touch事件,返回false则继续执行onTouchEvent()




好了这一篇就到这里了,如果你觉得还不错,就留个言顶一下吧。哈哈。。。






猜你喜欢

转载自blog.csdn.net/dmk877/article/details/48781845