Android 事件响应原理
一、简介
触摸事件就是捕获触摸屏幕后产生的事件。Android为触摸事件封装了一个类——MotionEvent,如果重写onTouchEvent(MotionEvent event)方法,就会发现该方法的参数就是一个MotionEvent类实例。
事件触发分为三个阶段,分发、拦截、消费。
二、事件拦截机制分析
Android的View结构就是一个树形结构,View可以放在一个ViewGroup里面,这个ViewGroup又放在另一个ViewGroup里面,甚至还有可能继续嵌套,一层层地叠起来。触摸事件就一个,到底该分给谁?同一个事件,子View和父ViewGroup都有可能想要进行处理,因此就产生了“事件拦截机制”。
假设,你所在公司,有一个总经理,级别最高;他下面有一个部长,级别次之;最低层,就是干活的你,没有级别。现在董事会交给总经理一个任务,总经理将这项任务布置给了部长,部长又把任务安排给了你。而当你完成了这项任务,你就把任务交给部长,部长觉得任务完成的不错,于是就签了他的名字,将任务交给了总经理,总经理看了也觉得不错,就也签了名字交给董事会。这个,一个任务就顺利完成了。
一个总经理——MyViewGroupA,最外层的ViewGroup;
一个部长——MyViewGroupB,中间层ViewGroup;
一个干活的你——MyView,在最低层;
对于ViewGroup,需要重写三个方法:
1 @Override 2 public boolean dispatchTouchEvent(MotionEvent event) 3 { 4 Log.d(TAG, "ViewGroup dispatchTouchEvent" + event.getAction()); 5 return super.dispatchTouchEvent(event); 6 } 7 8 @Override 9 public boolean onInterceptTouchEvent(MotionEvent event) 10 { 11 Log.d(TAG, "ViewGroup onInterceptTouchEvent" + event.getAction()); 12 return super.onInterceptTouchEvent(event); 13 } 14 15 @Override 16 public boolean onTouchEvent(MotionEvent event) 17 { 18 Log.d(TAG, "ViewGroup onTouchEvent" + event.getAction()); 19 return super.onTouchEvent(event); 20 }
对于View,需要重写如下两个方法:
1 @Override 2 public boolean dispatchTouchEvent(MotionEvent event) 3 { 4 Log.d(TAG, "View dispatchTouchEvent" + event.getAction()); 5 return super.dispatchTouchEvent(event); 6 } 7 8 @Override 9 public boolean onTouchEvent(MotionEvent event) 10 { 11 Log.d(TAG, "View onTouchEvent" + event.getAction()); 12 return super.onTouchEvent(event); 13 }
当点击View后的Log如下所示:
1 D/MyViewGroupA : ViewGroupA dispatchTouchEvent 2 D/MyViewGroupA : ViewGroupA onInterceptTouchEvent 3 D/MyViewGroupB : ViewGroupB dispatchTouchEvent 4 D/MyViewGroupB : ViewGroupB onInterceptTouchEvent 5 D/MyView : View dispatchTouchEvent 6 D/MyView : View onTouchEvent 7 D/MyViewGroupB : MyViewGroupB onTouchEvent 8 D/MyViewGroupA : MyViewGroupA onTouchEvent
从其中可以看到,事件传递顺序是:
总经理(MyViewGroupA)— 部长(MyViewGroupB)— 你(MyView),这个过程就是事件的分发阶段;
PS:事件传递,即分发,就是执行dispatchTouchEvent()方法,再执行onInterceptTouchEvent()方法。
事件的处理顺序:
在你去干活的过程就是事件的目标阶段,即View的onTouchEvent()方法执行;
你(MyView)— 部长(MyViewGroupB)— 总经理(MyViewGroupA),这个过程就是事件的向上传递消费阶段;
PS:事件处理就是执行onTouchEvent()方法。
假设1:总经理(MyViewGroupA)发现这个任务太简单了,觉得自己就可以完成,完全没必要再找下属。因此,事件就被总经理(MyViewGroupA)onInterceptTouchEvent()方法把事件拦截了,即让onInterceptTouchEvent()方法返回值为True,下面再看看Log:
1 D/MyViewGroupA : ViewGroupA dispatchTouchEvent 2 D/MyViewGroupA : ViewGroupA onInterceptTouchEvent 3 D/MyViewGroupA : MyViewGroupA onTouchEvent
假设2:部长(MyViewGroupB)发现这个任务太简单了,觉得自己就可以完成,完全没必要再找下属。因此,事件就被部长(MyViewGroupB)onInterceptTouchEvent()方法把事件拦截了,即让onInterceptTouchEvent()方法返回值为True,下面再看看Log:
1 D/MyViewGroupA : ViewGroupA dispatchTouchEvent 2 D/MyViewGroupA : ViewGroupA onInterceptTouchEvent 3 D/MyViewGroupB : ViewGroupB dispatchTouchEvent 4 D/MyViewGroupB : ViewGroupB onInterceptTouchEvent 5 D/MyViewGroupB : MyViewGroupB onTouchEvent 6 D/MyViewGroupA : MyViewGroupA onTouchEvent
这两种情况下,可以看到总经理或者部长,MyViewGroupA或者MyViewGroupB将事件拦截了。
事件的返回值含义:
当事件在传递的过程中,返回值:True,拦截,不继续;False,不拦截,继续流程,传递给下一级。
当事件在处理的过程中,返回值:True,处理了,不用审核了;False,交给上一级处理。
三、总结
1. dispatchTouchEvent(...):
作用:决定事件是否由onInterceptTouchEvent拦截处理;
当返回super.dispatchTouchEvent(...)时,由onInterceptTouchEvent来决定事件的流向,onInterceptTouchEvent返回值为false时,继续向子View分发事件,本View只处理ACTION_DOWN事件。
当onInterceptTouchEvent返回值为true时,不继续向子View分发事件,本View处理所有事件。
2. onInterceptTouchEvent(...):
作用:拦截事件,决定是否将事件传递给子View;
当返回值为false时,事件继续传递给子View;
当返回值为true时,事件交给onTouchEvent(...)处理,不再向子View传递。
3. onTouchEvent(MotionEvent event):
作用:事件最终到这个方法,由此方法处理事件;
当返回值为false时,事件继续向上传递给其父View的onTouchEvent()方法,直到根View的,一直传递到根View时,返回值都是false,也就是说在事件传递过程中没有View的onTouchEvent()方法返回true,此次手势就会结束,此次事件就会被取消。
当返回值为true时,此View处理所有事件,并且处理的事件不会再向上返回。也就是说在值为true时,此手势的所有事件都传递给此View的onTouchEvent()处理,包括ACTION_DOWN、ACTION_UP、ACTION_MOVE等事件。
四、举例说明
自定义三个View,分别为ViewEventA、ViewEventB、ViewEventC:
1 // ViewEventA 2 public class ViewEventA extends ViewGroup { 3 private static final String TAG = "ViewEventA"; 4 5 public ViewEventA(Context context) { 6 super(context); 7 } 8 9 @Override 10 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 11 int count = getChildCount(); 12 for (int idx = 0; idx < count; idx++) { 13 View child = getChildAt(idx); 14 int widthMeSpec = MeasureSpec.makeMeasureSpec(300, MeasureSpec.EXACTLY); 15 int heightmeSpec = MeasureSpec.makeMeasureSpec(300, MeasureSpec.EXACTLY); 16 child.measure(widthMeSpec, heightmeSpec); 17 } 18 int widthMeSpec = MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY); 19 int heightmeSpec = MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY); 20 setMeasuredDimension(widthMeSpec, heightmeSpec); 21 } 22 23 @Override 24 protected void onLayout(boolean changed, int l, int t, int r, int b) { 25 if (changed) { 26 int count = getChildCount(); 27 for (int idx = 0; idx < count; idx++) { 28 View child = getChildAt(idx); 29 child.layout(0, 0, 300, 300); 30 } 31 } 32 } 33 34 @Override 35 public boolean dispatchTouchEvent(MotionEvent event) { 36 Log.d(TAG, "dispatchTouchEvent"); 37 return super.dispatchTouchEvent(event); 38 } 39 40 @Override 41 public boolean onInterceptTouchEvent(MotionEvent event) { 42 Log.d(TAG, "onInterceptTouchEvent"); 43 return super.onInterceptTouchEvent(event); 44 } 45 46 @Override 47 public boolean onTouchEvent(MotionEvent event) { 48 String action = ""; 49 switch (event.getAction()) { 50 case MotionEvent.ACTION_DOWN: 51 action = "ACTION_DOWN"; 52 break; 53 case MotionEvent.ACTION_MOVE: 54 action = "ACTION_MOVE"; 55 break; 56 case MotionEvent.ACTION_UP: 57 action = "ACTION_UP"; 58 break; 59 case MotionEvent.ACTION_CANCEL: 60 action = "ACTION_CANCEL"; 61 break; 62 } 63 Log.d(TAG, "onTouchEvent: " + action); 64 return super.onTouchEvent(event); 65 } 66 } 67 68 // ViewEventB 69 public class ViewEventB extends ViewGroup { 70 private static final String TAG = "ViewEventB"; 71 72 public ViewEventB(Context context) { 73 super(context); 74 } 75 76 @Override 77 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 78 int count = getChildCount(); 79 for (int idx = 0; idx < count; idx++) { 80 View child = getChildAt(idx); 81 int width = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY); 82 int height = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY); 83 child.measure(width, height); 84 } 85 86 setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); 87 } 88 89 @Override 90 protected void onLayout(boolean changed, int l, int t, int r, int b) { 91 if (changed) { 92 int count = getChildCount(); 93 for (int idx = 0; idx < count; idx++) { 94 View child = getChildAt(idx); 95 child.layout(0, 0, 100, 100); 96 } 97 } 98 } 99 100 @Override 101 public boolean dispatchTouchEvent(MotionEvent event) { 102 Log.d(TAG, "dispatchTouchEvent"); 103 return super.dispatchTouchEvent(event); 104 } 105 106 @Override 107 public boolean onInterceptTouchEvent(MotionEvent event) { 108 Log.d(TAG, "onInterceptTouchEvent"); 109 return super.onInterceptTouchEvent(event); 110 } 111 112 @Override 113 public boolean onTouchEvent(MotionEvent event) { 114 String action = ""; 115 switch (event.getAction()) { 116 case MotionEvent.ACTION_DOWN: 117 action = "ACTION_DOWN"; 118 break; 119 case MotionEvent.ACTION_MOVE: 120 action = "ACTION_MOVE"; 121 break; 122 case MotionEvent.ACTION_UP: 123 action = "ACTION_UP"; 124 break; 125 case MotionEvent.ACTION_CANCEL: 126 action = "ACTION_CANCEL"; 127 break; 128 } 129 Log.d(TAG, "onTouchEvent: " + action); 130 return super.onTouchEvent(event); 131 } 132 } 133 134 // ViewEventC 135 public class ViewEventC extends ViewGroup { 136 private static final String TAG = "ViewEventC"; 137 138 public ViewEventC(Context context) { 139 super(context); 140 } 141 142 @Override 143 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 144 setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); 145 } 146 147 @Override 148 protected void onLayout(boolean changed, int l, int t, int r, int b) { 149 150 } 151 152 @Override 153 public boolean dispatchTouchEvent(MotionEvent event) { 154 Log.d(TAG, "dispatchTouchEvent"); 155 return super.dispatchTouchEvent(event); 156 } 157 158 @Override 159 public boolean onInterceptTouchEvent(MotionEvent event) { 160 Log.d(TAG, "onInterceptTouchEvent"); 161 return super.onInterceptTouchEvent(event); 162 } 163 164 @Override 165 public boolean onTouchEvent(MotionEvent event) { 166 String action = ""; 167 switch (event.getAction()) { 168 case MotionEvent.ACTION_DOWN: 169 action = "ACTION_DOWN"; 170 break; 171 case MotionEvent.ACTION_MOVE: 172 action = "ACTION_MOVE"; 173 break; 174 case MotionEvent.ACTION_UP: 175 action = "ACTION_UP"; 176 break; 177 case MotionEvent.ACTION_CANCEL: 178 action = "ACTION_CANCEL"; 179 break; 180 } 181 Log.d(TAG, "onTouchEvent: " + action); 182 return super.onTouchEvent(event); 183 } 184 }
场景1:
当本三个嵌套View中onTouchEvent()方法返回值均为false时,输出:
1 D/ViewEventA: dispatchTouchEvent 2 D/ViewEventA: onInterceptTouchEvent 3 D/ViewEventB: dispatchTouchEvent 4 D/ViewEventB: onInterceptTouchEvent 5 D/ViewEventC: dispatchTouchEvent 6 D/ViewEventC: onInterceptTouchEvent 7 D/ViewEventC: onTouchEvent: ACTION_DOWN 8 D/ViewEventB: onTouchEvent: ACTION_DOWN 9 D/ViewEventA: onTouchEvent: ACTION_DOWN
从结果看出onTouchEvent()方法返回值均为false直到根View,事件消失了,手势被取消了。
场景2:
当本三个嵌套View中第二个View的onTouchEvent()方法返回值为true时,中间View,输出:
1 D/ViewEventA: dispatchTouchEvent 2 D/ViewEventA: onInterceptTouchEvent 3 D/ViewEventB: dispatchTouchEvent 4 D/ViewEventB: onInterceptTouchEvent 5 D/ViewEventC: dispatchTouchEvent 6 D/ViewEventC: onInterceptTouchEvent 7 D/ViewEventC: onTouchEvent: ACTION_DOWN 8 D/ViewEventB: onTouchEvent: ACTION_DOWN 9 D/ViewEventA: dispatchTouchEvent 10 D/ViewEventA: onInterceptTouchEvent 11 D/ViewEventB: dispatchTouchEvent 12 D/ViewEventB: onTouchEvent: ACTION_MOVE 13 D/ViewEventA: dispatchTouchEvent 14 D/ViewEventA: onInterceptTouchEvent 15 D/ViewEventB: dispatchTouchEvent 16 D/ViewEventB: onTouchEvent: ACTION_MOVE 17 D/ViewEventA: dispatchTouchEvent 18 D/ViewEventA: onInterceptTouchEvent 19 D/ViewEventB: dispatchTouchEvent 20 D/ViewEventB: onTouchEvent: ACTION_UP
从结果看出事件在向上冒泡返回时,发现第二个View的onTouchEvent()方法返回值为true时,此View消化了此次事件,并且此手势的其它事件均由第二个View处理。在第二个事件ACTION_UP从根View向下传递时,直到第二个View消化了此次事件,并且不再向下传递事件,同时,在事件被消化后也未再传递此事件。
场景三:
在第二个View中使用onInterceptTouchEvent()方法拦截事件,即第二个View的onInterceptTouchEvent()方法返回值为true,结果输出:
1 D/ViewEventA: dispatchTouchEvent 2 D/ViewEventA: onInterceptTouchEvent 3 D/ViewEventB: dispatchTouchEvent 4 D/ViewEventB: onInterceptTouchEvent 5 D/ViewEventB: onTouchEvent: ACTION_DOWN 6 D/ViewEventA: onTouchEvent: ACTION_DOWN
从结果看出第二个View拦截了手势的ACTION_DOWN事件,事件未再向下(第三个)子View传递,再是在第二个View时,将事件交给了onTouchEvent()方法处理,但是由于第二个View的onTouchEvent()方法返回值为false,事件继续向上(父View)传递。发现没有View处理此次事件,手势被系统取消。
场景4:
当第二个View的onInterceptTouchEvent()方法和onTouchEvent()方法返回值均为true时,拦截事件并消化事件,结果输出:
1 D/ViewEventA: onInterceptTouchEvent 2 D/ViewEventB: dispatchTouchEvent 3 D/ViewEventB: onInterceptTouchEvent 4 D/ViewEventB: onTouchEvent: ACTION_DOWN 5 D/ViewEventA: dispatchTouchEvent 6 D/ViewEventA: onInterceptTouchEvent 7 D/ViewEventB: dispatchTouchEvent 8 D/ViewEventB: onTouchEvent: ACTION_MOVE 9 D/ViewEventA: dispatchTouchEvent 10 D/ViewEventA: onInterceptTouchEvent 11 D/ViewEventB: dispatchTouchEvent 12 D/ViewEventB: onTouchEvent: ACTION_UP
结果与场景二一致。