前言:欢迎大家给我指出错误,一起进步。谢谢,也希望大家能认真看。
如果没有看Activity、View、Window之间关系的朋友,推荐先看一下,因为清楚了Activity与View之间的关系,对于触摸事件的传递理解起来也更容易!Activity、View、Window之间关系:http://blog.csdn.net/u011733020/article/details/49465707
关于Touch 时间的分发响应文章有很多了,但是自己理一遍的印象 要比看别人的深刻,这里自己记一下,也便与以后回过头来翻!
Touch 事件 对于 新手来说,是很困扰的,因为事件之间的传递过程,我们还不理解,去翻看代码,翻着 翻着,反而越发不清晰,这里 我跟大伙一起理一下传递机制。
首先 ,一点大家需要知道 所谓的 Touch事件包含的几个动作:
MotionEvent.ACTION_DOWN :手指按下
MotionEvent.ACTION_UP :手指离开屏幕
MotionEvent.ACTION_MOVE :手指在屏幕上滑动
有几个方法:
要了解事件分发过程,肯定是从手机触摸到屏幕这个动作发起的,所以 最先开始的肯定是 ACTION_DOWN 这个动作的捕获。捕获这个动作后,再去传递给要消费该动作的具体View。
那就从简单地Demo开始了解一下View的触摸事件,这里比较基础,如果你对触摸比较了解,可以跳过。
我们定义一个RelativeLayout 包裹一个ImageView 的简单布局:
图一
我们实现Activity的dispatchTouchEvent(MotionEvent ev)和onTouchEvent(MotionEvent event)这两个分方法。
ImageView的dispatchTouchEvent(MotionEvent event)、onTouchEvent(MotionEvent event)以及实现setOnTouchListener
、setOnClickListener两个监听事件。
RelativeLayout的dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)和onTouchEvent(MotionEvent event)这三个方法,同时监听RelativeLayout的setOnTouchListener、setOnClickListener事件。
将程序运行到手机上,我们先点击一下图片以外的地方,看一下打印的Log信息:
点击布局10-29 11:40:24.509: E/MainActivity(29973): dispatchTouchEvent:--------------MainActivity 10-29 11:40:24.519: E/TouchRelativeLayout(29973): MotionEvent.ACTION_DOWN--------------dispatchTouchEvent10-29 11:40:24.519: E/TouchRelativeLayout(29973): onInterceptTouchEvent10-29 11:40:24.519: E/TouchRelativeLayout(29973): onInterceptTouchEvent: false10-29 11:40:24.519: E/TouchRelativeLayout(29973): MotionEvent.ACTION_DOWN--------------onTouch10-29 11:40:24.519: E/TouchRelativeLayout(29973): MotionEvent.ACTION_DOWN--------------onTouchEvent10-29 11:40:24.519: E/TouchRelativeLayout(29973): super.onTouchEvent(event) :true10-29 11:40:24.519: E/TouchRelativeLayout(29973): super.dispatchTouchEvent(event) :true10-29 11:40:24.519: E/MainActivity(29973): dispatchTouchEvent:--------------MainActivity :true抬手10-29 11:40:30.649: E/MainActivity(29973): dispatchTouchEvent:--------------MainActivity 10-29 11:40:30.649: E/TouchRelativeLayout(29973): MotionEvent.ACTION_UP--------------dispatchTouchEvent10-29 11:40:30.649: E/TouchRelativeLayout(29973): MotionEvent.ACTION_UP--------------onTouch10-29 11:40:30.649: E/TouchRelativeLayout(29973): MotionEvent.ACTION_UP--------------onTouchEvent10-29 11:40:30.649: E/TouchRelativeLayout(29973): super.onTouchEvent(event) :true10-29 11:40:30.649: E/TouchRelativeLayout(29973): super.dispatchTouchEvent(event) :true10-29 11:40:30.649: E/MainActivity(29973): dispatchTouchEvent:--------------MainActivity :true10-29 11:40:30.649: E/TouchRelativeLayout(29973): OnClickListener--------------
我们分析一下,传递流程,首先 Activity 的 dispatchTouchEvent 方法手机按下这一事件给RelativeLayout布局,RelativeLayout这个布局判断是否要拦截该事件的传递,false 不拦截,由于点击的是ImageView 以外的部分,所以该事件传递到RelativeLayout布局上面,就已经找到了targetView,OK,接下来RelativeLayout处理掉这个按下的动作(响应了onTouch和onTouchEvent),并且RelativeLayout的dispatchTouchEvent方法返回true,方法结束,通知Activity 的dispatchTouchEvent方法 ,OK,事件已经被消费掉了,这个按下的动作就响应完了。
当手指抬起的时候,同样的道理,首先Activity 先知道手指抬起来了,然后通过dispatchTouchEvent 方法将这一动作往内传递。传递给RelativeLayout去分发这个抬手事件,然而RelativeLayout在这个手机抬起的范围内,只有自己能消费这一动作,由于ImageView不在手指抬起范围内,所以ImageView不能消费这一事件,故而 RelativeLayout 在dispatchTouchEvent方法判断,只能到自己这里,因此交给onTouch和onTouchEvent 去消费该事件,同时返回true 告诉Activity已经分发消费掉该事件。
另外大家都注意到最后执行OnClickListener,为什么最后执行了OnClickListener?我们这里想一下,我们手指抬起的时候都执行了dispatchTouchEvent、onTouch和onTouchEvent。OK 这里dispatchTouchEvent是分发事件找到可以消费的View,故 onClick事件的产生应该与这个方法没有多大关系,这样只剩下onTouch和onTouchEvent了,肯定其中一个 执行了onClick!,这里我们大胆的靠直觉来说,由于onTouch 是setOnTouchListener 的回调,因此也不是这个方法调用了onClick方法,那现在就只有onTouchEvent中执行了onClick方法咯,到底是不是这样呢?
我们知道我们实现的setOnTouchListener 默认返回的是false,如果我们改成 return true,我们在重复上面的动作会发生什么?好,看下我们改成true 后的Log 信息:
10-29 16:16:50.092: E/MainActivity(9735): dispatchTouchEvent:--------------MainActivity 10-29 16:16:50.092: E/TouchRelativeLayout(9735): MotionEvent.ACTION_DOWN--------------dispatchTouchEvent10-29 16:16:50.092: E/TouchRelativeLayout(9735): onInterceptTouchEvent10-29 16:16:50.092: E/TouchRelativeLayout(9735): MotionEvent.ACTION_DOWN--------------onTouch10-29 16:16:50.092: E/TouchRelativeLayout(9735): super.dispatchTouchEvent(event) :true10-29 16:16:50.092: E/MainActivity(9735): dispatchTouchEvent:--------------MainActivity :true10-29 16:16:52.692: E/MainActivity(9735): dispatchTouchEvent:--------------MainActivity 10-29 16:16:52.692: E/TouchRelativeLayout(9735): MotionEvent.ACTION_UP--------------dispatchTouchEvent10-29 16:16:52.692: E/TouchRelativeLayout(9735): MotionEvent.ACTION_UP--------------onTouch10-29 16:16:52.692: E/TouchRelativeLayout(9735): super.dispatchTouchEvent(event) :true10-29 16:16:52.692: E/MainActivity(9735): dispatchTouchEvent:--------------MainActivity :true
诶,最前面我们最后执行的是onClick 事件,这里的onClick并没有执行。。。并且onTouchEvent也没有执行。。我们先想想,首先是Activity 分发给RelativeLayout,RelativeLayout分发给自己消费,当我们设置了setOnTouchListener,return true。以后就没有执行onTouchEvent 方法,那么我们是不是应该去RelativeLayout的dispatchTouchEvent方法里面看看,是不是把我们的事件给拦截掉了?,我们看一下ViewGroup的dispatchTouchEvent方法:
@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) { ...... boolean handled = false; ...... final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; // 1)处理初始的ACTION_DOWN if (actionMasked == MotionEvent.ACTION_DOWN) { // 把ACTION_DOWN作为一个Touch手势的始点,清除之前的手势状态。 cancelAndClearTouchTargets(ev); //清除前一个手势,*关键操作:mFirstTouchTarget重置为null* resetTouchState(); //重置Touch状态标识 } // 2)检查是否会被拦截 final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { // 是ACTION_DOWN的事件,或者mFirstTouchTarget不为null(已经找到能够接收touch事件的目标组件) final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; // 判断禁止拦截的FLAG,因为requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法可以禁止执行是否需要拦截的判断 if (!disallowIntercept) { // 禁止拦截的FLAG为false,说明可以执行拦截判断,则执行此ViewGroup的onInterceptTouchEvent方法 intercepted = onInterceptTouchEvent(ev); // 此方法默认返回false,如果想修改默认的行为,需要override此方法,修改返回值。 ev.setAction(action); } else { // 禁止拦截的FLAG为ture,说明没有必要去执行是否需要拦截了,这个事件是无法拦截的,能够顺利通过,所以设置拦截变量为false intercepted = false; } } else { // 当不是ACTION_DOWN事件并且mFirstTouchTarget为null(意味着没有touch的目标组件)时,这个ViewGroup应该继续执行拦截的操作。 intercepted = true; } // 通过前面的逻辑处理,得到了是否需要进行拦截的变量值 final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; if (!canceled && !intercepted) { // 不是ACTION_CANCEL并且拦截变量为false if (actionMasked == MotionEvent.ACTION_DOWN) { // 在ACTION_DOWN时去寻找这次DOWN事件新出现的TouchTarget final int actionIndex = ev.getActionIndex(); // always 0 for down ..... final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { // 根据触摸的坐标寻找能够接收这个事件的子组件 final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); final View[] children = mChildren; // 逆序遍历所有子组件 for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = i; final View child = children[childIndex]; // 寻找可接收这个事件并且组件区域内包含点击坐标的子View if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; } newTouchTarget = getTouchTarget(child); // 找到了符合条件的子组件,赋值给newTouchTarget ...... // 把ACTION_DOWN事件传递给子组件进行处理 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // 如果此子ViewGroup消费了这个touch事件 mLastTouchDownTime = ev.getDownTime(); mLastTouchDownIndex = childIndex; mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); // 则为mFirstTouchTarget赋值为newTouchTarget,此子组件成为新的touch事件的起点 newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } } } ...... } } // 经过前面的ACTION_DOWN的处理,有两种情况。 if (mFirstTouchTarget == null) { // 情况1:(mFirstTouchTarget为null) 没有找到能够消费touch事件的子组件或者是touch事件被拦截了, // 那么在ViewGroup的dispatchTransformedTouchEvent方法里面,处理Touch事件则和普通View一样, // 自己无法消费,调用super.dispatchOnTouchEvent()把事件回递给父ViewGroup进行处理 handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { // 情况2:(mFirstTouchTarget!=null) 找到了能够消费touch事件的子组件,那么后续的touch事件都可以传递到子View TouchTarget target = mFirstTouchTarget; // (这里为了理解简单,省略了一个Target List的概念,有需要的同学再查看源码) while (target != null) { if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { // 如果前面利用ACTION_DOWN事件寻找符合接收条件的子组件的同时消费掉了ACTION_DOWN事件,这里直接返回true handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; // 对于非ACTION_DOWN事件,则继续传递给目标子组件进行处理(注意这里的非ACTION_DOWN事件已经不需要再判断是否拦截) if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { // 如果target子组件进行处理,符合某些条件的话,会传递ACTION_CANCEL给target子组件 // 条件是:如果ACTION_DOWN时没有被拦截,而后面的touch事件被拦截,则需要发送ACTION_CANCEL给target子组件 handled = true; } ...... } } } if (canceled || actionMasked == MotionEvent.ACTION_UP) { // 如果是ACTION_CANCEL或者ACTION_UP,重置Touch状态标识,mFirstTouchTarget赋值为null,后面的Touch事件都无法派发给子View resetTouchState(); } ...... return handled;}OK 注释很详细,感谢这位朋友类:http://hukai.me/android-deeper-touch-event-dispatch-process/,继续不废话,看代码,不懂得看三遍,重要的事情说三遍。。。三遍。。。遍!!!
这里大体捋一捋ViewGroup的dispatchTouchEvent 方法,手抬起是Action_Up 方法。故往下看,判断是Action_Down的可以都不看啦,有一段代码 if(mFirstTouchTraget==null),注释写的很详细,我们这里是找到了RelativeLayout 因此not null。继续往下执行 while(target!=null) 因为是Action_Up走到else里面去执行了dispatchTransformedTouchEvent方法,这里进去看一下做了什么操作,是不是 执行了onClick??让源码给我们揭晓答案吧!
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; } // Calculate the number of pointers to deliver. final int oldPointerIdBits = event.getPointerIdBits(); final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; // If for some reason we ended up in an inconsistent state where it looks like we // might produce a motion event with no pointers in it, then drop the event. if (newPointerIdBits == 0) { return false; } // If the number of pointers is the same and we don't need to perform any fancy // irreversible transformations, then we can reuse the motion event for this // dispatch as long as we are careful to revert any changes we make. // Otherwise we need to make a copy. final MotionEvent transformedEvent; if (newPointerIdBits == oldPointerIdBits) { if (child == null || child.hasIdentityMatrix()) { if (child == null) { handled = super.dispatchTouchEvent(event); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; event.offsetLocation(offsetX, offsetY); handled = child.dispatchTouchEvent(event); event.offsetLocation(-offsetX, -offsetY); } return handled; } transformedEvent = MotionEvent.obtain(event); } else { transformedEvent = event.split(newPointerIdBits); } // Perform any necessary transformations and dispatch. if (child == null) { handled = super.dispatchTouchEvent(transformedEvent); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; transformedEvent.offsetLocation(offsetX, offsetY); if (! child.hasIdentityMatrix()) { transformedEvent.transform(child.getInverseMatrix()); } handled = child.dispatchTouchEvent(transformedEvent); } // Done. transformedEvent.recycle(); return handled; }代码比较长,不一行一行看,挑出下面这一部分来看
// Perform any necessary transformations and dispatch. if (child == null) { handled = super.dispatchTouchEvent(transformedEvent); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; transformedEvent.offsetLocation(offsetX, offsetY); if (! child.hasIdentityMatrix()) { transformedEvent.transform(child.getInverseMatrix()); } handled = child.dispatchTouchEvent(transformedEvent); }OK。我们不关系 child 是什么,看这个方法我们看到不论是 if 还是else 都执行了dispatchTouchEvent方法。
public boolean dispatchTouchEvent(MotionEvent event) {if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); } if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { return true; } if (onTouchEvent(event)) { return true; } } if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } return false; }
相信这个方法,我们都自己翻看过很多次,这个方法的第九行,在if判断了mOnTouchListener!=null ,mOnTouchListener是在哪里赋值呢?
public void setOnTouchListener(OnTouchListener l) { getListenerInfo().mOnTouchListener = l; }看,就是我们设置的setOnTouchListener,传递进去的OnTouchListener,只要我们注册了Touch事件,这个mOnTouchListener 就是非null 的。然后执行了mOnTouchListener 的onTouch, 我们在setOnTouchListener的时候我们把返回值改成了true(默认false)。因为if条件满足,直接返回true, 下面的if(onTouchEvent(event))就执行不到了!,所以跟我们测试的结果一样,当我们把setOnTouchListener返回值改为true 的时候 onTouchEvent 没有执行,但是前面onClick不是也没有执行吗?不要急,我们一起看一下onTouchEvent方法做了什么吧。
/** * Implement this method to handle touch screen motion events. * * @param event The motion event. * @return True if the event was handled, false otherwise. */ public boolean onTouchEvent(MotionEvent event) { final int viewFlags = mViewFlags; if ((viewFlags & ENABLED_MASK) == DISABLED) { if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); } 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: 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); } if (!mHasPerformedLongPress) { // 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(); } } } 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(); } break; case MotionEvent.ACTION_DOWN: mHasPerformedLongPress = false; 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(); } postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { // Not inside a scrolling container, so show the feedback right away setPressed(true); checkForLongClick(0); } break; case MotionEvent.ACTION_CANCEL: setPressed(false); removeTapCallback(); removeLongPressCallback(); break; case MotionEvent.ACTION_MOVE: final int x = (int) event.getX(); final int y = (int) event.getY(); // Be lenient about moving outside of buttons if (!pointInView(x, y, mTouchSlop)) { // Outside button removeTapCallback(); if ((mPrivateFlags & PFLAG_PRESSED) != 0) { // Remove any future long press/tap checks removeLongPressCallback(); setPressed(false); } } break; } return true; } return false; }
代码比较长,这里摘出Action_Up中的一部分代码
if (!mHasPerformedLongPress) { // 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(); } } }
里面执行了PerformClick,大家都知道PerformClick 会调用Click方法
public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); return true; } return false; }前提是onClickListener我们注册过。
好了。到这里我们得出了结论,也看晕了!!画个图表示一下整个过程加深一下。
图二
如果是点击我们图一中的图片呢?这里我简单说一下,先看一下Log信息:
点击布局中的图片10-29 11:41:05.839: E/MainActivity(29973): dispatchTouchEvent:--------------MainActivity 10-29 11:41:05.839: E/TouchRelativeLayout(29973): MotionEvent.ACTION_DOWN--------------dispatchTouchEvent10-29 11:41:05.839: E/TouchRelativeLayout(29973): onInterceptTouchEvent10-29 11:41:05.839: E/TouchRelativeLayout(29973): onInterceptTouchEvent: false10-29 11:41:05.839: E/TouchImageView(29973): MotionEvent.ACTION_DOWN--------------dispatchTouchEvent10-29 11:41:05.839: E/TouchImageView(29973): MotionEvent.ACTION_DOWN--------------onTouch10-29 11:41:05.839: E/TouchImageView(29973): MotionEvent.ACTION_DOWN--------------onTouchEvent10-29 11:41:05.839: E/TouchImageView(29973): super.onTouchEvent(event) :true10-29 11:41:05.839: E/TouchImageView(29973): super.dispatchTouchEvent(event) :true10-29 11:41:05.839: E/TouchRelativeLayout(29973): super.dispatchTouchEvent(event) :true10-29 11:41:05.839: E/MainActivity(29973): dispatchTouchEvent:--------------MainActivity :true这个跟前面的差不多,不浪费大家的时间,三言两语介绍一下!
首先 Activity 感知到手指按下这一动作,然后传递给RelativeLayout,然后RelativeLayout 发现 还有子View,继续传递给了ImageView,此时ImageView 自己消费了Action_Down 这个动作,然后告诉RelativeLayout,返回true,RelativeLayout又告诉Activity ,OK 结束。
当手指抬起时继续看下面:
抬手10-29 11:41:09.099: E/MainActivity(29973): dispatchTouchEvent:--------------MainActivity 10-29 11:41:09.099: E/TouchRelativeLayout(29973): MotionEvent.ACTION_UP--------------dispatchTouchEvent10-29 11:41:09.099: E/TouchRelativeLayout(29973): onInterceptTouchEvent10-29 11:41:09.099: E/TouchRelativeLayout(29973): onInterceptTouchEvent: false10-29 11:41:09.099: E/TouchImageView(29973): MotionEvent.ACTION_UP--------------dispatchTouchEvent10-29 11:41:09.099: E/TouchImageView(29973): MotionEvent.ACTION_UP--------------onTouch10-29 11:41:09.099: E/TouchImageView(29973): MotionEvent.ACTION_UP--------------onTouchEvent10-29 11:41:09.099: E/TouchImageView(29973): super.onTouchEvent(event) :true10-29 11:41:09.099: E/TouchImageView(29973): super.dispatchTouchEvent(event) :true10-29 11:41:09.099: E/TouchRelativeLayout(29973): super.dispatchTouchEvent(event) :true10-29 11:41:09.099: E/MainActivity(29973): dispatchTouchEvent:--------------MainActivity :true10-29 11:41:09.099: E/TouchImageView(29973): OnClickListener--------------同样的,先通过Activity传递给RelativeLayout,RelativeLayout又传递给ImageView,最终由ImageView处理了Action_Up 这一事件,处理完后,告诉RelativeLayout,RelativeLayout有告诉Activity。
同样的用图表示一下,帮助理解:
另外我们知道,默认RelativeLayout的onInterceptTouchEvent是返回false的,表示不拦截,如果我们改成true,再看下会发生什么??
对于Action_Down 在RelativeLayout,打印的Log并没有区别,当按下在ImageView的时候,是有区别的,看下Log
拦截点击ImageViewdown10-29 18:43:17.004: E/MainActivity(30724): dispatchTouchEvent:--------------MainActivity 10-29 18:43:17.004: E/TouchRelativeLayout(30724): MotionEvent.ACTION_DOWN--------------dispatchTouchEvent10-29 18:43:17.004: E/TouchRelativeLayout(30724): onInterceptTouchEvent true10-29 18:43:17.004: E/TouchRelativeLayout(30724): MotionEvent.ACTION_DOWN--------------onTouch10-29 18:43:17.004: E/TouchRelativeLayout(30724): MotionEvent.ACTION_DOWN--------------onTouchEvent10-29 18:43:17.004: E/TouchRelativeLayout(30724): super.onTouchEvent(event) :true10-29 18:43:17.004: E/TouchRelativeLayout(30724): super.dispatchTouchEvent(event) :true10-29 18:43:17.004: E/MainActivity(30724): dispatchTouchEvent:--------------MainActivity :trueup10-29 18:43:18.734: E/MainActivity(30724): dispatchTouchEvent:--------------MainActivity 10-29 18:43:18.734: E/TouchRelativeLayout(30724): MotionEvent.ACTION_UP--------------dispatchTouchEvent10-29 18:43:18.734: E/TouchRelativeLayout(30724): MotionEvent.ACTION_UP--------------onTouch10-29 18:43:18.734: E/TouchRelativeLayout(30724): MotionEvent.ACTION_UP--------------onTouchEvent10-29 18:43:18.734: E/TouchRelativeLayout(30724): super.onTouchEvent(event) :true10-29 18:43:18.734: E/TouchRelativeLayout(30724): super.dispatchTouchEvent(event) :true10-29 18:43:18.734: E/MainActivity(30724): dispatchTouchEvent:--------------MainActivity :true10-29 18:43:18.734: E/TouchRelativeLayout(30724): OnClickListener--------------
对比前面的我们可以看出,当onInterceptTouchEvent 返回true 时,RelativeLayout就不会把事件往ImageView那里分发,而是自己消费掉。同时通知Activity。
因此得出结论, 在哪一个ViewGroup 的onInterceptTouchEvent 如果拦截了该事件,那么该事件最终只能传递到当前ViewGroup,不会继续往下执行。当然是否是该ViewGroup消费该事件,还要具体看情况。
总结起来我们得出以下结论:
1.事件分发 是有Activity 往ViewGroup 传递,ViewGroup在传递给内部,去找到可以消费该事件的具体TargetView.
2.触摸事件的分发动作——>Activity——>ViewGroup——>子ViewGroup——>...内部子ViewGroup——>Target View
响应就是反过来:Target View——>子ViewGroup——>...外部子ViewGroup——>ViewGroup——>Activity
如果我们嵌套的ViewGroup比较多,那么分发的时候ViewGroup会向子ViewGroup传递,同样的,响应反过来理解。
3.在ViewGroup中如果onInterceptTouchEvent 拦截了该事件,那么该事件就不会往下传递。
4.如果当前TargetView 处理了该次事件,那么该次事件不会再回传给ViewGroup。
----------------欢迎爱学习的小伙伴加群
--------------------android交流群:230274309
------------------------一起分享,一起进步!需要你们
------------------------------期待各位爱学习的小伙伴们的到来
能看到这里的,都是比较认真的同学咯、灰常感谢、用的Demo 这里没有就不上传了。需要的可以等我回头再上传,或者加群,去群里问一下。
参考一下资料:
http://www.mobdevgroup.com/platform/android/article/
http://blog.csdn.net/lvxiangan/article/details/9309927
http://blog.csdn.net/xyz_lmn/article/details/12517911
http://blog.csdn.net/qiushuiqifei/article/details/9918527
http://blog.csdn.net/guolin_blog/article/details/9097463
灰常感谢以上!!
版权声明:本文为博主原创文章,未经博主允许不得转载。
- 3楼u010786678昨天 21:29
- 谢谢小伙伴的分享,学习了
- Re: u0117330203小时前
- 回复u010786678n嘿,向你们这些大牛学习~
- 2楼u010786678昨天 21:29
- 谢谢小伙伴的分享,学习了
- 1楼yulianlin昨天 13:06
- 收藏了