Android View 触摸屏事件派发机制和源码分析

来源:互联网 发布:linux配置web服务器 编辑:程序博客网 时间:2024/06/04 18:41

Android View 触摸屏事件派发机制和源码分析

最近参考Android SystemUI 的源码其中涉及到很多事件派发和处理相关的问题.早期感觉很复杂,后面有时间了通过结合log来分析渐渐有一定的理解.现在参考网络上面的文章自己也将这一方面的源码分析一下,同时写下来,以便然后回忆和加深理解.(基于mtk 的Android 5.1 系统)



Android 的触摸事件都是从dispatchTouchEvent()开始的,View 和ViewGroup 都是一样的.暂时只分析View,不分析ViewGroup.

1.View 的dispatchTouchEvent()开始

其实View的dispatchTouchEvent也是有父布局(某个ViewGroup,可以理解为LinerLayout)调用的.直接上源码.

在判断是ACTION_DOWN之前dispatchTouchEvent 做了处理手势相关的工作(mInputEventConsistencyVerifier),这个具体还没有研究,以后再研究,在笔记的后面有一个"Android的用户输入处理"可以参考.目前只是分析我们需要关注的重点.

  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. if (DBG_MOTION || DEBUG_DEFAULT) {
  16. if (event.getAction() == MotionEvent.ACTION_DOWN) {
  17. Xlog.i(VIEW_LOG_TAG, "Touch down dispatch to " + this + ", event = " + event);
  18. } else if (event.getAction() == MotionEvent.ACTION_UP) {
  19. Xlog.i(VIEW_LOG_TAG, "Touch up dispatch to " + this + ", event = " + event);
  20. }
  21. }
  22. if (DBG_MOTION) {
  23. Xlog.d(VIEW_LOG_TAG, "(View)dispatchTouchEvent: event = " + event + ",this = " + this);
  24. }
  25. final int actionMasked = event.getActionMasked();
  26. if (actionMasked == MotionEvent.ACTION_DOWN) {
  27. // Defensive cleanup for new gesture
  28. stopNestedScroll();
  29. }
  30. if (onFilterTouchEventForSecurity(event)) {//判断当前View 没有被遮蔽
  31. //noinspection SimplifiableIfStatement
  32. ListenerInfo li = mListenerInfo;
  33. if (li != null && li.mOnTouchListener != null
  34. && (mViewFlags & ENABLED_MASK) == ENABLED
  35. && li.mOnTouchListener.onTouch(this, event)) {//1.设置touchListener,2.是enable状态,3.touchListener的onTouch返回true
  36. /// M : add log to help debugging
  37. if (DBG_TOUCH) {
  38. Xlog.d(VIEW_LOG_TAG, "handle Touch event by listerner, listener = " + li
  39. + ", event = " + event + ", this = " + this);
  40. }
  41. result = true;
  42. }
  43. if (!result && onTouchEvent(event)) {//touchListener的onTouch没有返回false,或者没有设置clickListener/longClickListener,而且onTouchEvent返回true.而且onTouchEvent返回什么dispatchTouchEvent也会返回什么
  44. /// M : add log to help debugging
  45. if (DBG_TOUCH) {
  46. Xlog.d(VIEW_LOG_TAG, "handle Touch event by onTouchEvent, event = "
  47. + event + ", this = " + this);
  48. }
  49. result = true;
  50. }
  51. }
  52. /// M : add log to help debugging
  53. if (!result && DBG_TOUCH) {
  54. Xlog.d(VIEW_LOG_TAG, "Do not handle Touch event, event = "
  55. + event + ", this = " + this);
  56. }
  57. if (!result && mInputEventConsistencyVerifier != null) {
  58. mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
  59. }
  60. // Clean up after nested scrolls if this is the end of a gesture;
  61. // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
  62. // of the gesture.
  63. if (actionMasked == MotionEvent.ACTION_UP ||
  64. actionMasked == MotionEvent.ACTION_CANCEL ||
  65. (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
  66. stopNestedScroll();
  67. }
  68. return result;
  69. }
