Android之Touch事件分发机制

原文地址:http://blog.csdn.net/chenzujie/article/details/13277081


直接进入正题,在android开发中,手势触碰是经常使用到的。这两天刚好在看这方面的资料,在这里写篇文章做个小总结。

首先必须知道五大布局Layout:LineaLayout,RelativeLayout,FrameLayout,AbsoluteLayout都继承自ViewGroup,而TableLayout继承LinearLayout,也相当于继承于ViewGroup。

而ViewGroup继承自View。

而我们最终在屏幕上显示控制的控件如TextView,Button等都直接或间接继承自View。

View中有两个方法参与到Touch事件分发

onDispatchTouchEvent(MotionEvent event)和onTouch(MotionEvent event)

ViewGroup有三个方法参与到Touch事件分发

onDispatchTouchEvent(MotionEvent event),onInterceptTouchEvent(MotionEvent ev),onTouch(MotionEvent event)


当我们手指出门屏幕,手势事件最早被传递到Layout布局的dispatchTouchEvent,即ViewGroup的dispatchTouchEvent

[java]  view plain copy
  1. public boolean dispatchTouchEvent(MotionEvent ev) {  
  2.     final int action = ev.getAction();  
  3.     final float xf = ev.getX();  
  4.     final float yf = ev.getY();  
  5.     final float scrolledXFloat = xf + mScrollX;  
  6.     final float scrolledYFloat = yf + mScrollY;  
  7.     final Rect frame = mTempRect;  
  8.     boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
  9.     if (action == MotionEvent.ACTION_DOWN) {  
  10.         if (mMotionTarget != null) {  
  11.             mMotionTarget = null;  
  12.         }  
  13.         if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
  14.             ev.setAction(MotionEvent.ACTION_DOWN);  
  15.             final int scrolledXInt = (int) scrolledXFloat;  
  16.             final int scrolledYInt = (int) scrolledYFloat;  
  17.             final View[] children = mChildren;  
  18.             final int count = mChildrenCount;  
  19.             for (int i = count - 1; i >= 0; i--) {  
  20.                 final View child = children[i];  
  21.                 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
  22.                         || child.getAnimation() != null) {  
  23.                     child.getHitRect(frame);  
  24.                     if (frame.contains(scrolledXInt, scrolledYInt)) {  
  25.                         final float xc = scrolledXFloat - child.mLeft;  
  26.                         final float yc = scrolledYFloat - child.mTop;  
  27.                         ev.setLocation(xc, yc);  
  28.                         child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  29.                         if (child.dispatchTouchEvent(ev))  {  
  30.                             mMotionTarget = child;  
  31.                             return true;  
  32.                         }  
  33.                     }  
  34.                 }  
  35.             }  
  36.         }  
  37.     }  
  38.     boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  
  39.             (action == MotionEvent.ACTION_CANCEL);  
  40.     if (isUpOrCancel) {  
  41.         mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
  42.     }  
  43.     final View target = mMotionTarget;  
  44.     if (target == null) {  
  45.         ev.setLocation(xf, yf);  
  46.         if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
  47.             ev.setAction(MotionEvent.ACTION_CANCEL);  
  48.             mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  49.         }  
  50.         return super.dispatchTouchEvent(ev);  
  51.     }  
  52.     if (!disallowIntercept && onInterceptTouchEvent(ev)) {  
  53.         final float xc = scrolledXFloat - (float) target.mLeft;  
  54.         final float yc = scrolledYFloat - (float) target.mTop;  
  55.         mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  56.         ev.setAction(MotionEvent.ACTION_CANCEL);  
  57.         ev.setLocation(xc, yc);  
  58.         if (!target.dispatchTouchEvent(ev)) {  
  59.         }  
  60.         mMotionTarget = null;  
  61.         return true;  
  62.     }  
  63.     if (isUpOrCancel) {  
  64.         mMotionTarget = null;  
  65.     }  
  66.     final float xc = scrolledXFloat - (float) target.mLeft;  
  67.     final float yc = scrolledYFloat - (float) target.mTop;  
  68.     ev.setLocation(xc, yc);  
  69.     if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
  70.         ev.setAction(MotionEvent.ACTION_CANCEL);  
  71.         target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  72.         mMotionTarget = null;  
  73.     }  
  74.     return target.dispatchTouchEvent(ev);  
  75. }  

