一、带着问题出发
我们手触摸点击屏幕的时候,触摸、点击事件是如何分发的呢? 布局中的控件是如何获取到按键事件的呢? 布局中有多个控件,如何只让指定的控件接收到相关的事件呢?复制代码
二、说在前面
下面来大体说下事件分发涉及到的几个类和相关方法:
Android的事件分发顺序是:Activity ----> ViewGroup ----> View复制代码
涉及到的几个重要方法:
dispatchTouchEvent(); onInterceptTouchEvent(); onTouchEvent();复制代码
dispatchTouchEvent负责事件的分发,onInterceptTouchEvent()是ViewGroup中判断是否进行事件拦截的方法,onTouchEvent() 是dispatchTouchEvent() 中执行的方法。
下面就分别来看下事件在Activity、ViewGroup、View 的分发机制。
三、Activity 的事件分发机制
Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } // >>> 分析1 if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }复制代码
PhoneWindow.java
@Override public boolean superDispatchKeyEvent(KeyEvent event) { return mDecor.superDispatchKeyEvent(event); } private final class DecorView extends FrameLayout implements RootViewSurfaceTaker { @Override public boolean dispatchTouchEvent(MotionEvent ev) { final Callback cb = getCallback(); return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev); } } public class FrameLayout extends ViewGroup { ... }复制代码
分析1:getWindow()是获取当前的window,调用到的是PhoneWindow,PhoneWindow 的superDispatchKeyEvent()中调用了DecorView 的superDispatchKeyEvent();而DecorView中调用的是super.dispatchTouchEvent(ev);
DecorView继承的是FrameLayout,FrameLayout又是继承的ViewGroup,所以最终会调用到ViewGroup的dispatchTouchEvent(ev);
四、ViewGroup的事件分发机制
ViewGroup.java
public boolean dispatchTouchEvent(MotionEvent ev) { //>>> 分析2 intercepted = onInterceptTouchEvent(ev); ... if(dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { ... } ... } public boolean onInterceptTouchEvent(MotionEvent ev) { return false; } private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } }复制代码
分析2:这里会有intercept,是否进行事件拦截的判断,该值是根据onInterceptTouchEvent()的返回值来判断,默认是返回的fale。
如果需要对事件进行拦截,可以重写ViewGroup的onInterceptTouchEvent()方法,直接return true,onInterceptTouchEvent()方法只有ViewGroup有,Activity和View都没有。
intercept 值为true,则进行事件拦截,执行的是super.dispatchTouchEvent(event),也就是View的dispatchTouchEvent(ev)方法,这个下面会分析.
intercept 值为false,则会遍历ViewGroup的子view,如果是处于子View的触摸区域,则会调用子view的dispatchTouchEvent();
五、View的事件分发机制
View.java
public boolean dispatchTouchEvent(MotionEvent event) { // >>>分析3 if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } // >>>分析4 if (!result && onTouchEvent(event)) { result = true; } } public void setOnTouchListener(OnTouchListener l) { getListenerInfo().mOnTouchListener = l; } public boolean onTouchEvent(MotionEvent event) { ... if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: ...... if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick(); } break; case MotionEvent.ACTION_DOWN: ... break; case MotionEvent.ACTION_CANCEL: ... break; case MotionEvent.ACTION_MOVE: ... break; } return true; } return false; } public boolean performClick() { final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); result = true; } else { result = false; } return result; }复制代码
分析3:如果view设置了OnTouchListener监听,这会会执行OnTouchListener的监听,如果onTouchLister return true,则表示事件被消费掉了,会直接return true,事件分发结束;可以重写setOnTouchListener()方法.
分析4:OnTouchListener中返回false,则会往下执行onTouchEvent(ev)方法,onTouchEvent(ev)中会执行performClick()方法,这时如果view设置了onClick监听,会执行onClick方法。
这里也可以看到,onTouchListener的执行会先于onClickListener.
六、最后总结
通过上面简单的代码跟读,我们知道点击Activity时,点击事件最先传递到Activity.java dispatchTouchEvent()中进行分发, dispatchTouchEvent()中会调用到ViewGroup 中的dispatchTouchEvent()方法, ViewGroup 中有个重要的onInterceptTouchEvent()方法来判断是否进行事件拦截,该方法默认return false,如果我们希望进行事件拦截, 可以重写该方法,return true,则事件会被消费掉, 不会再分发给子view;onInterceptTouchEvent() return false的时候, 会继续往下走,往下会遍历子view,如果当前点击的区域是相应子view的区域, 则会调用到子view的dispatchTouchEvent()方法,view 的dispatchTouchEvent()中会 先优先判断是否设置了onTouchListener监听,onTouchListener return false,后面才会执行view 的onClick监听。