MotionEvent
系统有一个线程在循环收集屏幕硬件信息,当用户触摸屏幕时,该线程会把从硬件设备收集到的信息封装成一个MotionEvent对象,然后把该对象存放到一个消息队列中。
系统的另一个线程循环的读取消息队列中的MotionEvent,然后交给WMS去派发,WMS把该事件派发给当前处于活动的Activity,即处于活动栈最顶端的Activity。
这就是一个先进先出的消费者和生产者的模板,一个线程不停的创建MotionEvent对象放入队列中,另一个线程不断的从队列中取出MotionEvent对象进行分发。
当用户的手指从接触屏幕到离开屏幕,是一个完整的触摸事件,在该事件中,系统会不断收集事件信息封装成MotionEvent对象。收集的间隔时间取决于硬件设备,例如屏幕的灵敏度以及cpu的计算能力。目前的手机一般在20毫秒左右。
MotionEvent中的事件:
ACTION_DOWN ——> 表示按下了屏幕,一个事件必然从ACTION_DOWN开始
ACTION_MOVE ——> 表示移动手势
ACTION_UP ——> 表示离开屏幕
ACTION_CANCEL ——> 表示取消手势,一般由程序产生,不会由用户产生
一个ACTION_DOWN, n个ACTION_MOVE,1个ACTION_UP,就构成了Android中众多的事件。
Android中的事件onClick, onScroll, onFling等等,都是由许多个Touch组成的。
一个原则,所有的touch事件都是从父容器开始向下传递的,呈U字形。
MotionEvent中的坐标:
可知:
getRawX:触摸点相对于屏幕的坐标
getX: 触摸点相对于按钮的坐标
getTop: 按钮左上角相对于父view(LinerLayout)的y坐标
getLeft: 按钮左上角相对于父view(LinerLayout)的x坐标
可以想象 getRight()等同于下面的计算:getLeft()+getWidth()。
View事件处理机制核心代码
Android中诸如ImageView、textView、Button等控件都没有重写View的dispatchTouchEvent方法,所以View的事件处理机制对这些控件都有效。
View.java(基于android2.3.3):
public boolean dispatchTouchEvent(MotionEvent event) {//返回true,表示该View内部消化掉了所有事件。返回false,表示View内部只处理了ACTION_DOWN事件,事件继续传递,向上级View(ViewGroup)传递。
...
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {//此处的onTouch方式就是回调的我们注册OnTouchListener时重写的onTouch()方法
return true;
}
if (onTouchEvent(event)) {// onTouchEvent参考下面源码
return true;
}
...
}
public boolean onTouchEvent(MotionEvent event) {
...
// 当前onTouch的组件必须是可点击的比如Button,ImageButton等等,此处CLICKABLE为true,才会进入if方法,最后返回true。
// 如果是ImageView、TexitView这些默认为不可点击的View,此处CLICKABLE为false,最后返回false。当然会有特殊情况,如果给这些View设置了onClick监听器,此处CLICKABLE也将为true,参考下面源码
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
...
if (!post(mPerformClick)) {
performClick();// 实际就是回调了我们注册的OnClickListener中重新的onClick()方法,源码下面源码
}
...
break;
case MotionEvent.ACTION_DOWN:
...
break;
case MotionEvent.ACTION_CANCEL:
...
break;
case MotionEvent.ACTION_MOVE:
...
break;
}
return true;
}
return false;
}
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
public boolean performClick() {
...
if (li != null && li.mOnClickListener != null) {
...
li.mOnClickListener.onClick(this);
return true;
}
return false;
}
总结:
OnTouchListener不为null(即给view注册了OnTouchListener)——> 重写的onTouch()方法中返回false ——> 执行onTouchEvent方法 ——> 当前控件是ENABLED状态——> onClick()回调方法执行
OnTouchListener不为null ——> 重写的onTouch()方法返回true ——> onTouchEvent方法不执行 ——> onClick()回调方法不会执行
OnTouchListener为null ——> 执行onTouchEvent方法 ——> 当前控件是ENABLED状态——> onClick()回调方法执行
OnTouchListener为null ——> 执行onTouchEvent方法 ——> 当前控件不是ENABLED状态——> onClick()回调方法不会执行
ViewGroup事件处理机制核心代码
Android中诸如LinearLayout等的五大布局控件,都是继承自ViewGroup,而ViewGroup本身是继承自View,所以ViewGroup的事件处理机制对这些控件都有效。
ViewGroup.java(基于android2.3.3):
<pre class="java" name="code">@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
mMotionTarget = null;// mMotionTarget设置为null,后面用来记录子view
}
//onInterceptTouchEvent默认返回false,说明向下传递
//onInterceptTouchEvent返回true,说明拦截
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
...
// 伪代码如下:
// 倒着迭代所有的子view
for (int i = childrenCount - 1; i >= 0; i--) {
//1,一个个取出子view(child),判断是否是可见的(visible)
//2,得到当前手指touch的位置坐标(x,y)
//3, 判断坐标(x,y)是否落在child的矩形区域内
//4, 如果在矩形区域内,判断child是viewgroup的子类对象,还是view的子类对象
if (child.dispatchTouchEvent(ev)) {
mMotionTarget = child;// 如果当前child能够消费这个事件,那么就把child赋值为mMotionTarget(以后的事件都交给mMotionTarget处理)
return true;
}
//4.1 如果是viewgroup的子类对象: 调用ViewGroup的dispatchTouchEvent方法(必须从第一行开始重新再来一遍)
//4.2 如果是view的子类对象: 调用View的dispatchTouchEvent方法处理事件——>onTouchEvent()方法执行
// 具体参考上面”View事件处理机制核心代码“的内容
// (onTouchEvent方法返回true——>dispatchTouchEvent方法返回true,23、24行代码被执行,程序reurn ture,整个down事件分发结束。
// (onTouchEvent方法返回false——>dispatchTouchEvent方法返回false, 23、24行代码不会执行,程序继续向上递归,走第41行代码
...
}
}
}
...
target = mMotionTarget;
//由24,25行代码可知,程序能走到这里,target一定是null,说明子view没有消费这个事件。
if (target == null) {
...
//调用View的dispatchTouchEvent方法处理事件——>onTouchEvent()方法执行 具体参考上面”View事件处理机制核心代码“的内容
return super.dispatchTouchEvent(ev);
}
...
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;// 默认返回false
}
总结:
1、dispatchTouchEvent(事件分发):决定事件是否由onInterceptTouchEvent来拦截处理。
返回super.dispatchTouchEvent时,由onInterceptTouchEvent来决定事件的流向
返回false时,会继续分发事件,自己内部只处理了ACTION_DOWN
返回true时,不会继续分发事件,自己内部处理了所有事件(ACTION_DOWN,ACTION_MOVE,ACTION_UP)
2、onInterceptTouchEvent(事件拦截):拦截事件,用来决定事件是否传向子View
返回true时,拦截后交给自己的onTouchEvent处理
返回false时,拦截后交给子View来处理
3、onTouchEvent(事件处理):事件最终到达这个方法
返回true时,内部处理所有的事件,换句话说,后续事件将继续传递给该view的onTouchEvent()处理
返回false时,事件会向上传递,由onToucEvent来接受,如果最上面View中的onTouchEvent也返回false的话,那么事件就会消失
ACTION_DOWN是最重要处理最复杂的事件,它作为一个探路者,一路跋山涉水下来都在寻找onTouchEvent()方法返回true的view(事件终结者), 只要找到了这个view,以后的ACTION_MOVE,ACTION_UP事件都将作用在这个view身上,被这个view终结掉。
综合示例分析
以下摘自:http://www.longdw.com/android-onintercepttouchevent-ontouchevent/
源码:
public class MainActivity extends Activity {
Group1 group1;
Group2 group2;
MyTextView myTv;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//--group1
//----|
//-------group2
//---------|
//------------myTv
group1 = new Group1(this);
group2 = new Group2(this);
myTv = new MyTextView(this);
group2.addView(myTv, new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT));
group1.addView(group2, new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT));
setContentView(group1);
}
}
public class Group1 extends FrameLayout {
public Group1(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
Log.d(Constant.LOGCAT, "Group1 onInterceptTouchEvent触发事件:"+Constant.getActionTAG(ev.getAction()));
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
Log.d(Constant.LOGCAT, "Group1 onTouchEvent触发事件:"+Constant.getActionTAG(event.getAction()));
return false;
}
}
public class Group2 extends FrameLayout {
public Group2(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
Log.d(Constant.LOGCAT, "Group2 onInterceptTouchEvent触发事件:"+Constant.getActionTAG(ev.getAction()));
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
Log.d(Constant.LOGCAT, "Group2 onTouchEvent触发事件:"+Constant.getActionTAG(event.getAction()));
return false;
}
}
public class MyTextView extends TextView {
public MyTextView(Context context) {
super(context);
this.setGravity(Gravity.CENTER);
this.setText("点击我!");
// TODO Auto-generated constructor stub
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
Log.d(Constant.LOGCAT, "MyTextView onTouchEvent触发事件:"+Constant.getActionTAG(event.getAction()));
return false;
}
}
public class Constant {
public static final String LOGCAT = "logcat";
public static String getActionTAG(int action) {
switch (action) {
case 0:
return "ACTION_DOWN";
case 1:
return "ACTION_UP";
case 2:
return "ACTION_MOVE";
default:
return "NULL";
}
}
}
分别重写Group1和Group2的onInterceptTouchEvent和onTouchEvent方法,重写MyTextView的onTouchEvent方法,最终得到的控件层次结构如下:
1.在默认返回值情况下logcat输出如下:
测试后可知默认情况下和所有方法返回值为false的结果一致,down事件的捕获顺序onInterceptTouchEvent先于onTouchEvent,由于onTouchEvent返回值为false,down事件没被消化,后续的move和up事件没有出现,同时逆序返回到父控件的onTouchEvent方法来捕获,如下图所示:
2.所有onTouchEvent返回值为true情况下logcat输出如下:
输出结果可以看出子控件MyTextView消化了down事件,后续的move和up事件正常捕获,由于down事件被消化,上层的onTouchEvent方法不执行,如下图所示:(三箭头分别指down、move、up事件)
既然如此,如果MyTextView中onTouchEvent方法返回为false,而group1和group2的onTouchEvent方法返回true的结果自然也就如下图的顺序了:
测试输出结果证明了这一猜测顺序:
注意问题:
ACTION_MOVE为什么没有经历myTv?
由于myTv的onTouchEvent返回了false,相当于告诉系统:“我处理不了touch事件”,导致down事件没被终结(down事件都终结不了,后续的其他事件都不会再给它了。换句话说,系统最终是Group2 的onTouchEvent方法终结掉了down事件,后续事件都将交给Group2的onTouchEvent去处理,没myTv什么事了)
ACTION_MOVE只经历了group1的onInterceptTouchEvent和group2的onTouchEvent而没有经历group2的onInterceptTouchEvent?
大家不要忘记了onInterceptTouchEvent的初衷是什么,返回false是让它的子view或viewgroup类处理,而group2的子控件显然是myTv,而myTv的onTouchEvent返回了false也就是接收不到后续的mov和up事件,也就没必要经过onInterceptTouchEvent来继续分发了(因为分发了也还是接收不到),group2的onInterceptTouchEvent方法也就没有执行的必要了,直接传递给group2的onTouchEvent因为它返回的是true,所有事件都将由它截获并且终结掉。
3.当某个GroupView中的onInterceptTouchEvent方法返回值为true情况下logcat输出如下(如group2):
如果group2的onInterceptTouchEvent方法返回true,那么子控件将获取不到任何点击事件,转而向自身的onTouchEvent方法转发,onTouchEvent方法返回false,事件向上传递,如下图所示:
如果group2的onTouchEvent方法返回true,那么根据规律结果就如下图顺序触发:
最后logcat的结果证实了这一猜测:
还有一篇文章也比较好,可作为这个案例的补充,http://orgcent.com/android-touch-event-mechanism/