onFilterTouchEventForSecurity里面看注释就知道是判断当前的View 有没有被遮蔽.我们考虑的是没有被遮蔽的情况,了就是onFilterTouchEventForSecurity返回true.
dispatchTouchEvent 在39行有一个if判断,这个判断依次做了3个小判断. if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event))
第一步:li != null && li.mOnTouchListener != null,这个ListenerInfo 是不是为null并且mListenerInfo的mOnTouchListener  是不是为null.在View 的源码里面查找mListenerInfo就会找到是在getListenerInfo里面调用的,接着会发现是在setOnClickListener里面调用的.显然也就是我们判断我们也没有给这个View 控件设置View.OnClickListener事件.如果有就已经第2部分的判断
  1. ListenerInfo getListenerInfo() {
  2. if (mListenerInfo != null) {
  3. return mListenerInfo;
  4. }
  5. mListenerInfo = new ListenerInfo();
  6. return mListenerInfo;
  7. }
  8. public void setOnClickListener(OnClickListener l) {
  9. if (!isClickable()) {
  10. setClickable(true);
  11. }
  12. getListenerInfo().mOnClickListener = l;
  13. }

第二步: (mViewFlags & ENABLED_MASK) == ENABLED,这个就是判断当前View是不是enable的.这个是通过setEnabel方法来设置的,如果是enable的,就继续第3部的判断
第三步:li.mOnTouchListener.onTouch(this, event),判断View.OnTouchListener的onTouch()返回true.如果是true.就是讲result赋值true.也就是说关系到这个dispatchTouchEvent的返回值了.
上面3个判断是一个接着一个,第一个false了,后面就没有戏了,是不会执行的.

