事件分发系列—View中的dispatchTouchEvent和onTouchEvent分析

dispatchTouchEvent

话不多说直接上源码

    /**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     * 将屏幕的按压事件传递给目标view,或者当前view即目标view
     * 
     * @param event The motion event to be dispatched.
     * 需要分发的事件
     * 
     * @return True if the event was handled by the view, false otherwise.
     * 如果返回true表示这个事件被这个view处理了,否则反
     */
    public boolean dispatchTouchEvent(MotionEvent event) {

        //系统调试分析相关,没有影响
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        //过滤是不是能够传递这个touch事件
        if (onFilterTouchEventForSecurity(event)) {

            //首先判断我们在使用该view的时候是否有实现OnTouchListener,如果有实现就判断当前的view
             //状态是不是ENABLED,如果实现的OnTouchListener的onTouch中返回true,并处理事件,则
             //返回true,这个事件在此处理了。
            if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
                    mOnTouchListener.onTouch(this, event)) {
                return true;
            }

            //如果没有在代码里面setOnTouchListener的话,就判断View自身的onTouchEvent方法有没有
            //处理,没有处理最后返回false,处理了返回true;
            if (onTouchEvent(event)) {
                return true;
            }

        }
        //系统调试分析相关,没有影响
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }

        //如果即没有setOnTouchListener,也没有在onTouchEvent中处理,就返回false
        return false;

    }

从上面的源码可以看出:在View的分发事件方法dispatchTouchEvent中,处理分发的顺序是实现OnTouchListener的onTouch(),之后是当前View的onTouchEvent(event)方法。

onTouchEvent

onTouchEvent的源码有点长,所以要沉下心来仔细阅读。事件消耗和事件处理都是返回true,事件消耗就相当于占坑不拉屎,虽然有点恶心哈,事件处理当然就是占坑拉屎。

在Android的触摸消息中,已经实现了三种监测,它们分别是
1)pre-pressed:对应的语义是用户轻触(tap)了屏幕
2)pressed:对应的语义是用户点击(press)了屏幕
3)long pressed:对应的语义是用户长按(long press)了屏幕
下图是触摸消息随时间变化的时间轴示意图:
事件分发系列—View中的dispatchTouchEvent和onTouchEvent分析_第1张图片
相关引用来自> http://www.linuxidc.com/Linux/2012-08/67979.htm


了解onTouchEvent就现需要了解的方法和类

  • CheckForTap类
    该类实现了Runnable接口,在run函数中设置触摸标识,并刷新Drawable的状态,同时用于发送一个检测长按事件的异步延迟消息,代码如下:
private final class CheckForTap implements Runnable {  
    public void run() {  
        // 进入该函数,说明已经过了ViewConfiguration.getTapTimeout()时间,   
        // 即pre-pressed状态结束,宣告触摸进入pressed状态   
        mPrivateFlags &= ~PREPRESSED;   
        mPrivateFlags |= PRESSED;  
        refreshDrawableState(); // 刷新控件的背景Drawable   
        // 如果长按检测没有被去使能,则发送一个检测长按事件的异步延迟消息   
        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {  
            postCheckForLongClick(ViewConfiguration.getTapTimeout());  
        }  
    }  
}  

private void postCheckForLongClick(int delayOffset) {  
    mHasPerformedLongPress = false;  

    // 实例化CheckForLongPress对象   
    if (mPendingCheckForLongPress == null) {  
        mPendingCheckForLongPress = new CheckForLongPress();  
    }  
    mPendingCheckForLongPress.rememberWindowAttachCount();  
    // 调用PostDelayed函数发送长按事件的异步延迟消息   
    postDelayed(mPendingCheckForLongPress,  
            ViewConfiguration.getLongPressTimeout() - delayOffset);  
}  
  • CheckForLongPress类
    该类定义了长按操作发生时的响应处理,同样实现了Runnable接口
class CheckForLongPress implements Runnable {  

    private int mOriginalWindowAttachCount;  

    public void run() {  
        // 进入该函数,说明检测到了长按操作   
        if (isPressed() && (mParent != null)  
                && mOriginalWindowAttachCount == mWindowAttachCount) {  
            if (performLongClick()) {   
                mHasPerformedLongPress = true;  
            }  
        }  
    }  

    public void rememberWindowAttachCount() {  
        mOriginalWindowAttachCount = mWindowAttachCount;  
    }  
}  

public boolean performLongClick() {  
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);  

    boolean handled = false;  
    if (mOnLongClickListener != null) {  
        // 回调用户实现的长按操作监听函数(OnLongClickListener)   
        handled = mOnLongClickListener.onLongClick(View.this);  
    }  
    if (!handled) {  
        // 如果OnLongClickListener的onLongClick返回false   
        // 则需要继续处理该长按事件,这里是显示上下文菜单   
        handled = showContextMenu();  
    }  
    if (handled) {  
        // 长按操作事件被处理了,此时应该给用户触觉上的反馈   
        performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);  
    }  
    return handled;  
}

