Android-View 点击&触摸事件优先级

1,693 阅读3分钟

一直很好奇,View 点击&触摸事件它们触发的优先级是怎么样,哪个先执行,哪个后执行,返回只true、false对后续执行有什么影响呢?主要分析onClick,onLongClick,onTouch,onTouchEvent这4个函数,有兴趣的可以一起往下看。

源码分析基于Android-23

简述作用
  1. onTouch&onTouchEvent 都是可以监听Touch事件,区别在于,前者是通过setOnTouchListener进行注册,后者只要重写即可;
  2. onClick 监听点击事件;
  3. onLongClick 监听长按事件。
OnTouchListener&onTouchEvent的调用关系
//View类中
public boolean dispatchTouchEvent(MotionEvent event) {
       //省略部分代码
      if (onFilterTouchEventForSecurity(event)) {
             //省略部分代码
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {//1
                result = true;
            }

            if (!result && onTouchEvent(event)) {//2
                result = true;
            }
        }
        //省略部分代码
        return result;
    }
  • 注释1:当设置了setOnTouchListener,那么li.mOnTouchListener就不为空,li.mOnTouchListener的onTouch函数就会被触发;
  • 注释2:如果li.mOnTouchListener的onTouch函数被触发并返回ture,那么重写的onTouchEvent就不会被触发;
onTouch&onTouchEvent的调用关系小结
  • li.mOnTouchListener的onTouch函数没有比触发或返回false,那么重写onTouchEvent才会被触发;
  • mOnTouchListener的onTouch方法优先级比onTouchEvent高;
流程图

1.png

onClick&onLongClick&onTouchEvent的调用关系
//View类中
public boolean onTouchEvent(MotionEvent event) {
            ......
            switch (action) {
                case MotionEvent.ACTION_UP:
                        ......
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {//3
                            removeLongPressCallback();
                            ......
                           performClick();
                        }
                    ......
                    break;

                case MotionEvent.ACTION_DOWN:
                    ......
                    if (isInScrollingContainer) {//1
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        checkForLongClick(0);//2
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:

                    removeTapCallback();//当发生canel,则移除判断点击任务
                    removeLongPressCallback();//当发生canel,则移除判断长按任务
                    ......
                    break;

                case MotionEvent.ACTION_MOVE:
                    ......
                    if (!pointInView(x, y, mTouchSlop)) {
                        removeTapCallback();//当发生move事件,则移除判断tap的任务
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                              ......
                            removeLongPressCallback();//当发生move事件,则移除判断长按任务
                        }
                    }
                    break;
            }
            return true;
        }
        return false;
    }
  • 注释2:先分析这里点,假设isInScrollingContainer为false,则发送500毫秒延时任务执行长按;
  • 注释1:当View被滑动控件包裹,则isInScrollingContainer为true,发送100毫秒的延时任务来判断判断是tap事件还是滑动事件,100毫秒内没有任何滑动,则发送400毫秒延时任务执行长按;
  • 注释3:如果onLongClick没有被触发或者返回false,mHasPerformedLongPress 则为false,将移除长按任务,接着执行performClick,通过从ViewRootImpl得到的handle往主线程队列发送点击任务(主要是触发li.mOnClickListener.onClick),等待被执行;
OnClickListener&OnLongClickListenerOnTouchEvent&onTouchEvent的调用关小结
  • down事件,View被滑动控件包裹,发送100毫秒的延时任务来判断是tap事件还是滑动事件,接着发送400毫秒的延时任务判断是是否长按;
  • 如果onLongClick被触发并返回true,那么onClick则不会被触发;
被滚动控件包裹,为什么要先发送100毫秒的延时任务呢?

重点是要区分tap(点击或长按)、滚动,如果是点击事件,需要给View设置一个按下的效果,但如果这个事件最终是滚动事件,那么先有按下后再有松开,用户体验显示是不合理的,所以有这样的判断。

流程图

2.png

总结
  • OnTouchListener onTouch函数优先级比onTouchEvent高;
  • 当onTouchEvent被触发,onClick、onLongClick才有可能被执行,
  • 当onLongClick消费了事件,那么onClick将无法执行;
  • 点击事件实现,在up事件时,利用ViewRootImpl构建的handler,往主线程MessageQueue发一个runnable点击任务,等待被分发执行;
  • 长按事件实现,发送100毫秒延时任务判断是tap还是滑动,如果是tap,则再发送400毫秒延时任务执行长按;

有分析不对的地方请指出,互相学习,谢谢哦!