再往下面看if(!result&& onTouchEvent(event)),这个if 通过由result 和onTouchEvent()一起决定.前面已经分析关于result的部分,现在接着分析onTouchEvent()部分.
代码如下,但是有的长,还是一样,看重点就好,放弃不重要的细节.这个方法重点就是38行开始的if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {,这个进行了2个判断,容易一个判断满足了就可执行下面关于ACTION_DWON,ACTION_MOVE,ACTION_UP等事件的处理.同样分布判断这个条件.
第一步:(viewFlags & CLICKABLE) == CLICKABLE,判断是否设置了setClickable(),注意setOnClickListener(OnClickListener l)也能转调这个方法
第二步: (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE,判断是否设置了setLongClickable(boolean longClickable),注意public void setOnLongClickListener(OnLongClickListener l) 也能转调这个方法
上面的判断只要在View源码来搜索CLICKABLE和LONG_CLICKABLE找到对应的方法再找到调用的地方就知道是判断设置相关监听事件View.onClickListener/View.onLongClickListener的.
如果if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {,为true,View 的onTouchEvent()最终就是返回true,相关这个条件没有通过View 的onTouchEvent()最终就是返回false.再回溯到dispatchTouchEvent()就会知道这个onTouchEvent返回true, dispatchTouchEvent就是返回true,dispatchTouchEvent()就会知道这个onTouchEvent返回false, dispatchTouchEvent就是返回false.

12行:disable 的情况下也可以消耗掉点击事件,只是不会执行click等方法.
  1. public boolean onTouchEvent(MotionEvent event) {
  2. final float x = event.getX();
  3. final float y = event.getY();
  4. final int viewFlags = mViewFlags;
  5. if (DBG_MOTION) {
  6. Xlog.d(VIEW_LOG_TAG, "(View)onTouchEvent 1: event = " + event + ",mTouchDelegate = "
  7. + mTouchDelegate + ",enable = " + isEnabled() + ",clickable = " + isClickable()
  8. + ",isLongClickable = " + isLongClickable() + ",this = " + this);
  9. }
  10. if ((viewFlags & ENABLED_MASK) == DISABLED) {//disable 的情况下也可以消耗掉点击事件,只是不会执行click等方法.
  11. /// M: we need to reset the pressed state or remove prepressed callback
  12. /// either up or cancel event happens.
  13. final int action = event.getAction();
  14. if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
  15. if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
  16. setPressed(false);
  17. } else if ((mPrivateFlags & PFLAG_PREPRESSED) != 0) {
  18. Xlog.d(VIEW_LOG_TAG, "View onTouch event, if a view's DISABLED&PFLAG_PREPRESSED"
  19. + "equal to TRUE, then remove callback mPrivateFlags = " + mPrivateFlags
  20. + ", this = " + this);
  21. removeTapCallback();
  22. }
  23. }
  24. // A disabled view that is clickable still consumes the touch
  25. // events, it just doesn't respond to them.
  26. return (((viewFlags & CLICKABLE) == CLICKABLE ||
  27. (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
  28. }
  29. if (mTouchDelegate != null) {//设置了代理的情况下调用代理的onTouchEvent
  30. if (mTouchDelegate.onTouchEvent(event)) {
  31. return true;
  32. }
  33. }
  34. if (((viewFlags & CLICKABLE) == CLICKABLE ||
  35. (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {//判断是否设置了setClickable();判断是否设置了setLongClickable()
  36. switch (event.getAction()) {
  37. case MotionEvent.ACTION_UP:
  38. boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
  39. if (DBG_MOTION) {
  40. Xlog.d(VIEW_LOG_TAG, "(View)Touch up: prepressed = " + prepressed
  41. + ",this = " + this);
  42. }
  43. if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
  44. // take focus if we don't have it already and we should in
  45. // touch mode.
  46. boolean focusTaken = false;
  47. if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
  48. focusTaken = requestFocus();
  49. }
  50. if (prepressed) {
  51. // The button is being released before we actually
  52. // showed it as pressed. Make it show the pressed
  53. // state now (before scheduling the click) to ensure
  54. // the user sees it.
  55. setPressed(true, x, y);
  56. }
  57. if (!mHasPerformedLongPress) {
  58. // This is a tap, so remove the longpress check
  59. removeLongPressCallback();
  60. // Only perform take click actions if we were in the pressed state
  61. if (!focusTaken) {
  62. // Use a Runnable and post this rather than calling
  63. // performClick directly. This lets other visual state
  64. // of the view update before click actions start.
  65. if (mPerformClick == null) {
  66. mPerformClick = new PerformClick();
  67. }
  68. if (DBG_TOUCH) {
  69. Xlog.d(VIEW_LOG_TAG, "(View)Touch up: post perfomrClick"
  70. + " runnable, this = " + this);
  71. }
  72. if (!post(mPerformClick)) {
  73. performClick();
  74. }
  75. }
  76. }
  77. if (mUnsetPressedState == null) {
  78. mUnsetPressedState = new UnsetPressedState();
  79. }
  80. if (prepressed) {
  81. postDelayed(mUnsetPressedState,
  82. ViewConfiguration.getPressedStateDuration());
  83. } else if (!post(mUnsetPressedState)) {
  84. // If the post failed, unpress right now
  85. mUnsetPressedState.run();
  86. }
  87. removeTapCallback();
  88. }
  89. break;
  90. case MotionEvent.ACTION_DOWN:
  91. mHasPerformedLongPress = false;
  92. if (performButtonActionOnTouchDown(event)) {
  93. break;
  94. }
  95. // Walk up the hierarchy to determine if we're inside a scrolling container.
  96. boolean isInScrollingContainer = isInScrollingContainer();
  97. if (DBG_MOTION) {
  98. Xlog.d(VIEW_LOG_TAG, "(View)Touch down: isInScrollingContainer = "
  99. + isInScrollingContainer + ",this = " + this);
  100. }
  101. // For views inside a scrolling container, delay the pressed feedback for
  102. // a short period in case this is a scroll.
  103. if (isInScrollingContainer) {
  104. mPrivateFlags |= PFLAG_PREPRESSED;
  105. if (mPendingCheckForTap == null) {
  106. mPendingCheckForTap = new CheckForTap();
  107. }
  108. mPendingCheckForTap.x = event.getX();
  109. mPendingCheckForTap.y = event.getY();
  110. postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
  111. } else {
  112. // Not inside a scrolling container, so show the feedback right away
  113. setPressed(true, x, y);
  114. checkForLongClick(0);
  115. }
  116. break;
  117. case MotionEvent.ACTION_CANCEL:
  118. if (DBG_MOTION) {
  119. Xlog.d(VIEW_LOG_TAG, "(View)Touch cancel: this = " + this);
  120. }
  121. setPressed(false);
  122. removeTapCallback();
  123. removeLongPressCallback();
  124. break;
  125. case MotionEvent.ACTION_MOVE:
  126. if (DBG_MOTION) {
  127. Xlog.d(VIEW_LOG_TAG, "(View)Touch move: x = " + x + ",y = " + y
  128. + ",mTouchSlop = " + mTouchSlop + ",this = " + this);
  129. }
  130. drawableHotspotChanged(x, y);
  131. // Be lenient about moving outside of buttons
  132. if (!pointInView(x, y, mTouchSlop)) {
  133. // Outside button
  134. removeTapCallback();
  135. if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
  136. // Remove any future long press/tap checks
  137. removeLongPressCallback();
  138. setPressed(false);
  139. }
  140. }
  141. break;
  142. }
  143. return true;
  144. }
  145. return false;
  146. }
判断了CLICKABLE和LONG_CLICKABLE之后需要分析的就是ACTION_DOWN,ACTION_MOVE,ACTION_UP这几个事件的处理了,ACTION_DOWN,ACTION_MOVE主要还是记录坐标位置等工作,重点还是ACTION_UP事件.
判断是ACTION_UP时间之后会判断之前是不是按下过(prepressed)->能不能获取焦点->获取焦点->没有被长按过->调用View.post()来执行一个实现了Runnable接口的PerformClick类的run方法,其实也就是performClick().
performClick()方法判断我们设置了View.OnClickListener之后就会根据实际情况播放触屏声音,同时执行View.OnClickListener的onClick()方法.如果设置了View.OnClickListener,performClick()就会返回true,相反就是false.当是这个方法的返回值不影响onTouchEvent() 的返回值.
  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. if (DBG_TOUCH) {
  7. Xlog.d(VIEW_LOG_TAG, "(View)performClick, listener = " + li.mOnClickListener
  8. + ",this = " + this);
  9. }
  10. li.mOnClickListener.onClick(this);
  11. result = true;
  12. } else {
  13. result = false;
  14. }
  15. sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
  16. return result;
  17. }
到这里View 视图从dispatchTouchEvent()开始的触屏处理的主要流程已经分析完了.
现在总结一下:
1.View 的触摸屏时间是从dispatchTouchEvent() 开始的;
2.View设置相关的Listener 之后,是先处理onTouch,再来处理onClick(),其实onClick是最后才有机会处理.
    先后顺序为:onTouch()->onTouchEvent->onClick(),当然这是事件没有被消耗也就是前面的方法没有返回true的情况下.
3.如果onTouch()返回true,将会阻止事件传递,也就是不会执行View的onTouchEvent()和onClick(),如果onTouch()返回false,才会继续走onTouchEvnet()流程.
4.onClick是在onTouchEvent()里面调用执行的,而且是在onTouchEvent()的ACTION_UP事件里面执行的.
5.如果Enable 为true,并且设置了OnTouchListener同时onTouch返回了true,就不会执行onTouchEvent(),onClick()更不会执行
具体看图
6.View 的dispatchTouchEvent()返回值:如果重写了onTouch(),就和onTouch()返回值一致.没有没有重写onTouch(),就和onTouchEvent()返回值一致.
7.View的onTouchEvent()返回值:如果设置View.OnClickListener或者View.OnLongClickListener.就返回true.否则就是false.





参考:
 Android触摸屏事件派发机制详解与源码分析一(View篇)
http://blog.csdn.net/yanbober/article/details/45887547

Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)
http://blog.csdn.net/yanbober/article/details/45912661

Android触摸屏事件派发机制详解与源码分析三(Activity篇)
http://blog.csdn.net/yanbober/article/details/45932123

Android的用户输入处理
http://www.cnblogs.com/samchen2009/p/3368158.html


0 0
原创粉丝点击