源码内容不长,主要的逻辑内容上面已经讲了,其他的都是一些细节的处理。onTouchListener一般情况下我们是不会使用,那么接下来我们直接看到onTouchEvent方法。
onTouchEvent总体上就做一件事:根据按下情况选择触发onClickListener或者onLongClickListener ,也就是判断是单击还是长按事件,其他的源码都是实现细节。onTouchEvent方法正确处理每一个事件类型,来确保点击与长按监听器可以被准确地执行。理解onTouchEvent的源码之前,有几个重要的点需要先了解一下。
我们的操作模式有按键模式、触摸模式。按键模式对应的是外接键盘或者以前的老式键盘机,在按键模式下我们要点击一个按钮通常都是先使用方向光标选中一个button(也就是让该button获取到focus),然后再点击确认按下一个button。但是在触摸模式下,button却不需要获取焦点。如果一个view在触摸模式下可以获取焦点,那么他将无法响应点击事件,也就是无法调用onClickListener监听器 ,例如EditText。
view辨别单击和长按的方法是设置延时任务,在源码中会看到很多的类似的代码,这里延时任务使用handler来实现。当一个down事件来临时,会添加一个延时任务到消息队列中。如果时间到还没有接收到up事件,说明这是个长按事件,那么就会调用onLongClickListener监听器,而如果在延时时间内收到了up事件,那么说明这是个单击事件,取消这个延时的任务,并调用onClickListener。判断是否是一个长按事件,调用的是 checkForLongClick 方法来设置延时任务:
// 接收四个参数: // delay:延时的时长;x、y: 触控点的位置;classification:长按类型分类 private void checkForLongClick(long delay, float x, float y, int classification) { // 只有是可以长按或者长按会显示工具提示的view才会创建延时任务 if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) { // 标志还没触发长按 // 如果延迟时间到,触发长按监听,这个变量 就会被设置为true // 那么当up事件到来时,就不会触摸单击监听,也就是onClickListener mHasPerformedLongPress = false; // 创建CheckForLongPress // 这是一个实现Runnable接口的类,run方法中回调了onLongClickListener if (mPendingCheckForLongPress == null) { mPendingCheckForLongPress = new CheckForLongPress(); } // 设置参数 mPendingCheckForLongPress.setAnchor(x, y); mPendingCheckForLongPress.rememberWindowAttachCount(); mPendingCheckForLongPress.rememberPressedState(); mPendingCheckForLongPress.setClassification(classification); // 使用handler发送延时任务 postDelayed(mPendingCheckForLongPress, delay); } }上面这个方法的逻辑还是比较简单的,下面看看 CheckForLongPress 这个类:
private final class CheckForLongPress implements Runnable { ... @Override public void run() { if ((mOriginalPressedState == isPressed()) && (mParent != null) && mOriginalWindowAttachCount == mWindowAttachCount) { recordGestureClassification(mClassification); // 在延时时间到之后,就会运行这个任务 // 调用onLongClickListener监听器 // 并设置mHasPerformedLongPress为true if (performLongClick(mX, mY)) { mHasPerformedLongPress = true; } } } ... }延迟时间结束后,就会运行 CheckForLongPress 对象,回调onLongClickListener,这样就表示这是一个长按的事件了。
另外,在默认的情况下,当我们按住一个view,然后手指滑动到该view所在的范围之外,那么系统会认为你对这个view已经不感兴趣,所以无法触发单击和长按事件。当然,很多时候并不是如此,这就需要具体的view来重写onTouchEvent逻辑了,但是view的默认实现是这样的逻辑。