在Android系统中View的结构为树形结果,那么就会出现上层的view盖住下层的view的情况发生,而当我们点击上层View的时候,这个时候是上层的View还是盖住的下层的View可以消费这个touch事件呢?
而为了解决这个问题,就引出了Android中的Touch事件的分发机制.
而在Android中Touch事件的分发又分为View和ViewGroup的事件分发,由此我们先从简单的View的touch事件分发讲起:
首先,我们知道一次完整的Touch事件序列为:
ACTION_DOWN ----> ACTION_MOVE ----> ACTION_UP / ACTION_CANCEL(人为取消的情况)
而对于Touch事件的分发,不管是View还是ViewGroup都和一下的三个方法有关系:
dispatchTouchEvent():事件分发
onInterceptTouchEvent():事件拦截(只有ViewGroup才有该方法)
onTouchEvent():事件消费
1. dispatchTouchEvent 分发触摸事件 onInterceptTouchEvent() onTouchEvent()方法都是由dispatchTouchEvent()方法进行调用的
2. onInterceptTouchEvent 拦截触摸事件 表示是否拦截touch事件,true表示拦截 反之false表示不拦截
3. onTouchEvent 处理触摸事件 true表示消费事件,false表示没有消费
View的dispatchTouchEvent()方法源码分析如下:
//1.首先View的事件分发过程是从dispatchTouchEvent()方法开始的
//view的父容器将touch事件传递给View的dispatchTouchEvent()方法
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
//2.定义一个变量表示当前touch事件是否被该view消费
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
//3.首先判断li是否为空 一般情况是不为空null li是view的一个内部类里面定义了一些常用监听器接口的实例如 OnClickListener OnLongClickListener onTouchListener等....
//再判断li.mOnTouchListener是否为空如果我们对view设置了setOnTouchListener(new OnTouchListener())的话该变量也不会为空
//然后判断View是否为激活Enabled的状态
//最后调用我们设置的OnTouchListener()监听器的onTouch()方法并根据
//OnTouchListener()的onTohch()方法的返回值来决定是否 消费这次的touch事件
//如果onTouchListener()的onTouch()方法返回了true直接就返回true表示消费这次的touch事件,不再继续往下面走了
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//4.如果没有对View设置OnTouchListener()或者View为disable状态或者设置的OnTouchListener()的onTouch()方法返回false
//那么会去调用View的onTouchEvent()方法 由View的onTouchEvent()方法的返回值来决定是否消费此次touch事件
if (!result && onTouchEvent(event)) {
//如果onTouchEvent()方法返回true直接返回true表示消费了这次的touch事件 不在继续往下面走了
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
//如果OnTouchListener()的onTouch()方法返回false(即onTouchListener的onTouch()方法没有消费touch事件)
//同时View的onTouchEvent()方法也返回false表示view自身没有消费这次touch事件那么就直接返回result=false表示该view没有消费这次touch事件
//会将该touch事件交给其父容器的onTouchEvent()进行处理
return result;
}
View的onTouchEvent()方法源码分析如下:
//1.view的onTouchEvent()方法由view的dispatchTouchEvent()方法进行调用
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
//2.首先判断view的是否可点击cilck
//通过CLICKABLE LONG_CLICKABLE CONTEXT_CLICKABLE三个flag来决定cliable的值
//以上的三个分别表示view是否可以点击以及长点击
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//3.判断view是否是DISABLED未激活的状态
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
//如果View是未激活DISABLED的状态,但是只要该view的viewFlags 为CLICKABLE LONG_CLICKABLE CONTEXT_CLICKABLE中的一种的话
//那么该view还是可以消费掉这次touch事件的,只是不会响应我们设置的OnCLickListener的onClick()方法 onLongClickListener的onLongClick()方法等等
return clickable;
}
if (mTouchDelegate != null) {
//如果设置了TouchDelegate代理对象的话会直接将touch事件交给TouchDelegate对象处理直接返回true
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//如果View的clickable为true即view是可以进行点击的则进入相应的ACTION的判断
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
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)) {
//在ACTION_UP中执行了performClick()方法可以看到在performClick()方法中最终执行了OnClickListener的onClick()方法
//由此我们可以得出click点击事件是在ACTION_UP中触发的
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
//只要clickable为true即view只要是可点击的就会消费掉touch事件返回true
return true;
}
//反之就不消费touch事件返回false
return false;
}
View的Touch事件分发流程大致如下:
从View的touch事件传递流程得出以下几点:
1.OnTouchListener()的onTouch()方法是优先于View的OnTouchEvent()方法执行的如果OnTouchListener的onTouch()方法
返回了true表示消费了touch事件那么后续View的onTouchEvent()方法也就不会再执行了那么View的onClick() onLongClick()
等方法也就不会再接着执行了(onClick() onLongClick()等方法都是在onTouchEvnet()方法中进行执行的)
2.如果View是未激活的即处于DISABLED状态但是是可点击的(CLICKABLE LONG_CLICKABLE CONTEXT_CLICKALE)那么view也会
消费掉touch事件但是不会响应OnClickListener的onClick()方法 onLongClickListener的onLongCLick()方法等
3.只要是View可点击的并且处于ENABLED状态那么就一定返回true即一定会消费touch事件
4.在View的onTouchEvent()方法中处理我们常见的点击事件如:ACTION_DOWN 中处理长点击onLongClick()
ACTION_UP中处理点击onClick()等
5.onTouch() onTouchEvent()中事件是否被消费了由方法的返回值来决定 而不是由我们是否在方法中使用了
touch事件MotionEvent来决定的
6.View的事件调度顺序是dispatchTouchEvent() -> onTouchListener() -> onTouchEvent() -> onLongCLick() -> onClick()
7.View是没有onIterceptTouchEvent()即没有拦截touch事件的方法的,ViewGroup才有
8.View的onTouchEvent()方法中只要view是clickable(CLICKABLE LONG_CLICKABLE CONTEXT_CLICKABLE)的那么
就会消费这次touch事件(从ACTION_DOWN 到 ACTION_UP结束)
在安卓中有些控件默认是clickable的比如Button checkbox 等 而TextView LinearLayout等是non clickable的
说完View的Touch事件分发,接下类我们就开始将讲讲ViewGroup的Touch事件分发
1.ViewGroup的Touch事件是从Activity(或者通过WindowManager添加的view)的dispatchTouchEvent()方法开始
Activity的dispatchTouchEvent()方法如下:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//当touch事件的ACTION_DOWN到达Activity的dispatchTouchEvent()方法时会首先回调该方法(空实现) 我们可以根据业务需要实现一些特定的逻辑
//比如实现屏保功能,当此activity在栈顶时,触屏点击按home,back,menu键等都会触发此方法
onUserInteraction();
}
//获取Window接口的实现对象PhoneWindow 调用PhoneWindow对象的superDispatchTouchEvent()方法
if (getWindow().superDispatchTouchEvent(ev)) {
//如果superDispatchTouchEvent()方法返回true代表消费了此次touch事件 直接返回true
return true;
}
//反之如果superDispatchTouchEvent()返回false交由Activity的onTouchEvent()方法进行touch事件的处理
return onTouchEvent(ev);
}
PhoneWindow的superDispatchTouchEvent()方法如下:
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
PhoneWindow的superDispatchTouchEvent()方法最终会调用DecorView的superDispatchTouchEvent()方法:
public boolean superDispatchTouchEvent(MotionEvent event) {
//由于DecorView继承自FrameLayout的因此最终会调用ViewGroup的dispatchTouchEvent()方法
return super.dispatchTouchEvent(event);
}
ViewGroup的dispatchTouchEvent()方法:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
//定义一个变量表示是否消费这次的touch事件(ACTION_DOWN -> ACTINO_MOVE -> ACTION_UP)默认是不消费
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
//处理当MotionEvent为ACTION_DOWN的时候 ACTION_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.
//当开始一个新的触摸手势时(ACTION_DOWN为开始一个新的触摸手势),必须丢弃掉所有以前的状态。
//由于应用程序切换、ANR或其他一些状态变化系统可能已经删除了上一个手势的up或cancel事件(UP或者CANCEL为一次完整的touch事件的结束)
//因此我们需要在ACTION_DOWN即一次全新的touch事件开始时对一些变量进行重置操作
//最重要的是重置FLAG_DISALLOW_INTERCEPT为false(表示拦截touch事件)
// 将mFirstTouchTarget置为null 表示当前没有子view消费touch事件
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
//检查是否需要拦截touch事件
final boolean intercepted;
//如果为ACTION_DOWN 或者mFirstTouchTarget不为null空(即有子view消费touch事件)
//这里要注意这个mFirstTouchTarget 它是ViewGroup的一个内部类封装了被触摸的view以及这次触摸对应的id主要用于多点触控
//mFirstTouchTarget贯穿整个dispatchTouchEvent()的流程
//mFirstTouchTarget不为空表示有子View消费touch事件
//mFirstTouchTarget为空表示ViewGroup拦截了touch事件或者子view没有消费touch事件总之此时需要viewGroup自己处理touch事件(消费或者不消费)
//当actionMasked 不为ACTION_DOWN但是有子view消费touch事件时此时任然需要判断是否拦截touch事件(比如ACTION_MOVE ACTION_UP..)
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//获取FLAG_DISALLOW_INTERCEPT的值该值在上面的resetTouchState()方法中(ACTION_DOWN时)进行过重置为false
//我们知道子view可以通过getParent().requestDisallowInterceptTouchEvent(true)让ViewGroup禁止掉touch事件的拦截
//但是requestDisallowInterceptTouchEvent()针对ACTION_DOWN 是无效的只针对ACTION_MOVE ACTION_UP等才有效果
//因为在上面判断actionMasked为ACTION_DOWN时会调用resetTouchState()方法重置FLAG_DISALLOW_INTERCEPT为false
//因此会走到调用onInterceptTouchEvenet()方法中根据onInterceptTouchEvent()方法的返回值来决定是否拦截touch事件
//requestDisallowInterceptTouchEvent()方法的本质其实是通过改变flag值来控制最终是否调用onInterceptTouchEvent()方法来实现的
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//disallowIntercept为false会调用onInterceptTouchEvent()方法根据onInterceptTouchEvent()方法的返回值
//来决定是否拦截touch事件
if (!disallowIntercept) {
//调用onInterceptTouchEvent()方法来决定是否拦截touch事件
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
//反之disallowIntercept 为true则不进行touch事件的拦截将intercepted的值置为false
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
//如果不是ACTION_DOWN 并且没有子view消费掉touch事件此时直接拦截touch事件
//意思就是如果ACTION_DOWN 没有被子view消费那么接下来的ACTION_MOVE ACTINO_UP就都不会走onInterceptTouchEvent()方法
//判断是否需要拦截touch事件了而是直接将intercepted设置为true直接进行拦截交给自身进行touch事件的处理(dispatchTouchEvent() -> onTouch() -> onTouchEvent())
intercepted = true;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// Check for cancelation.
//检查cancel状态
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
//如果Touch事件没有被ACTION_CANCEL取消 或者touch事件没有被拦截就开始执行Touch事件的分发
//注意在if (!canceled && !intercepted) {} 的过程中只针对ACTION_DOWN的分发不针对ACTION_MOVE ACTION_UP的分发
if (!canceled && !intercepted) {
// If the event is targeting accessiiblity focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
//如果为ACTION_DOWN执行ACTION_DOWN的分发
if (actionMasked == MotionEvent.ACTION_DOWN
//ACTION_POINTER_DOWN为多点触控
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
//计算touch点的坐标根据touch点坐标判断当前触摸到的是哪个子view
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
//对可能需要接收touch事件的子view进行一个接收顺序的排序 从前往后 即从最上层往最下层进行排序
final ArrayList preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//注意是从后往前进行遍历 最后面的child是最上层的view
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
//此处的break表示当newTouchTarget != null即已经找到了接收touch事件的子view 不用再继续遍历寻找接收touch事件的目标view了
//即此时手指已经按在了一个button上(ACTION_DOWN)此时又有一个手指按在了这个button上(ACTION_POINTER_DOWN),那么接收
//touch事件的还是这个button只是会给这个button新增一个pointerId而已
break;
}
resetCancelNextUpFlag(child);
//在dispatchTransformedTouchEvent()方法中执行touch事件的分发
//dispatchTransformedTouchEvent()分发touch事件有两种情况
//dispatchTransformedTouchEvent()中调用了child.dispatchTouchEvent()方法
//根据child.dispatchTouchEvent()方法的返回值判断子view是否消费了touch事件
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//通过addTouchTarget()方法将消费了touch事件的child view赋值给
//mFirstTouchTarget此时mFirstTouchTarget不为null空
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//标记alreadyDispatchedToNewTouchTarget为true表示此时已经将touch事件分发给了子view即子view消费了touch事件
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
//在此处再执行touch事件的分发
// Dispatch to touch targets.
//如果mFirstTouchTarget == null表示ViewGroup拦截了touch事件或者在上面遍历寻找接收touch事件的子view
//没有找到或者找到了接收touch事件的子view但是子view没有消费掉touch事件
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
//如果没有找到接收touch事件的子view那么调用dispatchTransformedTouchEvent()方法
//在dispatchTransformedTouchEvent()方法内部执行super.dispatchTouchEvent(ev)
//即将ViewGroup当做一个普通的view调用View的dispatchTouchEvent()方法(ViewGroup继承自View)
//那么最终的调用顺序是onTouch()(如果设置了OnTouchListener()) -> onTouchEvent()方法由自身来决定是否消费touch事件
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;
//mFirstTouchTarget 不为空null 表示已经找到了接收touch事件的child view
while (target != null) {
//遍历TouchTarget链表 获取链表中的每一个目标view
final TouchTarget next = target.next;
//alreadyDispatchedToNewTouchTarget 表示是否找到了接收touch事件的子view
//此时mFirstTouchTarget不为空表示肯定执行过上面的遍历寻找接收touch事件的子view
//的流程此时会将alreadyDispatchedToNewTouchTarget置为true同时调用了addTouchTarget()方法将
//接收touch事件的子view添加进TouchTarget链表的头部同时将mFirstTouchTarget 指向了newTouchTarget
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
//此时已经在上面的遍历寻找接收touch事件的子view的过程中找到了接收touch事件的子view
//同时子view也已经消费了ACTION_DOWN事件因此此时不执行任何操作直接返回true
handled = true;
} else {
//通过intercepted来判断是否拦截取消了touch事件的分发
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//此时没有执行过上面的遍历寻找接收touch事件的子view的流程,但是此时已经找到了
//接收touch事件的子view因此流程是如果子view处理了ACTION_DOWN 事件在接下来的
//ACTION_MOVE ACTION_UP事件到来时会跳过上面的寻找接收touch事件的子view的流程
//而是直接进入当前的这步流程中执行ACTION_MOVE ACTION_UP事件的分发
//因为在上面寻找接收touch事件的子view的流程中一开始就有actionMasked == MotionEvent.ACTION_DOWN 的判断
//如果不是ACTION_DOWN事件就不会执行遍历寻找接收touch事件的流程也就不会分发ACTION_MOVE ACTION_UP这些touch事件
//如果cancelChild为true即ViewGroup拦截了ACTION_DOWN之后的ACTION_MOVE ACTION_UP等事件
//那么在dispatchTransformedTouchEvent()方法中会生成一个ACTION_CANCEL事件分发给子view
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
//同时如果ViewGroup拦截取消了touch事件分发会发送一个ACTION_CANCEL事件给子view的同时
//还会将mFirstTouchTarget置为null空,那么当下次touch事件来临时就直接走ViewGroup自身的onTouch() -> onTouchEvent()方法的流程
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
//在ACTION_UP 手指抬起或者ACTION_CANCEL取消touch事件分发恢复重置一些状态
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
//返回handled的值 父容器根据返回的handled的值判断viewgroup/view是否消费掉了touch事件
return handled;
}
通过以上的源码分析我们得出ViewGroup的Touch事件分发的流程:
另外Activity View ViewGroup这三者的touch事件的方法区别如下:
Activity | View | ViewGroup | |
dispatchTouchEvent()(事件分发) | 有 | 有 | 有 |
onInterceptTouchEvent()(事件拦截) | 无 | 无 | 有 |
onTouchEvent()(事件消费) | 有 | 有 | 有 |
整个touch事件的传递过程为: Activity.dispatchTouchEvent() -> PhoneWindow.superDispatchTouchEvent() -> DecorView.superDispatchTouchEvent() -> ViewGroup.dispatchTouchEvent() -> View.dispatchTouchEvent()
而消费过程则相反: View.onTouchEvent() -> ViewGroup.onTouchEvent() -> DecorView.onTouchEvent() -> Activity.onTouchEvent()
ViewGroup中包含多个子view时会将touch事件分配给包含在点击位置处的子view
ViewGroup和子view同时注册了监听器OnClickListener 监听事件由子view进行消费
在一次完整的touch事件(ACTION_DOWN -> ACTION_MOVE -> ACTION_UP)传递过程中,touch事件应该被
同一个view进行消费,全部接受或者全部拒绝
只要接受ACTION_DOWN事件就意味着接受所有的事件,拒绝接受ACTION_DOWN 则不会接受后续的内容
如果当前正在处理的touch事件被上层的view拦截,会接收到一个ACTION_CANCEL,后续事件不会再传递过来
父容器拿到触摸事件,默认不拦截(onInterceptTouchEvent() return false),分发给孩子(dispatchTransformedTouchEvent()),
看孩子是否消费, 孩子不消费,事件又传回父容器(onTouchEvent()),看父容器是否消费。
* 父容器拿到触摸事件,如果ACTION_DWON事件传递下去没有孩子消费,那么后续的事件就不会传了。(没有消费mFirstTouchTarget == null)
* 父容器拿到触摸事件,默认不拦截,分发给孩子,看孩子是否消费,孩子消费,事件传递结束。
* 父容器拿到触摸事件,拦截,事件不会分发给孩子,交给自己是否消费(调用父容器自己的onTouchEvent)
父容器拦截touch事件要分为两种:
1.拦截ACTION_DOWN 直接导致mFirstTouchTarget为null 那么直接调用ViewGroup的父类即View类中的dispatchTouchEvent()
方法即dispatchTouchEvent() -> onTouch() -> onTouchEvent()此时会将ViewGroup当做一个普通的View进行处理
2.拦截ACTION_DOWN之后的ACTION_MOVE ACTION_UP事件如果拦截这些事件中的一个事件会将该事件转换成一个
ACTION_CANCEL给消费了这个事件的子view 因此本次的touch事件是不会 传递给拦截了touch事件的viewgroup的而是
当下次touch事件到来时才会传递给该viewgroup的onTouchEvent()方法来处理并且会将mFirstTouchTarget置为null 当下次
touch事件到来时由于mFirstTouchTatget为Null会直接调用自己的onTouchEvent()方法 而不会在传递个子view了
另外还有就是关于setOnTouchListener与 onTouchEvent的关系:
setOnTouchListener的onTouch方法和onTouchEvent()方法都会在我们View的dispatchTouchEvent()方法里面执行
不过onTouch()方法会优先于我们的onTouchEvent()方法而且如果OnTouchListener不为空直接执行onTouch()方法
并且直接返回true就不会在调用onTouchEvent()方法了
1. 先后关系:onTouch先于onTouchEvent执行
2. 如果onTouch返回true,表示消费,onTouchEvent就不会执行。
最后就是Android中的Touch事件的冲突如何解决的问题,关于事件冲突大家可以看看下面这篇博客:
https://www.jianshu.com/p/fd528c2311da