您的位置:首页 > 移动开发 > Android开发

Android Touch事件的分发响应机制

2015-10-29 21:52 465 查看
    前言:欢迎大家给我指出错误,一起进步。谢谢,也希望大家能认真看。    
    如果没有看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--------------dispatchTouchEvent
10-29 11:40:24.519: E/TouchRelativeLayout(29973): onInterceptTouchEvent
10-29 11:40:24.519: E/TouchRelativeLayout(29973): onInterceptTouchEvent: false
10-29 11:40:24.519: E/TouchRelativeLayout(29973): MotionEvent.ACTION_DOWN--------------onTouch
10-29 11:40:24.519: E/TouchRelativeLayout(29973): MotionEvent.ACTION_DOWN--------------onTouchEvent
10-29 11:40:24.519: E/TouchRelativeLayout(29973): super.onTouchEvent(event) :true
10-29 11:40:24.519: E/TouchRelativeLayout(29973): super.dispatchTouchEvent(event) :true
10-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--------------dispatchTouchEvent
10-29 11:40:30.649: E/TouchRelativeLayout(29973): MotionEvent.ACTION_UP--------------onTouch
10-29 11:40:30.649: E/TouchRelativeLayout(29973): MotionEvent.ACTION_UP--------------onTouchEvent
10-29 11:40:30.649: E/TouchRelativeLayout(29973): super.onTouchEvent(event) :true
10-29 11:40:30.649: E/TouchRelativeLayout(29973): super.dispatchTouchEvent(event) :true
10-29 11:40:30.649: E/MainActivity(29973): dispatchTouchEvent:--------------MainActivity :true
10-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--------------dispatchTouchEvent
10-29 16:16:50.092: E/TouchRelativeLayout(9735): onInterceptTouchEvent
10-29 16:16:50.092: E/TouchRelativeLayout(9735): MotionEvent.ACTION_DOWN--------------onTouch
10-29 16:16:50.092: E/TouchRelativeLayout(9735): super.dispatchTouchEvent(event) :true
10-29 16:16:50.092: E/MainActivity(9735): dispatchTouchEvent:--------------MainActivity :true
10-29 16:16:52.692: E/MainActivity(9735): dispatchTouchEvent:--------------MainActivity
10-29 16:16:52.692: E/TouchRelativeLayout(9735): MotionEvent.ACTION_UP--------------dispatchTouchEvent
10-29 16:16:52.692: E/TouchRelativeLayout(9735): MotionEvent.ACTION_UP--------------onTouch
10-29 16:16:52.692: E/TouchRelativeLayout(9735): super.dispatchTouchEvent(event) :true
10-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方法:
@Override
public 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--------------dispatchTouchEvent
10-29 11:41:05.839: E/TouchRelativeLayout(29973): onInterceptTouchEvent
10-29 11:41:05.839: E/TouchRelativeLayout(29973): onInterceptTouchEvent: false
10-29 11:41:05.839: E/TouchImageView(29973): MotionEvent.ACTION_DOWN--------------dispatchTouchEvent
10-29 11:41:05.839: E/TouchImageView(29973): MotionEvent.ACTION_DOWN--------------onTouch
10-29 11:41:05.839: E/TouchImageView(29973): MotionEvent.ACTION_DOWN--------------onTouchEvent
10-29 11:41:05.839: E/TouchImageView(29973): super.onTouchEvent(event) :true
10-29 11:41:05.839: E/TouchImageView(29973): super.dispatchTouchEvent(event) :true
10-29 11:41:05.839: E/TouchRelativeLayout(29973): super.dispatchTouchEvent(event) :true
10-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--------------dispatchTouchEvent
10-29 11:41:09.099: E/TouchRelativeLayout(29973): onInterceptTouchEvent
10-29 11:41:09.099: E/TouchRelativeLayout(29973): onInterceptTouchEvent: false
10-29 11:41:09.099: E/TouchImageView(29973): MotionEvent.ACTION_UP--------------dispatchTouchEvent
10-29 11:41:09.099: E/TouchImageView(29973): MotionEvent.ACTION_UP--------------onTouch
10-29 11:41:09.099: E/TouchImageView(29973): MotionEvent.ACTION_UP--------------onTouchEvent
10-29 11:41:09.099: E/TouchImageView(29973): super.onTouchEvent(event) :true
10-29 11:41:09.099: E/TouchImageView(29973): super.dispatchTouchEvent(event) :true
10-29 11:41:09.099: E/TouchRelativeLayout(29973): super.dispatchTouchEvent(event) :true
10-29 11:41:09.099: E/MainActivity(29973): dispatchTouchEvent:--------------MainActivity :true
10-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
拦截点击ImageView

