Android 事件分发机制

2017-06-05  本文已影响0人  Kurtis

在Android中事件分发是很重要的一块知识,了解并熟悉整套的分发机制有助于更好的分析各种点击滑动失效问题,更好去扩展控件的事件功能和开发自定义控件,同时事件分发机制也是Android面试必问考点之一。

MotionEvent事件初探

MotionEvent在Android View事件分发、处理过程中很重要的一个类,我们对屏幕的点击,滑动,抬起等一系的动作都是由一个一个MotionEvent对象组成的。根据不同动作,主要有以下三种事件类型:

从ACTION_DOWN开始到ACTION_UP结束我们称为一个事件序列
正常情况下,无论你手指在屏幕上有多么骚的操作,最终呈现在MotionEvent上来讲无外乎下面两种:

事件分发中重要方法

事件分发需要View的三个重要方法来共同完成:

以下是伪代码(基于ViewGroup源码)描述以上三个方法的关系:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
            // 检查是否拦截该事件 
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                // 子View请求是否需要父View拦截标志
               if (!disallowIntercept) {
                    // 调用onIntercepteTouchEvent判断时候拦截该事件
                    intercepted = onInterceptTouchEvent(ev);
                } 
            } 
        
        if (intercepted)  {
           // 该事件被拦截,交由onTouchEvent方法来处理
           handled = onTouchEvent(ev)
        } else {
           // 该事件未被拦截,交由其子类的dispatchTouchEvent处理
           handled = child.dispatchTouchEvent(ev);
        }
        return handled;
    }

我们再看看View的dispatchTouchEvent方法是如何实现的:

    public boolean dispatchTouchEvent(MotionEvent event) {
        // 事件是否被消费
        boolean result = false;
        // 相关的监听器信息
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            // 检查事件被OnTouchListener的onTouch方法消费了
            result = true;
        }
        // 如果没有被执行,才执行onTouchEvent
        if (!result && onTouchEvent(event)) {
            result = true;
        }

        return result;
    }

在ViewGroup的dispatchTouchEvent我们发现disallowIntercept这个标志, 通过它我们可以找到另一个重要的方法requestDisallowInterceptTouchEvent:

实际的应用中,可以在子view的onTouch事件中注入父ViewGroup的实例,并调用requestDisallowInterceptTouchEvent去阻止父view拦截点击事件

public boolean onTouchEvent(View v, MotionEvent event) {
     ViewGroup viewGroup = (ViewGroup) v.getParent();
     switch (event.getAction()) {
     case MotionEvent.ACTION_MOVE: 
         viewGroup.requestDisallowInterceptTouchEvent(true);
         break;
     case MotionEvent.ACTION_UP:
     case MotionEvent.ACTION_CANCEL:
         viewGroup .requestDisallowInterceptTouchEvent(false);
         break;
     }
}

事件分发过程

了解Android整个事件分发过程,比较好的方式是写一个Demo,步骤大致是先重写一个ViewGroup,一个View,然后重写Activity的dispatchTouchEvent、onTouchEvent,ViewGroup的dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent以及View的dispatchTouchEvent、onTouchEvent,然后针对各个Event打Log,网上的很多关于Android事件分发的博文都是这么讲解的, 有兴趣可以自行查找。
通过下面的流程图,会更加清晰的帮助我们梳理事件分发机制:

Android事件流程图
<center> View结构图</center >

<center> View事件分发流程图</center >

OnTouch & OnTouchEvent的关系

其实在前面已经提到过,还是直接看源码

   public boolean dispatchTouchEvent(MotionEvent event) {
        // 事件是否被消费
        boolean result = false;
        // 相关的监听器信息
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            // 检查事件被OnTouchListener的onTouch方法消费了
            result = true;
        }
        // 如果没有被执行,才执行onTouchEvent
        if (!result && onTouchEvent(event)) {
            result = true;
        }

        return result;
    }

onTouch是OnTouchListener的接口方法,它是用来获取Touch 信息用的,onTouchEvent方法用来处理诸如down, move, up的消息, 通过以上源码可以看出:
onTouchListener的onTouch方法优先级比onTouchEvent高,会先触发。
假如onTouch方法返回false会接着触发onTouchEvent,反之onTouchEvent方法不会被调用。
内置诸如click事件的实现等等都基于onTouchEvent,假如onTouch返回true,这些事件将不会被触发。

OnTouchEvent & OnClick & OnLongClick的关系

还是来看下伪代码

 public boolean onTouchEvent(MotionEvent event) {
    switch (action) {
       case MotionEvent.ACTION_UP:
         if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
            // This is a tap, so remove the longpress check
            removeLongPressCallback();
            if (!focusTaken) {
                if (mPerformClick == null) {
                    mPerformClick = new PerformClick();
                }
                if (!post(mPerformClick)) {
                    // 触发OnClickListener的onClick回调
                    performClick();
                }
            }
        }
        break:
      case MotionEvent.ACTION_DOWN:
        setPressed(true, x, y);
        checkForLongClick(0, x, y);
        break;
      case MotionEvent.ACTION_CANCEL:
        setPressed(false);
        removeTapCallback();
        removeLongPressCallback();
        break;
    }
 }

以上源码看一看出,onClick是在onTouchEvent ACTION_UP的时候出发的,如果该View的onTouchEvent或者它的ACTION_UP不被调用是不会触发onClick回调的。

关于onLongClick就稍微复杂一些,通过以上源码可以看到,当用户按下时(ACTION_DOWN)执行了checkForLongClick方法

private void checkForLongClick(int delayOffset, float x, float y) {
        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
            mHasPerformedLongPress = false;

            if (mPendingCheckForLongPress == null) {
                mPendingCheckForLongPress = new CheckForLongPress();
            }
            mPendingCheckForLongPress.setAnchor(x, y);
            mPendingCheckForLongPress.rememberWindowAttachCount();
            postDelayed(mPendingCheckForLongPress,
                    ViewConfiguration.getLongPressTimeout() - delayOffset);
        }
    }

该方法实际就是创建了个CheckForLongPress 的 Runnable, 它的run方法里面就是performLongClick(OnLongClickListener的回调就是在该方法中执行的),然后把它放入一个HandlerActionQueue队列中,让它延迟一段时间(DEFAULT_LONG_PRESS_TIMEOUT = 500)以后执行。
同时如果500毫秒之内,ACTION_UP或者ACTION_CANCEL执行了,就会移除该Runnable。

参考

图解 Android 事件分发机制
一文读懂Android View事件分发机制
一文解决Android View滑动冲突

上一篇 下一篇

猜你喜欢

热点阅读