在分析Android事件分发机制前,明确android的两大基础控件类型:View和ViewGroup。View即普通的控件,没有子布局的,如Button、TextView. ViewGroup继承自View,表示可以有子控件,如Linearlayout、Listview这些。今天我们先来了解View的事件分发机制。

        先看下代码,非常简单,只有一个Button,分别给它注册了OnClick和OnTouch的点击事件。

  1. btn.setOnClickListener(new View.OnClickListener() {
  2. @Override
  3. public void onClick(View v) {
  4. Log.i("Tag", "This is button onClick event");
  5. }
  6. });
  7. btn.setOnTouchListener(new View.OnTouchListener() {
  8. @Override
  9. public boolean onTouch(View v, MotionEvent event) {
  10. Log.i("Tag", "This is button onTouch action" + event.getAction());
  11. return false;
  12. }
  13. });

运行一下项目,结果如下:

  1. I/Tag: This is button onTouch action0
  2. I/Tag: This is button onTouch action2
  3. I/Tag: This is button onTouch action2
  4. I/Tag: This is button onTouch action1
  5. I/Tag: This is button onClick event

可以看到,onTouch是有先于onClick执行的,因此事件的传递顺序是先onTouch,在到OnClick。具体为什么这样,下面会通过源码来说明。这时,我们可能注意到了,onTouch的方法是有返回值,这里是返回false,我们将它改为true再运行一次,结果如下:

  1. I/Tag: This is button onTouch action0
  2. I/Tag: This is button onTouch action2
  3. I/Tag: This is button onTouch action2
  4. I/Tag: This is button onTouch action2
  5. I/Tag: This is button onTouch action1
  对比两次结果,我们发现onClick方法不再执行,为什么会这样,下面我将通过源码给大家一步步理清这个思路。
  查看源码时,首先要知道所有View类型控件事件入口都是dispatchTouchEvent(),所以我们直接进入到View这个类里面的dispatchTouchEvent()方法看一下。

  1. public boolean dispatchTouchEvent(MotionEvent event) {
  2. // If the event should be handled by accessibility focus first.
  3. if (event.isTargetAccessibilityFocus()) {
  4. // We don't have focus or no virtual descendant has it, do not handle the event.
  5. if (!isAccessibilityFocusedViewOrHost()) {
  6. return false;
  7. }
  8. // We have focus and got the event, then use normal event dispatch.
  9. event.setTargetAccessibilityFocus(false);
  10. }
  11. boolean result = false;
  12. if (mInputEventConsistencyVerifier != null) {
  13. mInputEventConsistencyVerifier.onTouchEvent(event, 0);
  14. }
  15. final int actionMasked = event.getActionMasked();
  16. if (actionMasked == MotionEvent.ACTION_DOWN) {
  17. // Defensive cleanup for new gesture
  18. stopNestedScroll();
  19. }
  20. if (onFilterTouchEventForSecurity(event)) {
  21. //noinspection SimplifiableIfStatement
  22. ListenerInfo li = mListenerInfo;
  23. if (li != null && li.mOnTouchListener != null
  24. && (mViewFlags & ENABLED_MASK) == ENABLED
  25. && li.mOnTouchListener.onTouch(this, event)) {
  26. result = true;
  27. }
  28. if (!result && onTouchEvent(event)) {
  29. result = true;
  30. }
  31. }
  32. if (!result && mInputEventConsistencyVerifier != null) {
  33. mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
  34. }
  35. // Clean up after nested scrolls if this is the end of a gesture;
  36. // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
  37. // of the gesture.
  38. if (actionMasked == MotionEvent.ACTION_UP ||
  39. actionMasked == MotionEvent.ACTION_CANCEL ||
  40. (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
  41. stopNestedScroll();
  42. }
  43. return result;
  44. }
   从源码第25行处可以看到,mOnTouchListener.onTouch()的方法首先被执行,如果li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)都为真的话,result赋值为true,否则就执行onTouchEvent(event)方法。
  从上面可以看到要符合条件有四个,
