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)了屏幕
下图是触摸消息随时间变化的时间轴示意图:

相关引用来自> 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的回调。

更多相关文章

  1. listview中放Button 点击 长按事件
  2. Android事件分发机制解析
  3. 扩大View的点击区域
  4. Android事件处理(6)
  5. Android(安卓)GridView宫格视图(一) 运用--BaseAdapter
  6. Android(安卓)Activity 生命周期
  7. 【翻译】Android(安卓)Support Library Features(二)
  8. Android状态栏透明(沉浸式效果)
  9. Android(安卓)EditText回车不换行

随机推荐

  1. php实现简单的登陆功能(附源码)
  2. PHP中unserialize的使用方法
  3. PHP使用JWT创建Token的实例详解
  4. PHP中php_uname的用法详解
  5. PHP源码加密方法实例详解
  6. 详述php的下载安装教程
  7. php实现简单的留言板功能(附源码)
  8. 基于PHP的extract的用法详解
  9. 详解PHP随机生成中国人姓名的类
  10. php+javascript实现用户注册模块(附源码)