最近因为个人原因需要准备一些资料,所以就萌生了把应用层所涉及的知识点做个基类,不仅仅是简单的知识结论,同时还希望能从源码角度上做个积累,让自己日后在学习中都能有时间拿出来翻翻,可能每次花费几分钟的时候都会有新的感悟,同时也是为了锻炼自己的能力,毕竟不是做纯移动端的,技术还是挺薄弱的,而且之前去大公司面没拿到啥好成绩哈,自嘲一下<..>,希望大家共同加油吧。接下来记录的是Android事件分发机制,其中有些图片是借鉴别的同学,大家多多包含哈。
- 事件分发流程
-
源码解析
-View分发
-ViewGroup分发
事件分发流程
事件分发我们需要关注主要是三个方法dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent。但是这三个方法对应的返回值或执行super方法都会导致最终事件分发的走向。
若都返回super方法,则事件分发的流程如下图
整个事件流向应该是从Activity---->ViewGroup--->View 从上往下调用dispatchTouchEvent方法,一直到叶子节点(View)的时候,再由View--->ViewGroup--->Activity从下往上调用onTouchEvent方法。
若dispatchTouchEvent和onTouchEvent返回true,则事件分发的流程如下图
当dispatchTouchEvent或onTouchEvent返回true,事件就会停止传递被消耗的。为什么会这样呢,等下我们分析下代码。
若dispatchTouchEvent和onTouchEvent返回false,则事件分发的流程如下图
关注下标注蓝线的部分,当返回false时,事件都会回传给父控件的onTouchEvent处理,同样这是为什么呢,我们也可以通过源码来看看哈。
接下来总结一下。我们常见的使用场景如滑动冲突控制,就需要改变如onIterceptTouchEvent的返回值等。
ViewGroup和View 的dispatchTouchEvent 是做事件分发,那么这个事件可能分发出去的四个目标
注:------> 后面代表事件目标需要怎么做。
1、 自己消费,终结传递。------->return true ;
2、 给自己的onTouchEvent处理-------> 调用super.dispatchTouchEvent()系统默认会去调用 onInterceptTouchEvent,在onInterceptTouchEvent return true就会去把事件分给自己的onTouchEvent处理。
3、 传给子View------>调用super.dispatchTouchEvent()默认实现会去调用 onInterceptTouchEvent 在onInterceptTouchEvent return false,就会把事件传给子类。
4、 不传给子View,事件终止往下传递,事件开始回溯,从父View的onTouchEvent开始事件从下到上回归执行每个控件的onTouchEvent------->return false;
注: 由于View没有子View所以不需要onInterceptTouchEvent 来控件是否把事件传递给子View还是拦截,所以View的事件分发调用super.dispatchTouchEvent()的时候默认把事件传给自己的onTouchEvent处理(相当于拦截),对比ViewGroup的dispatchTouchEvent 事件分发,View的事件分发没有上面提到的4个目标的第3点。
ViewGroup和View的onTouchEvent方法是做事件处理的,那么这个事件只能有两个处理方式:
1、自己消费掉,事件终结,不再传给谁----->return true;
2、继续从下往上传,不消费事件,让父View也能收到到这个事件----->return false;View的默认实现是不消费的。所以super==false。
ViewGroup的onInterceptTouchEvent方法对于事件有两种情况:
1、拦截下来,给自己的onTouchEvent处理--->return true;
2、不拦截,把事件往下传给子View---->return false,ViewGroup默认是不拦截的,所以super==false;
关于ACTION_MOVE 和 ACTION_UP
上面讲解的都是针对ACTION_DOWN的事件传递,ACTION_MOVE和ACTION_UP在传递的过程中并不是和ACTION_DOWN 一样,你在执行ACTION_DOWN的时候返回了false,后面一系列其它的action就不会再得到执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个事件(如ACTION_DOWN)返回true,才会收到ACTION_MOVE和ACTION_UP的事件。具体这句话很多博客都说了,但是具体含义是什么呢?我们来看一下下面的具体分析。
上面提到过了,事件如果不被打断的话是会不断往下传到叶子层(View),然后又不断回传到Activity,dispatchTouchEvent 和 onTouchEvent 可以通过return true 消费事件,终结事件传递,而onInterceptTouchEvent 并不能消费事件,它相当于是一个分叉口起到分流导流的作用,ACTION_MOVE和ACTION_UP 会在哪些函数被调用,之前说了并不是哪个函数收到了ACTION_DOWN,就会收到 ACTION_MOVE 等后续的事件的。
下面通过几张图看看不同场景下,ACTION_MOVE事件和ACTION_UP事件的具体走向并总结一下规律。
1、我们在ViewGroup1 的dispatchTouchEvent 方法返回true消费这次事件
ACTION_DOWN 事件从(Activity的dispatchTouchEvent)--------> (ViewGroup1 的dispatchTouchEvent) 后结束传递,事件被消费(如下图红色的箭头代码ACTION_DOWN 事件的流向)。
下图中红色的箭头代表ACTION_DOWN 事件的流向,蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向
2、我们在ViewGroup2 的dispatchTouchEvent 返回true消费这次事件
3、我们在View 的dispatchTouchEvent 返回true消费这次事件
这个我不就画图了,效果和在ViewGroup2 的dispatchTouchEvent return true的差不多,同样的收到ACTION_DOWN 的dispatchTouchEvent函数都能收到 ACTION_MOVE和ACTION_UP。
所以我们就基本可以得出结论如果在某个控件的dispatchTouchEvent 返回true消费终结事件,那么收到ACTION_DOWN 的函数也能收到 ACTION_MOVE和ACTION_UP。
4、我们在View 的onTouchEvent 返回true消费这次事件
红色的箭头代表ACTION_DOWN 事件的流向
蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向
5、我们在ViewGroup 2 的onTouchEvent 返回true消费这次事件
红色的箭头代表ACTION_DOWN 事件的流向
蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向
6、我们在ViewGroup 1 的onTouchEvent 返回true消费这次事件
红色的箭头代表ACTION_DOWN 事件的流向
蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向
7、我们在Activity 的onTouchEvent 返回true消费这次事件
红色的箭头代表ACTION_DOWN 事件的流向
蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向
8、我们在View的dispatchTouchEvent 返回false并且Activity 的onTouchEvent 返回true消费这次事件
红色的箭头代表ACTION_DOWN 事件的流向
蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向
9、我们在View的dispatchTouchEvent 返回false并且ViewGroup 1 的onTouchEvent 返回true消费这次事件
红色的箭头代表ACTION_DOWN 事件的流向
蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向
10、我们在View的dispatchTouchEvent 返回false并且在ViewGroup 2 的onTouchEvent 返回true消费这次事件
红色的箭头代表ACTION_DOWN 事件的流向
蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向
11、我们在ViewGroup2的dispatchTouchEvent 返回false并且在ViewGroup1 的onTouchEvent返回true消费这次事件
红色的箭头代表ACTION_DOWN 事件的流向
蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向
12、我们在ViewGroup2的onInterceptTouchEvent 返回true拦截此次事件并且在ViewGroup 1 的onTouchEvent返回true消费这次事件。
红色的箭头代表ACTION_DOWN 事件的流向
蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向
对于在onTouchEvent消费事件的情况:在哪个View的onTouchEvent 返回true,那么ACTION_MOVE和ACTION_UP的事件从上往下传到这个View后就不再往下传递了,而直接传给自己的onTouchEvent 并结束本次事件传递过程。
对于ACTION_MOVE、ACTION_UP总结:ACTION_DOWN事件在哪个控件消费了(return true), 那么ACTION_MOVE和ACTION_UP就会从上往下(通过dispatchTouchEvent)做事件分发往下传,就只会传到这个控件,不会继续往下传,如果ACTION_DOWN事件是在dispatchTouchEvent消费,那么事件到此为止停止传递,如果ACTION_DOWN事件是在onTouchEvent消费的,那么会把ACTION_MOVE或ACTION_UP事件传给该控件的onTouchEvent处理并结束传递。
分发机制源码解析
其实在上述的结论中,都是可以从源码上一探究竟的。那我们先从View的事件分发开始吧,主要还是看dispatchTouchEvent和onTouchEvent方法。
- View事件分发源码
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. }
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. }
如果要分析到上述情况,我们还要借助于ViewGroup的分发事件的源码,马上供上哈。
dispatchTouchEvent
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. }
如果我们重写View的dispatchTouchEvent并返回true,我们跟踪到ViewGroup中第29行代码,你看执行child.dispatchTouchEvent返回true后ViewGroup的该方法也直接return true了,接下来ACTION_UP其他事件类型,我们定位到代码74行,最终会执行target.dispatchTouchEvent,但view的dispatchTouchEvent返回true,说明事件被消费了,不会再进入到ViewGroup的onTouchEvent里。
如果我们重写View的dispatchTouchEvent并返回false或super,那么在ViewGroup代码29行里,我么看到它返回false还会继续走ViewGroup后续代码,target为null,执行super.dispatchTouchEvent方法(ViewGroup的父类是View)这里调用View的dispatchTouchEvent方法->内部调用onTouchEvent方法。
如果我们重写ViewGroup的dispatchTouchEvent方法,直接返回false,我特意弄了这样的场景,看了Activity的dispatchTouchEvent的源码,会发现会执行Activity的onTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
其他场景也是差不多的,都是可以套入代码中做分析的,这里要我继续分析也感觉无从入手,大家可以看看源码,找到关键代码分析分析场景。
附上别人分析的源码解析