1、ListenerInfo li,它是view中的一个静态类,里面定义view的事件的监听等等,所以有涉及到view的事件,ListenerInfo都会被实例化,因此li不为null
2、mOnTouchiListener是在setOnTouchListener方法里面赋值的,只要touch事件被注册,mOnTouchiListener一定不会null
3、 (mViewFlags & ENABLED_MASK) == ENABLED,是判断当前点击的控件是否是enable的,button默认为enable,这个条件也恒定为true,
4、重点来了,li.mOnTouchListener.onTouch(this, event)就是回调控件onTouch方法,当这个条件也为true时,result=true,onTouchEvent(event)将不会被执行。如果onTouch返回false,就会再执行onTouchEvent(event)方法。
  我们接着再进入到onTouchEvent方法查看源码。

  1. public boolean onTouchEvent(MotionEvent event) {
  2. final float x = event.getX();
  3. final float y = event.getY();
  4. final int viewFlags = mViewFlags;
  5. final int action = event.getAction();
  6. if ((viewFlags & ENABLED_MASK) == DISABLED) {
  7. if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
  8. setPressed(false);
  9. }
  10. // A disabled view that is clickable still consumes the touch
  11. // events, it just doesn't respond to them.
  12. return (((viewFlags & CLICKABLE) == CLICKABLE
  13. || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
  14. || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
  15. }
  16. if (mTouchDelegate != null) {
  17. if (mTouchDelegate.onTouchEvent(event)) {
  18. return true;
  19. }
  20. }
  21. if (((viewFlags & CLICKABLE) == CLICKABLE ||
  22. (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
  23. (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
  24. switch (action) {
  25. case MotionEvent.ACTION_UP:
  26. boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
  27. if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
  28. // take focus if we don't have it already and we should in
  29. // touch mode.
  30. boolean focusTaken = false;
  31. if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
  32. focusTaken = requestFocus();
  33. }
  34. if (prepressed) {
  35. // The button is being released before we actually
  36. // showed it as pressed. Make it show the pressed
  37. // state now (before scheduling the click) to ensure
  38. // the user sees it.
  39. setPressed(true, x, y);
  40. }
  41. if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
  42. // This is a tap, so remove the longpress check
  43. removeLongPressCallback();
  44. // Only perform take click actions if we were in the pressed state
  45. if (!focusTaken) {
  46. // Use a Runnable and post this rather than calling
  47. // performClick directly. This lets other visual state
  48. // of the view update before click actions start.
  49. if (mPerformClick == null) {
  50. mPerformClick = new PerformClick();
  51. }
  52. if (!post(mPerformClick)) {
  53. performClick();
  54. }
  55. }
  56. }
  57. if (mUnsetPressedState == null) {
  58. mUnsetPressedState = new UnsetPressedState();
  59. }
  60. if (prepressed) {
  61. postDelayed(mUnsetPressedState,
  62. ViewConfiguration.getPressedStateDuration());
  63. } else if (!post(mUnsetPressedState)) {
  64. // If the post failed, unpress right now
  65. mUnsetPressedState.run();
  66. }
  67. removeTapCallback();
  68. }
  69. mIgnoreNextUpEvent = false;
  70. break;
  71. case MotionEvent.ACTION_DOWN:
  72. mHasPerformedLongPress = false;
  73. if (performButtonActionOnTouchDown(event)) {
  74. break;
  75. }
  76. // Walk up the hierarchy to determine if we're inside a scrolling container.
  77. boolean isInScrollingContainer = isInScrollingContainer();
  78. // For views inside a scrolling container, delay the pressed feedback for
  79. // a short period in case this is a scroll.
  80. if (isInScrollingContainer) {
  81. mPrivateFlags |= PFLAG_PREPRESSED;
  82. if (mPendingCheckForTap == null) {
  83. mPendingCheckForTap = new CheckForTap();
  84. }
  85. mPendingCheckForTap.x = event.getX();
  86. mPendingCheckForTap.y = event.getY();
  87. postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
  88. } else {
  89. // Not inside a scrolling container, so show the feedback right away
  90. setPressed(true, x, y);
  91. checkForLongClick(0);
  92. }
  93. break;
  94. case MotionEvent.ACTION_CANCEL:
  95. setPressed(false);
  96. removeTapCallback();
  97. removeLongPressCallback();
  98. mInContextButtonPress = false;
  99. mHasPerformedLongPress = false;
  100. mIgnoreNextUpEvent = false;
  101. break;
  102. case MotionEvent.ACTION_MOVE:
  103. drawableHotspotChanged(x, y);
  104. // Be lenient about moving outside of buttons
  105. if (!pointInView(x, y, mTouchSlop)) {
  106. // Outside button
  107. removeTapCallback();
  108. if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
  109. // Remove any future long press/tap checks
  110. removeLongPressCallback();
  111. setPressed(false);
  112. }
  113. }
  114. break;
  115. }
  116. return true;
  117. }
  118. return false;
  119. }

从源码的21行我们可以看出,该控件可点击就会进入到switch判断中,当我们触发了手指离开的实际,则会进入到MotionEvent.ACTION_UP这个case当中。我们接着往下看,在源码的50行,调用到了mPerformClick()方法,我们继续进入到这个方法的源码看看。

  1. public boolean performClick() {
  2. final boolean result;
  3. final ListenerInfo li = mListenerInfo;
  4. if (li != null && li.mOnClickListener != null) {
  5. playSoundEffect(SoundEffectConstants.CLICK);
  6. li.mOnClickListener.onClick(this);
  7. result = true;
  8. } else {
  9. result = false;
  10. }
  11. sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
  12. return result;
  13. }

现在我们可以看到,只要ListenerInfo和mOnClickListener不为null就会调用onClick这个方法,之前说过,只要有监听事件,ListenerInfo就不为null,带mOnClickListener又是在哪里赋值呢?我们再继续看下它的源码。

  1. public void setOnClickListener(@Nullable OnClickListener l) {
  2. if (!isClickable()) {
  3. setClickable(true);
  4. }
  5. getListenerInfo().mOnClickListener = l;
  6. }

  看到这里一切就清楚了,当我们调用setOnClickListener方法来给按钮注册一个点击事件时,就会给mOnClickListener赋值。整个分发事件的顺序是onTouch()-->onTouchEvent(event)-->performClick()-->OnClick()。

  现在我们可以解决之前的问题。

