如果要查看ACTION_MOVE与ACTION_UP的事件传递机制,查看Android事件分发之ACTION_MOVE与ACTION_UP的传递机制
在阅读ViewGroup事件分发相关源码过程中,有时候会见到ACTION_CANCEL
这一事件。那么这一事件是如何产生的呢?按照网上的说法,当手指从当前view移出后,当前view就会收到ACTION_CANCEL这一事件,这一定是正确的吗?下面我们来看两个例子:
例子1:
import android.content.Context
import android.support.constraint.ConstraintLayout
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
class CustomViewGroup @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
var hasInterceptMoveEvent = false
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
// Log.d("TAG", "${ev?.action}:CustomViewGroup onInterceptTouchEvent")
if (!hasInterceptMoveEvent && ev?.action == MotionEvent.ACTION_MOVE) {
hasInterceptMoveEvent = true
return true
}
return false
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
Log.d("TAG", "${getAction(ev?.action)}:CustomViewGroup dispatchTouchEvent")
return super.dispatchTouchEvent(ev)
}
}
我们自定义一个ViewGroup,覆盖它的onInterceptTouchEvent
方法和dispatchTouchEvent
方法。在onInterceptTouchEvent
方法中我们只拦截了一次MotionEvent.ACTION_MOVE
事件。hasInterceptMoveEvent
用于控制只拦截一次。而在dispatchTouchEvent
方法中,我们打印出当前ViewGroup处理的事件。看下getAction
方法定义:
fun getAction(action: Int?): String {
return when (action) {
MotionEvent.ACTION_DOWN -> "MotionEvent.ACTION_DOWN"
MotionEvent.ACTION_MOVE -> "MotionEvent.ACTION_MOVE"
MotionEvent.ACTION_UP -> "MotionEvent.ACTION_UP"
MotionEvent.ACTION_CANCEL -> "MotionEvent.ACTION_CANCEL"
else -> "OTHER"
}
}
其实就是根据Action对应的Int值转化为字符串,让我们的Log更加直观。
而在ViewGroup内部,我们放置了一个自定义Button。代码如下:
class CusButton @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : Button(context, attrs, defStyleAttr) {
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
Log.d("TAG", "${getAction(event?.action)}:CusButton dispatchTouchEvent")
return true
}
}
这个自定义Button只是将当前dispatchTouchEvent
方法收到的事件打印出来。
手指按住button,然后移动,移出button外,然后松开。我们看下打印出的Log:
04-23 15:13:48.553 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_DOWN:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.553 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_DOWN:CusButton dispatchTouchEvent
04-23 15:13:48.574 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.574 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_CANCEL:CusButton dispatchTouchEvent
04-23 15:13:48.591 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.607 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.624 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.642 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.658 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.674 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.691 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.708 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.724 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.741 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.758 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.775 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.791 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.808 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.825 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.841 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.859 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.866 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:13:48.867 22928-22928/com.lee.myapplication D/TAG: MotionEvent.ACTION_UP:CustomViewGroup dispatchTouchEvent
我们看到CusButton确实收到了一个ACTION_CANCEL
事件,并且在这个事件之后,CusButton并没有收到任何事件。所以我们大概能够猜到:如果ViewGroup拦截了Move事件,那么这个Move事件将会转化为Cancel事件传递给子view。
例子2:
如果我们的ViewGroup不拦截Move事件,那么是否也会产生Cancel事件呢?
我们修改一下CustomViewGroup
源码:
class CustomViewGroup @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
// var hasInterceptMoveEvent = false
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
// Log.d("TAG", "${ev?.action}:CustomViewGroup onInterceptTouchEvent")
// if (!hasInterceptMoveEvent && ev?.action == MotionEvent.ACTION_MOVE) {
// hasInterceptMoveEvent = true
// return true
// }
return false
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
Log.d("TAG", "${getAction(ev?.action)}:CustomViewGroup dispatchTouchEvent")
return super.dispatchTouchEvent(ev)
}
}
即onInterceptTouchEvent
方法不拦截事件。同样的操作我们再来看下Log:
04-23 15:29:24.089 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_DOWN:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.089 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_DOWN:CusButton dispatchTouchEvent
04-23 15:29:24.104 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.105 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.121 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.121 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.139 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.139 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.154 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.155 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.171 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.171 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.188 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.188 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.205 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.205 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.221 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.221 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.233 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.233 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.250 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.250 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.268 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.268 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.275 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.276 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_MOVE:CusButton dispatchTouchEvent
04-23 15:29:24.276 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_UP:CustomViewGroup dispatchTouchEvent
04-23 15:29:24.276 24484-24484/com.lee.myapplication D/TAG: MotionEvent.ACTION_UP:CusButton dispatchTouchEvent
我们可以看到Button不会收到Cancel事件。即时手指滑出了button,仍然可以收到Move和Up事件。
我们知道如果某一个子View处理了Down事件,那么随之而来的Move和Up事件也会交给它处理。但是交给它处理之前,父View还是可以拦截事件的,如果拦截了事件,那么子View就会收到一个Cancel事件,并且不会收到后续的Move和Up事件。
下面我们通过源码验证上面这段话:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null
) {
//----------源码1-----------
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;
}
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
// ...
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// ...
} else {
// ---------------源码2----------------
// 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;
//-------------- 源码3---------
if (dispatchTransformedTouchEvent(
ev, cancelChild,
target.child, target.pointerIdBits
)
) {
handled = true;
}
//-------------- 源码4---------
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
return handled;
}
这段代码进行了一定的精简,但是大致流程还是很清楚的。当子view处理完Down事件之后,mFirstTouchTarget
不为null,那么是可以走到源码1处的,如果子view没有对ViewGroup进行不拦截的设置,那么disallowIntercept
为false,此时会走到onInterceptTouchEvent
方法,如果我们拦截了Move事件,那么onInterceptTouchEvent
返回true。也就是说intercepted
为true;
此时会走到源码2处,遍历每一个将要处理事件的view,alreadyDispatchedToNewTouchTarget
表示事件是否已经交给view处理,此时当然是false。到源码3处时,由于intercepted
为true,所以cancelChild
为true,我们看下dispatchTransformedTouchEvent
方法:
/**
* Transforms a motion event into the coordinate space of a particular child view,
* filters out irrelevant pointer ids, and overrides its action if necessary.
* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
*/
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
// 。。。
}
注意,走到dispatchTransformedTouchEvent
时,cancel这个参数为true。通过event.setAction(MotionEvent.ACTION_CANCEL);
这句话将MotionEvent的Action设置为了ACTION_CANCEL
,并且交给子view处理。
通过这里我源码我们知道了ACTION_CANCEL
的产生过程。那么为什么产生ACTION_CANCEL
后,子view无法收到后续事件了呢?
在源码4处,有这样一段代码:
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
cancelChild为true,会将当前target节点从链表中删除。那么后续事件到来时,view在mFirstTouchTarget
链表中不存在,自然就不会交给它处理了。
我们通过上面的代码可以看出,即时是MOVE和UP事件,在传递给子View之前也是可以通过ViewGroup的onInterceptTouchEvent
方法拦截的,如果拦截了,那么该事件就会变成Cancel事件传递给子view。
那么是否有办法,子view不让ViewGroup拦截时间呢?
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;
}
可以看到disallowIntercept
变量为true的时候,会跳过onInterceptTouchEvent
方法。换句话如果设置了FLAG_DISALLOW_INTERCEPT
这个flag,那么ViewGoup则不会拦截Move和Up事件。
我们再找到设置FLAG_DISALLOW_INTERCEPT
这个flag的地方。
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
这段代码,充分展示了位运算的强大即高效。子View可以通过设置requestDisallowInterceptTouchEvent(true)
来达到禁止父ViewGroup拦截事件的目的。
但是需要注意的是,FLAG_DISALLOW_INTERCEPT
这个flag无法对Down事件生效。因为在Down时,会清空FLAG_DISALLOW_INTERCEPT
。
// 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();
}
/**
* Resets all touch state in preparation for a new cycle.
*/
private void resetTouchState() {
clearTouchTargets();
resetCancelNextUpFlag(this);
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
mNestedScrollAxes = SCROLL_AXIS_NONE;
}