Android View 虽然不是四大组件,但其并不比四大组件的地位低。而View的核心知识点事件分发机制则是Android开发过程中一个重点也是难点。ScrollView嵌套RecyclerView(或者ListView)的滑动冲突这种问题的理论基础就是事件分发机制。
Android中的事件分发机制也就是View与ViewGroup的对事件的分发与处理。在ViewGroup的内部包含了许多View,而ViewGroup继承自View,所以ViewGroup本身也是一个View。对于事件可以通过ViewGroup下发到它的子View并交由子View进行处理,而ViewGroup本身也能够对事件做出处理。
什么是事件?什么是事件序列?
我们对屏幕的点击,滑动,抬起等一系的动作都是由一个一个MotionEvent对象组成的。根据不同动作,主要有以下三种事件类型:
1.ACTION_DOWN:手指刚接触屏幕,按下去的那一瞬间产生该事件
2.ACTION_MOVE:手指在屏幕上移动时候产生该事件
3.ACTION_UP:手指从屏幕上松开的瞬间产生该事件
上面提到了三种事件ACTION_DOWN、ACTION_MOVE和ACTION_UP。它们都是单独的事件,而事件序列由它们组成。一个事件序列指的是:由一个ACTION_DOWN事件,0个或者1个或者多个ACTION_MOVE事件,加上一个ACTION_UP事件组成的一个序列。
事件是用户与屏幕发生交互时产生的,而Activity则是Android中负责与用户发生交互的组件。所以事件的传递,首先是到达Activity,再通过内部传递之后,到达我们的布局文件中的layout和View。事件发生之后,需要进行响应处理,再传递的过程中,由上往下,都有可能有机会处理一个事件序列。如果从Activity往下,到最终的View,事件都没有得到处理,则事件又从下往上,回到Activity,如果回到Activity之后,Activity没有处理这个事件,那么这个事件就会自动结束。
下面简单看下三个事件方法:
mButton.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
e("MotionEvent: ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
e("MotionEvent: ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
e("MotionEvent: ACTION_UP");
break;
}
return false;
}
});
定义一个按钮,设置触摸事件,根据手指的触发,执行不同的类型是事件。
事件分发的三个方法:
dispatchTouchEvent:用于分发传递事件,只要事件能够传递到当前View,这个方法就会被调用。
onInterceptTouchEvent:用于判断是否拦截事件,不往下传递。(此方法只有ViewGroup拥有,Activity和View没有)此方法在dispatchTouchEvent方法中调用。
onTouchEvent:用于处理事件,同样也是在dispatchTouchEvent方法中调用。
ViewGroup的事件分发方法:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
show("dispatchTouchEvent",ev);
//switch
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
show("onInterceptTouchEvent",ev);
//switch
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
show("onTouchEvent",event);
return super.onTouchEvent(event);
}
View的事件分发方法:
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
show("dispatchTouchEvent",event);
//switch
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
show("onTouchEvent",event);
//switch
return super.onTouchEvent(event);
}
View是最后的事件处理,所以没有拦截事件。
dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev)
1)return true: 表示消耗了当前事件,有可能是当前 View 的 onTouchEvent 或者是子 View 的 dispatchTouchEvent消费了,事件终止,不再传递。
2)return false: 调用父 ViewGroup 或 Activity 的 onTouchEvent。 (不再往下传)。
3)return super.dispatherTouchEvent: 则继续往下(子 View )传递,或者是调用当前 View 的 onTouchEvent 方法;
总结: 用来分发事件,即事件序列的大门,如果事件传递到当前 View 的 onTouchEvent或者是子 View 的 dispatchTouchEvent,即该方法被调用了。 另外如果不消耗 ACTION_DOWN 事件,那么 down, move, up 事件都与该 View 无关,交由父类处理(父类的 onTouchEvent 方法)
onInterceptTouchEvent
public boolean onInterceptTouchEvent(MotionEvent ev)
1)return true: ViewGroup 将该事件拦截,交给自己的 onTouchEvent 处理。
2)return false: 继续传递给子元素的 dispatchTouchEvent 处理。
3)return super.dispatherTouchEvent: 事件默认不会被拦截。
总结: 在 dispatchTouchEvent 内部调用,顾名思义就是判断是否拦截某个事件。(注:ViewGroup 才有的方法,View 因为没有子View了,所以不需要也没有该方法) 。而且这一个事件序列(当前和其它事件)都只能由该 ViewGroup 处理,并且不会再调用该 onInterceptTouchEvent 方法去询问是否拦截。
onTouchEvent
public boolean onTouchEvent(MotionEvent ev)
1)return true: 事件消费,当前事件终止。
2)return false: 交给父 View 的 onTouchEvent。
3)return super.dispatherTouchEvent: 默认处理事件的逻辑和返回 false 时相同。
总结: 在dispatchTouchEvent内部调用
上面3个方法的关系,可以用下面的方法表示:
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;//事件是否被消费
if (onInterceptTouchEvent(ev)){//调用 onInterceptTouchEvent 判断是否拦截事件
consume = onTouchEvent(ev);//如果拦截则调用自身的onTouchEvent方法
}else{
consume = child.dispatchTouchEvent(ev);//不拦截调用子View的dispatchTouchEvent方法
}
return consume;//返回值表示事件是否被消费,true事件终止,false调用父View的onTouchEvent方法
}
View的事件分发也是一个顺序执行的操作,自上而下传递,自下而上的回调,直到在某个地方被消费了,下面会按照事件分发的顺序,依次介绍