1、onTouch方法是优先于OnClick,所以是执行了onTouch,再执行onClick。

2、无论是dispatchTouchEvent还是onTouchEvent,如果返回true表示这个事件已经被消费、处理了,不再往下传了。在dispathTouchEvent的源码里可以看到,如果onTouchEvent返回了true,那么它也返回true。如果dispatchTouchEvent在执行onTouch监听的时候,onTouch返回了true,那么它也返回true,这个事件提前被onTouch消费掉了。就不再执行onTouchEvent了,更别说onClick监听了。

Android事件的分发机制的更多相关文章

  1. Android NestedScrolling与分发机制

    在Android5.0之间要实现控件的嵌套滑动,都是要自己处理View事件即分发机制. 共有三个方法:    dispatchTouchEvent().onInterceptTouchEvent()和 ...

  2. View,ViewGroup的Touch事件的分发机制

    原帖地址:http://blog.csdn.net/xiaanming/article/details/21696315 ViewGroup的事件分发机制 我们用手指去触摸Android手机屏幕,就会 ...

  3. Andriod 从源码的角度详解View,ViewGroup的Touch事件的分发机制

    转自:xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/21696315) 今天这篇文章主要分析的是Android的事件分发机制, ...

  4. Android事件总线分发库EventBus3.0的简单讲解与实践

    Android事件总线分发库EventBus的简单讲解与实践 导语,EventBus大家应该不陌生,EventBus是一款针对Android优化的发布/订阅事件总线.主要功能是替代Intent,Han ...

  5. Android NestedScrolling与分发机制 二

    上篇转载了 Android:30分钟弄明白Touch事件分发机制 这篇转载 Android中的dispatchTouchEvent().onInterceptTouchEvent()和onTouchE ...

  6. android事件拦截处理机制详解

    前段时间刚接触过Android手机开发,对它的事件传播机制不是很了解,虽然网上也查了相关的资料,但是总觉得理解模模糊糊,似是而非,于是自己就写个小demo测试了一下.总算搞明白了它的具体机制.写下自己 ...

  7. 45、Android事件总线分发库的使用

    事件总线分发库EventBus和Otto的简介及对比 什么是事件总线管理: a.将事件放到队列里,用于管理和分发b.保证应用的各个部分之间高效的通信及数据.事件分发c.模块间解耦 Event Bus是 ...

  8. android事件拦截处理机制具体解释

    前段时间刚接触过android手机开发.对它的事件传播机制不是非常了解,尽管网上也查了相关的资料,可是总认为理解模模糊糊,似是而非,于是自己就写个小demo測试了一下. 总算搞明确了它的详细机制.写下 ...

  9. touch事件的分发机制

    作者:谢昆 一段伪代码反应整个touch事件的分发 public boolean dispatchTouchEvent(MotionEvent event) { boolean consume = f ...

随机推荐

  1. javascript入门学习笔记

    <button type="button" onclick="alert('Welcome!')">点击这里</button>alert ...

  2. 深入理解offsetTop与offsetLeft

    做为走上前端不归路的我,以前只是认为offsetTop是元素的左边框至包含元素offsetParent的左内边框之间的像素距离,同理offsetRight是相对于上内边框.那么问题来了,包含元素off ...

  3. Android单位度量

    px(像素):屏幕上的点. in(英寸):长度单位.mm(毫米):长度单位.pt(磅):1/72英寸.dp(与密度无关的像素):一种基于屏幕密度的抽象单位.在每英寸160点的显示器上,1dp = 1p ...

  4. 【BZOJ1901】Dynamic Rankings

    Description 给定一个含有n个数的序列a[1],a[2],a[3]……a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1],a[i+2]……a[j]中第k小的数是 ...

  5. Spring3.0提供的表达式语言spel

    package com.zf.spel; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.D ...

  6. jquery mobile 主题

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name ...

  7. Hibernate的CRUD

    1.CRUD: C:sesion.save() R:session.get()? session.load() D:session.delete() U:session.update() 2.读取数据 ...

  8. 如何写angularJS模块

    angularJS中提供模块的概念,供我们把代码封装在模块单元中,使用模块能给我们带来的好处 保持全局命名空间的清洁 易于在不同应用间复用代码 demo.html <!doctype html& ...

  9. jQuery解析JSON的问题

    在WEB数据传输过程中,json是以文本,即字符串的轻量级形式传递的,而客户端一般用JS操作的是接收到的JSON对象,所以,JSON对象和JSON字符串之间的相互转换.JSON数据的解析是关键. JS ...

  10. Day4 内置函数补充、装饰器

    li = [11,22,33,44]def f1(arg): arg.append(55)#函数默认返回值None,函数参数传递的是引用li = f1(li) print(li)   内置函数补充: ...