了解完,我们来看看onTouchEvent的实现

   /**
     * Implement this method to handle touch screen motion events.
     * 如果需要处理屏幕产生的事件流需要实现这个方法
     * 
     * @param event The motion event.
     * 
     * @return True if the event was handled, false otherwise.
     * 如果返回true表示这个处理了这个事件,false则反
     */
    public boolean onTouchEvent(MotionEvent event) {

        //viewFLags用来记录当前View的状态
        final int viewFlags = mViewFlags;

        //如果当前View状态为DISABLED,如果不清楚DISABLED是一种什么状态那你应该用过
        //setEnabled(boolean enabled)这个方法,DISABLED就是ENABLED相反的状态。
        //DISABLED = 0x00000020 ,ENABLED_MASK = 0x00000020
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {
               //如果View的状态是被按压过,且当抬起事件产生,重置View状态为未按压,刷新Drawable的状态
                mPrivateFlags &= ~PRESSED;
                refreshDrawableState();
            }
            //如果当前View是一个DISABLED状态,且当前View是一个可点击或者是可长按的状态则当前事件在
            //此消耗不做处理,返回true。
            return (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
        }



----------


        //TouchDelegate是一个事件处理逻辑封装的一个类,也就是说Touch事件处理被委托了,那么就交由
        //mTouchDelegate.onTouchEvent处理,如果返回true,则事件被处理了,则不会向下传递
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }



----------

        //如果当前View的状态是可点击或者是可长按的,就对事件流进行细节处理
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    //PREPRESSED = 0x02000000
                    boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
                    //如果是pressed状态或者是prepressed状态,才进行处理   
                    if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
                        // take focus if we don't have it already and we should in
                        // touch mode.
                        //如果设定了获取焦点,那么调用requestFocus获得焦点
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                            // The button is being released before we actually
                            // showed it as pressed.  Make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            //在释放之前给用户显示View的prepressed的状态,状态需要改变为
                            //PRESSED,并且需要将背景变为按下的状态为了让用户感知到
                            mPrivateFlags |= PRESSED;
                            refreshDrawableState();
                       }
                        // 是否处理过长按操作了,如果是,则直接返回   
                        if (!mHasPerformedLongPress) {
                            //如果不是长按的话,仅仅是一个Tap,所以移除长按的回调
                            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.
                                //UI子线程去执行click,为了让click事件开始的时候其他视觉发
                                //生变化不影响。
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                //如果post消息失败,直接调用处理click事件
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }


----------


                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }

                        if (prepressed) {
                            //ViewConfiguration.getPressedStateDuration() 获得的是按下效 
                            //果显示的时间,由PRESSED_STATE_DURATION常量指定,在2.2中为125
                            //毫秒,也就是隔了125毫秒按钮的状态重置为未点击之前的状态。目的是让用户
                            //感知到click的效果
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            //如果通过post(Runnable runnable)方式调用失败,则直接调用
                            mUnsetPressedState.run();
                        }
                        //移除Tap的回调 重置View的状态
                        removeTapCallback();
                    }
                    break;

                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;
                    //在触摸事件中执行按钮相关的动作,如果返回true则表示已经消耗了down
                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    //判断是否在一个滚动的容器内
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // 如果父容器是一个可滚动的容器
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PREPRESSED;
                        //将view的状态变为PREPRESSED,检测是Tap还是长按事件
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        //直接将view状态转化为PRESSED,更新Drawable
                        mPrivateFlags |= PRESSED;
                        refreshDrawableState();
                        //是否是长按事件的判断
                        checkForLongClick(0);
                    }
                    break;
                //接收到系统发出的ACTION_CANCLE事件时,重置状态
                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();

                    // 如果移动超出了按钮的范围
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        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;
    }

对于onTouchEvent总的来说,首先受到事件首先判断当前View的状态是否为DISABLED,如果是则只需要简单的做一些状态的重置,不对事件做细节处理。如果不是DISABLED,就需要对事件细节进行处理,这时候又半路来个程咬金TouchDelegate,如果mTouchDelegate不为空,且返回了true,就表示该事件流有人给你代劳处理了,后面的分析View自己也不用做了。最后如果没人拦截处理,那就得View自己来。

下面开始是View自己处理事件流的逻辑过程描叙,即switch判断事件分支的处理:

  • ACTION_DOWN
    判断是否在触摸事件中执行按钮相关的动作,如果是,直接跳出,不是则继续判断当前View是否在一个可滑动的容器中,如果是则判断是否是一个点击tab事件,还是长按事件的检查,如果不是则直接转化状态为PRESSED并判断是否为长按事件。

  • ACTION_MOVE
    判断移动的点是否在当前View中,如果不在其中且当前状态为PRESSED则重置非PRESSED,且移除长按的回调。

  • ACTION_UP
    当抬起事件产生,首先判断View的状态是pressed状态或者是prepressed状态(也就是按过),才进行处理,如果是prepressed状态则变为pressed,并更新Drawable,然后判断在Down中的mHasPerformedLongPress标志,有没有变为true,也就是有没有产生执行长按事件,如果没有,则把这个事件流当做一个click事件做处理,也就是执行performClick中的代码,执行完click中的代码,最后重置View的状态和刷新Drawable。

  • ACTION_CANCLE
    当系统发送一个action_cancle的事件,则重置View的状态为非PRESSED,刷新Drawable的状态,且移除Tab的回调。

你可能感兴趣的:(android,源码,View,android,事件分发)