此段代码叫长,挑重点看,第9行,但是ACTION_DOWN事件时,会进入到第13行的判断,第一个参数 disallowIntercept是指是否禁用掉事件拦截的功能,默认是false,也可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改。而第二个参数!onInterceptTouchEvent(ev)即是否进行拦截的判断,如果我们不复写这个方法,默认为false,就会进入if判断,并获得点击区域有的view,在第29行将手势事件传递给被点击到的view,如果view的dispatchTouchEvent(ev)返回true,表示手势事件被这个view消费了(view的dispatchTouchEvent后续分析),并把该view置给mMotionTarget后返回true。

这里,正是因为有view消费了ACTION_DOWN事件,使得当后续的ACTION_MOVE ACTION_UP传递到这个ViewGroup的时候,mMotionTarget不为空,使得47行的target不为空,最后都会执行74行的target.dispatchTouchEvent(ev),即ACTION_DOWN事件被哪个view的onTouch或者onTouchEvent返回了true,那么后续的ACTION_MOVE,ACTION_UP事件将会直接传递给这个view执行。(onTouch和onTouchEvent后续分析)

继续分析这个ViewGroup的dispatchTouchEvent,如果13行的onInterceptTouchEvent被我们复写返回true代表ACTION_DOWN被这个ViewGroup拦截下来,将不会进入if判断,因此mMotionTarget将为null使得target也为null,最后执行了50行的super.dispatchTouchEvent即ViewGroup的父类View的dispatchTouchEvent。并在其中调用onTouch或者onTouchEvent。

如果我们不复写onInterceptTouchEvent让其进入if判断,但如果child的dispatchTouchEvent返回的是false,表示child没有把这个事件消费了,最终也还是调用了50行的super.dispatchTouchEvent(ev)。


现在再来看看上面分析中经常说到的View的dispatchTouchEvent

[java]  view plain copy
  1. public boolean dispatchTouchEvent(MotionEvent event) {  
  2.     if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
  3.             mOnTouchListener.onTouch(this, event)) {  
  4.         return true;  
  5.     }  
  6.     return onTouchEvent(event);  
  7. }  

非常简短的几行代码,是不是比看了ViewGroup的dispatchTouchEvent轻松多了!

首先先判断mOnTouchListener是否为空,这个mOnTouchListener是啥呢,看下源码就知道了

[java]  view plain copy
  1. public void setOnTouchListener(OnTouchListener l) {  
  2.     mOnTouchListener = l;  
  3. }  

显然,就是我们设置OnTouchListener的接口实例,并在其中复写了onTouch方法。

if判断的第二个参数用来判断当前view是否是enable为状态。而第三个参数就是我们复写的onTouch方法了,当我们返回了true,这个view的dispatchtouchEvent将返回true,从而使得调用这个dispatchTouchEvent的ViewGroup的dispatchTouchEvent确定了mMotionTarget,确定了消费了ACTION_DOWN事件的view。

而如果我们没有设置OnTouchEvent接口或者onTouch返回false则调用了onTouchEvent。

继续看下view的onTouchEvent

