总结
事件分发是DOWN事件进行的,只能在MOVE中处理事件冲突。
在正常情况下,事件从Activity
的dispatchTouchEvent
传到PhoneWindow
的dispatchTouchEvent
再传到DecorView
的dispatchTouchEvent
然后再传到ViewGroup
的dispatchTouchEvent
,在ViewGroup
的dispatchTouchEvent
中,有两个子方法,一是onInterceptTouchEvent
,还有一个是onTouchEvent
。当onInterceptTouchEvent
返回true
的时候,代表拦截事件,则执行onTouchEvent
方法,否则不管返回啥,都传到下一层。ACTION_DOWN
事件在哪个控件消费了(return true
), 那么ACTION_MOVE
和ACTION_UP
就会从上往下(通过dispatchTouchEvent
)做事件分发往下传,就只会传到这个控件,不会继续往下传,如果ACTION_DOWN
事件是在dispatchTouchEvent
消费,那么事件到此为止停止传递,如果ACTION_DOWN
事件是在onTouchEvent
消费的,那么会把ACTION_MOVE
或ACTION_UP
事件传给该控件的onTouchEvent
处理并结束传递。
针对单点触摸
一.首先得知道有什么事件
MotionEvent
注意:
①ACTION_MOVE
会多次触发(体现为源码中同一块代码会调用多次)。
②ACTION_CANCLE
先记住,是事件被上层拦截时触发,至于具体的,后面就知道了。
二.DOWN事件处理与分发
知道了有什么事件,下面就得知道谁能怎么处理事件
具体来说,就是继承自View的只能处理事件,继承自ViewGroup的才能分发事件。ViewGroup先要走分发流程,再走处理流程。而View只能走处理流程。看下图
1.事件传递的顺序:当一个事件发生的时候,它传递的顺序,可以用这么一张图来表示
触摸屏幕后,首先接触到事件的是Activity
,下面让我们从源码角度理解此流程
进入Activity
类的dispatchTouchEvent
方法
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
第一个if
不需要我们管,看第二个if
,它调用了PhoneWindow
的superDispatchTouchEvent
方法,让我们继续追踪
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
发现它调用了DecorView
的superDispatchTouchEvent
方法,我们继续追踪
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
DecorView
的super
是FrameLayout
,
但是由于FrameLayout
没有实现dispatchTouchEvent
方法,所以此方法最最终在ViewGroup
里面执行。我们追踪它,就进入了ViewGroup
的此方法
然后就开始事件分发了。事件分发在ViewGroup.java中。事件处理在View.java中,有两个方法,即dispatchTouchEvent和onTouchEvent。
那么就有人会问了,
View
的dispatchTouchEvent
和ViewGroup
的dispatchTouchEvent
有什么区别呢?其实是这样的,我们知道,ViewGroup
是继承View
的,所以它重写了View
的dispatchTouchEvent
方法,使得它具有事件分发功能。而具体如何分发,后面来分析。
2.一个小案例,理解View处理事件
插播一个小知识:什么是冲突?事件只有一个,但是有多个人想要处理,如果处理的人不是我们想要的那个人,我们就说发生了冲突
来看这样一个非常典型的例子:
比如这个例子,我们想让onTouch
处理,结果onClick
处理了,那么我们就说发生了冲突。经过测试我发现,当我在onTouch
方法中返回true
的时候,onClick
方法不执行,当返回false
的时候,onClick
方法执行。此时问题来了,什么时候onTouch
处理,什么时候onClick
处理呢?我们能否自己控制呢?
刚刚说了,事件处理在View
的dispatchTouchEvent
中,所以我们直接进入源码找答案
public boolean dispatchTouchEvent(MotionEvent event) {
....
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
....
return result;
}
可以通过源码发现,在第一个if
的最后一个条件之前,所有的条件都为true
。而我们的onTouch
方法就是重写了最后一个条件的那个语句,即li.mOnTouchListener.onTouch(this, event)
,当我们onTouch
返回true
的时候,result
赋值为true
,当返回false
的时候,result
也赋值为false
。然后我们看下一个if
。当是第一种情况的时候,result
为true
,!
之后为false
,所以onTouchEvent
方法不会执行。而当是第二种情况的时候,result
为false
,!
之后为true
,所以onTouchEvent
就会执行。这个和前面的测试结果好像很像,所以我们不妨做个大胆的推测,即onClick
方法就在onTouchEvent
方法中调用。口说无凭,我们追踪进入
public boolean onTouchEvent(MotionEvent event) {
...
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...
if (!post(mPerformClick)) {
performClickInternal();
}
...
我们知道onClick
方法是在ACTION_UP
的时候执行的,所以我们进入相应的case
语句,找到performClickInternal
方法(略去大量无关代码),追踪
private boolean performClickInternal() {
// Must notify autofill manager before performing the click actions to avoid scenarios where
// the app has a click listener that changes the state of views the autofill service might
// be interested on.
notifyAutofillManagerOnClick();
return performClick();
}
追踪performClick
public boolean performClick() {
...
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
...
}
终于在if
中找到了onClick
的调用。同时result
为true
,意为告诉父容器,此事件我已处理了。
刚刚我们通过一个小案例,一个小事件冲突,体会到了View
事件处理的机制以及流程,也就是说View
的事件处理就是onTouch
和onClick
。除此之外,我们也体会到了一个小的事件冲突的处理是怎样的。也就是说,这张图的红框部分我们已经解决了,接下来我们看ViewGroup
的分发事件,是如何处理的
3.ViewGroup的事件分发源码解析
我们不妨类比这么一张图
DecorView
其实就是个ViewGroup
。下面这些总监也是各种ViewGroup
。我们的事件分发其实就是在总经理这里开始分发,一层一层地来。
第一个层级:总经理,即ViewGroup
第二个层级:总监,也是ViewGroup
第三个层级:比如清洁工,有可能是View
,也可能是ViewGroup
比如这张图,如果清洁是ImageButton
那就是View
,如果它是清洁工的头,比如LinearLayout
,那么他就是ViewGroup
。但是我们这里为了方便理解清楚,默认规定为普通清洁工,即View
(其实都一样)。
我们首先看总经理,也就是ViewGroup
的事件分发。源码进入ViewGroup.java
,找到dispatchTouchEvent
下面是拦截的情况(也就是不分发,或者是分发了,但是都不处理。如果子View都不处理,也只能自己处理)
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
//-----------------------------------
//-----------------------------------
//如果是DOWN事件,则进行一些清0操作
//-----------------------------------
//-----------------------------------
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//-----------------------------------
//-----------------------------------
//下面办的事就是判断事件是否需要拦截
//-----------------------------------
//-----------------------------------
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;
}
...
//-----------------------------------
//-----------------------------------
//如果拦截了,就会走到这个if里面
//-----------------------------------
//-----------------------------------
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.