点击事件的传递规则:
当一个MotionEvent产生后,系统需要把这个事件传递给一个具体的View,而这个过程就是事件的分发过程,这个过程由3个很重要的方法组成,dispatchTouchEvent,onInterceptTouchEvent和onTouchEvent,
public boolean dispatchTouchEvent(MotionEvent ev)(ViewGroup里面有的方法)
用来进行事件的分发.如果事件能够传递到当前的View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和下级子View的dispatchTouchEvent方法的影响,表示是否消耗当前事件
public boolean onInterceptTouchEvent(MotionEvent ev)(ViewGroup里面有的方法)
在上面的dispatchTouchEvent方法里面调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件(例如down事件),那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件
说明:
同一个事件 序列是指:从手机接触屏幕的那一刻起,到手机离开屏幕的那一刻结束,在这个过程中所产生的一系列事件,这个事件序列以down事件开始,中间含有数量不定的move事件,最终以up事件结束
public boolean onTouchEvent(MotionEvent event)(View里面有的方法)
在上面的dispatchTouchEvent方法里面调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗(down事件返回false),则在同一个事件序列当中,当前view无法再次接受到(move和up)事件
这3个方法的关系可以用下面的为代码来表示:
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume=false;
if (onInterceptTouchEvent(ev)) {
//如果父类ViewGroup要拦截事件,那么它就会调用自己的onTouchEvent方法来处理事件
consume=this.onTouchEvent(ev);
}else{
//如果父类ViewGroup不要事件,那么它就会让自己的孩子去分发事件
consume=child.dispatchTouchEvent(ev);
}
return consume;
}
当一个点击事件产生后,它的传递过程遵循如下顺序:Activity–>window–>view,即事件总是先传递给Activity,Activity再传递给window,最后window再传递给顶级View
当一个View需要处理事件时,如果它设置了OnTouchListener,那么OnTouchListener中的onTouch方法会被回调,如果onTouch方法返回false,则当前的onTouchEven方法会被调用,如果返回true,就不会调用onTouchEven方法,所以OnTouchListener的优先级大于
onTouchEven,在onTouchEvent方法中,如果当前设置的有onCLickListener,那么它的onClick方法会被调用
优先级:OnTouchListener>onTouchEven(onClick()方法在这个此方法里面)
正常情况下:一个事件序列只能被一个View拦截且消耗
当一个事件传到view上时:首先是触发action_down方法,action_down返回true表示此View要消耗这个事件,那么同一个事件序列的action_move和action_up都会传给它,action_down返回false 表示此view不要这个事件,那么同一个事件序列的action_move和action_up都不会传到它那里
事件的传递过程是由外向内,即事件总是先传递给父元素,然后再由父元素分发给子View,通过getParent().requestDisallowInterceptTouchEvent(boolean disallowIntercept);方法可以在子元素中干预父元素的事件分发过程,但是父元素的action_down事件子元素干预不了
事件分发的源码解析:
1.Activity对点击事件的分发过程:
点击操作发生时,事件最先传递给当前的acivity,由activity的dispatchTouchEvent方法来进行分发,具体的工作是由activity内部的window来完成,window会把事件传递给decor view,decor view一般就是当前界面的底层容器(即setContentView所设置的View的父容器),通过Activity.getWindow.getDecorView()可以获得.decor view继承帧布局FrameLayout
源码:Activity#dispatchTouchEvent(MotionEvent ev)
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
* @param ev The touch screen event.
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//这个方法是个空实现 interaction:相互作用,相互影响
//我们可以重写添加我们的逻辑
onUserInteraction();
}
//交由activity所属的window来派发事件
//返回true;表示有View消耗了这个事件
//返回false;表示没有View消耗事件,需要act自己来处理
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//如果没有view消耗事件,那么act自己的onTouchEven会调用
return onTouchEvent(ev);
}
2.window.对点击事件的分发过程:
接下来看一下window是如何把事件传递给ViewGroup的
/* The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
public abstract class Window {...}
源码:window#superDispatchTouchEvent(MotionEvent event)
/**
* Used by custom windows, such as Dialog, to pass the touch screen event
* further down the view hierarchy. Application developers should
* not need to implement or call this.
*/
public abstract boolean superDispatchTouchEvent(MotionEvent event);
通过源码和它的英文解释,我们知道Window 是一个抽象类,window的唯一实现类是PhoneWindow
(如何进到PhoneWindow源码里面看具体代码呢,这个有个小技巧:找到window里面的一个抽象方法 例如这里的getDecorView()方法,然后按住ctrl按键同时鼠标点击getView(),出现如下图的情况
找到PhoneWindow.java类,点击进去,就可以进到PhoneWindow.java类里面了)
/** @hide */
public class PhoneWindow extends Window implements MenuBuilder.Callback {...}
源码:PhoneWindow#superDispatchTouchEvent
//PhoneWindow实现window的superDispatchTouchEvent抽象方法
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
由上所知PhoneWindow把事件直接传递给了DecorView,至此window的事件就分发完成了
3.DecorView.对点击事件的分发过程:
首先我们看一下DecorView是什么东西
源码: phoneWindow#mDecor
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
/** @hide */
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {...}
由以上的源码和英文解释可以知道DecorView 是顶级View即根View,它继承与FrameLayout.通过getWindow().getDecorView()获得,我们常在activity中见的setContentView所设置的View,就可以通过mDecorView.findViewById(android.R.id.content).getChildAt(0)来得到
我们大致看一下ViewGroup对事件的分发:
源码 ViewGroup#dispatchTouchEvent
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
...代码省略
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
....代码省略
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
....代码省略
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
这里我们再回顾一下viewGroup事件的分发过程:
点击事件到达顶级View(一般是一个ViewGroup)以后,会调用ViewGroup的DispatchTouchEvent方法,如果顶级View拦截事件即onInterceptTouchEven方法返回true,则事件由顶级ViewGroup自己处理,这时候,如果ViewGroup的onTouchListener被设置了,则onTouchListener的onTouch()方法会被调用,否则onTouchEven方法会被调用,也就是说如果都提供的话,onTouchListener()方法会屏蔽onTouchEven方法(),在onTouchEven方法中如果设置了onClickListerner,则onClick方法会被调用.如果viewGroup不拦截事件,事件会分发给它的子View,这时子View的DispatchTouchEvent方法会被调用,到此事件已经从顶级VIewGroup传给了它的子view,即下来的传递过程和顶级ViewGroup的过程是一样的,如此循环完成整个事件的分发
说明:
1当子view通过设置父View不要拦截事件后即
getParent().requestDisallowInterceptTouchEvent(true);
父ViewGroup就拦截不了ACTION_MOVE和 ACTION_UP事件,但是 还是可以拦截ACTION_DOWN事件,
理由:ViewGrop会在ACTION_DOWN事件到来时做重置状态的操作,resetTouchState()方法中会对FLAG_DISALLOW_INTERCEPT进行重置,因此子view设置requestDisallowInterceptTouchEvent(true)不能影响ViewGroup对ACTION_DOWN事件的处理,源码如下
源码 ViewGroup#dispatchTouchEvent
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
4.View对点击事件的分发
源码: view#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
...省略代码
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
...省略代码
}
首先View会判断有没有设置OnTouchListener,如果有,则执行li.mOnTouchListener.onTouch(this, event)返回true,否则onTouchEvent(event)方法会被调用,我们在看一下onTouchEvent(event)方法里面的源码
源码:view#onTouchEvent(MotionEvent event)
public boolean onTouchEvent(MotionEvent event) {
...省略代码
switch (action) {
case MotionEvent.ACTION_UP:
...省略代码
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
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.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
...省略代码
}
由以上源码可知在 MotionEvent.ACTION_UP事件里 如果 if (!post(mPerformClick)) 条件为真的情况下,会执行performClick()方法
源码view#performClick()
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;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
在performClick() 里面如果 if (li != null && li.mOnClickListener != null) 条件为真的情况下,会执行 li.mOnClickListener.onClick(this);
至此,事件分发机制已经讲完啦~(容我喝口水,吃跟辣条休息一下,精彩马上继续^- ^)