[java]  view plain copy
  1. public boolean onTouchEvent(MotionEvent event) {  
  2.     final int viewFlags = mViewFlags;  
  3.     if ((viewFlags & ENABLED_MASK) == DISABLED) {  
  4.         // A disabled view that is clickable still consumes the touch  
  5.         // events, it just doesn't respond to them.  
  6.         return (((viewFlags & CLICKABLE) == CLICKABLE ||  
  7.                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));  
  8.     }  
  9.     if (mTouchDelegate != null) {  
  10.         if (mTouchDelegate.onTouchEvent(event)) {  
  11.             return true;  
  12.         }  
  13.     }  
  14.     if (((viewFlags & CLICKABLE) == CLICKABLE ||  
  15.             (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
  16.         switch (event.getAction()) {  
  17.             case MotionEvent.ACTION_UP:  
  18.                 boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
  19.                 if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  
  20.                     // take focus if we don't have it already and we should in  
  21.                     // touch mode.  
  22.                     boolean focusTaken = false;  
  23.                     if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  
  24.                         focusTaken = requestFocus();  
  25.                     }  
  26.                     if (!mHasPerformedLongPress) {  
  27.                         // This is a tap, so remove the longpress check  
  28.                         removeLongPressCallback();  
  29.                         // Only perform take click actions if we were in the pressed state  
  30.                         if (!focusTaken) {  
  31.                             // Use a Runnable and post this rather than calling  
  32.                             // performClick directly. This lets other visual state  
  33.                             // of the view update before click actions start.  
  34.                             if (mPerformClick == null) {  
  35.                                 mPerformClick = new PerformClick();  
  36.                             }  
  37.                             if (!post(mPerformClick)) {  
  38.                                 performClick();  
  39.                             }  
  40.                         }  
  41.                     }  
  42.                     if (mUnsetPressedState == null) {  
  43.                         mUnsetPressedState = new UnsetPressedState();  
  44.                     }  
  45.                     if (prepressed) {  
  46.                         mPrivateFlags |= PRESSED;  
  47.                         refreshDrawableState();  
  48.                         postDelayed(mUnsetPressedState,  
  49.                                 ViewConfiguration.getPressedStateDuration());  
  50.                     } else if (!post(mUnsetPressedState)) {  
  51.                         // If the post failed, unpress right now  
  52.                         mUnsetPressedState.run();  
  53.                     }  
  54.                     removeTapCallback();  
  55.                 }  
  56.                 break;  
  57.             case MotionEvent.ACTION_DOWN:  
  58.                 if (mPendingCheckForTap == null) {  
  59.                     mPendingCheckForTap = new CheckForTap();  
  60.                 }  
  61.                 mPrivateFlags |= PREPRESSED;  
  62.                 mHasPerformedLongPress = false;  
  63.                 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
  64.                 break;  
  65.             case MotionEvent.ACTION_CANCEL:  
  66.                 mPrivateFlags &= ~PRESSED;  
  67.                 refreshDrawableState();  
  68.                 removeTapCallback();  
  69.                 break;  
  70.             case MotionEvent.ACTION_MOVE:  
  71.                 final int x = (int) event.getX();  
  72.                 final int y = (int) event.getY();  
  73.                 // Be lenient about moving outside of buttons  
  74.                 int slop = mTouchSlop;  
  75.                 if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
  76.                         (y < 0 - slop) || (y >= getHeight() + slop)) {  
  77.                     // Outside button  
  78.                     removeTapCallback();  
  79.                     if ((mPrivateFlags & PRESSED) != 0) {  
  80.                         // Remove any future long press/tap checks  
  81.                         removeLongPressCallback();  
  82.                         // Need to switch from pressed to not pressed  
  83.                         mPrivateFlags &= ~PRESSED;  
  84.                         refreshDrawableState();  
  85.                     }  
  86.                 }  
  87.                 break;  
  88.         }  
  89.         return true;  
  90.     }  
  91.     return false;  
  92. }  

这段代码同样很长,同样挑重点看。

第14行当这个view是可点击的就会进入if判断,否则直接返回false,因此很有可能这个onTouchEvent的代码如果在第14行进入了if判断,很可能里面会有点击事件的执行。我们往下看,第38行执行ACTION_UP手势时,执行的performClick

[java]  view plain copy
  1. public boolean performClick() {  
  2.     sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  
  3.     if (mOnClickListener != null) {  
  4.         playSoundEffect(SoundEffectConstants.CLICK);  
  5.         mOnClickListener.onClick(this);  
  6.         return true;  
  7.     }  
  8.     return false;  
  9. }  


没错,performClick确实执行了mOnClickListener.onClikc(this);就是执行我们设置的onClick事件。并且在onTouchEvent的89行返回了true。


在这部分分析中我们也发现了一个小秘密,如果我们复写了onTouchListener中的onTouch让它返回false,这样dispatchTouchEvent将不会执行到onTouchEvent方法,使得onClick事件永远得不到执行。


好了,全部分析完毕,做几点小总结

1、onTouch优先于onTouchEvent执行,且onTouch返回true将不执行onTouchEvent,并导致onTouchEvent里的点击事件得不到执行

2、手势事件从最外部的ViewGroup的dispatchTouchEvent开始分发,如果onInterceptTouchEvent拦截了该手势事件,即返回true,手势事件将不会往下分发,即不执行view的dispatchTouchEvent,而是让该ViewGroup的的父类dispatchTouchEvent来消费该手势事件。

3、如果View的dispatchTouchEvent消费了ACTION_DOWN事件,即对ACTION_DOWN返回true,后续的的ACTION_MOVE和ACTION_UP将直接被分发到这个view来消费。


最后感谢 Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

Android事件分发机制完全解析,带你从源码的角度彻底理解(下)

两篇文章的作者,我就是在看了这两篇文章后,再看了些资料才了解了android手势分发事件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值