down
10-29 18:43:17.004: E/MainActivity(30724): dispatchTouchEvent:--------------MainActivity
10-29 18:43:17.004: E/TouchRelativeLayout(30724): MotionEvent.ACTION_DOWN--------------dispatchTouchEvent
10-29 18:43:17.004: E/TouchRelativeLayout(30724): onInterceptTouchEvent true
10-29 18:43:17.004: E/TouchRelativeLayout(30724): MotionEvent.ACTION_DOWN--------------onTouch
10-29 18:43:17.004: E/TouchRelativeLayout(30724): MotionEvent.ACTION_DOWN--------------onTouchEvent
10-29 18:43:17.004: E/TouchRelativeLayout(30724): super.onTouchEvent(event) :true
10-29 18:43:17.004: E/TouchRelativeLayout(30724): super.dispatchTouchEvent(event) :true
10-29 18:43:17.004: E/MainActivity(30724): dispatchTouchEvent:--------------MainActivity :true
up
10-29 18:43:18.734: E/MainActivity(30724): dispatchTouchEvent:--------------MainActivity
10-29 18:43:18.734: E/TouchRelativeLayout(30724): MotionEvent.ACTION_UP--------------dispatchTouchEvent
10-29 18:43:18.734: E/TouchRelativeLayout(30724): MotionEvent.ACTION_UP--------------onTouch
10-29 18:43:18.734: E/TouchRelativeLayout(30724): MotionEvent.ACTION_UP--------------onTouchEvent
10-29 18:43:18.734: E/TouchRelativeLayout(30724): super.onTouchEvent(event) :true
10-29 18:43:18.734: E/TouchRelativeLayout(30724): super.dispatchTouchEvent(event) :true
10-29 18:43:18.734: E/MainActivity(30724): dispatchTouchEvent:--------------MainActivity :true
10-29 18:43:18.734: E/TouchRelativeLayout(30724): OnClickListener--------------
    对比前面的我们可以看出,当onInterceptTouchEvent 返回true 时,RelativeLayout就不会把事件往ImageView那里分发,而是自己消费掉。同时通知Activity。
    因此得出结论, 在哪一个ViewGroup 的onInterceptTouchEvent 如果拦截了该事件,那么该事件最终只能传递到当前ViewGroup,不会继续往下执行。当然是否是该ViewGroup消费该事件,还要具体看情况。

通过本文,我们可以知道以下几点:

   (1)Touch事件,主要由 dispatchTouchEvent()函数(传递消息)、onInterceptTouchEvent()函数(拦截消息)、onTouchEvent()函数和OnTouchListener(消费事件)组成。
   (2)事件从Activity.dispatchTouchEvent()开始传递,只要没有被停止或拦截,从最上层的View(ViewGroup)开始一直往下(子View)传递。子View可以通过onTouchEvent()对事件进行处理。
   (3)事件由父View(ViewGroup)传递给子View,ViewGroup可以通过onInterceptTouchEvent()对事件做拦截,停止其往下传递。
   (4)如果事件从上往下传递过程中一直没有被停止,且最底层子View没有消费事件,事件会反向往上传递,这时父View(ViewGroup)可以进行消费,如果还是没有被消费的话,最后会到Activity的onTouchEvent()函数。

   (5) OnTouchListener优先于onTouchEvent()对事件进行消费,消费就是相应的函数返回true。
----------------欢迎爱学习的小伙伴加群
--------------------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
灰常感谢以上!!
接下来是分析Android Handler 的工作机制,感兴趣的小伙伴可以戳进去:看完这篇文章,你就了解了Android Handler的一切
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: