您的位置:首页 > 移动开发 > Android开发

Android 事件拦截和分发机制分析

2017-01-05 00:15 645 查看
当android系统捕获到用户的各种输入事件后,android提供了一整套完整的事件传递、处理机制,来帮助开发者完成准确的事件分配与处理。触摸事件就是捕获屏幕后的产生的事件。

Android为触摸事件封装了一个类—MotionEvent。

MotionEvent典型的事件类型有以下几种:

ACTION_DOWN—手指刚接触屏幕;

ACTION_UP—手指从屏幕上松开的一瞬间;

ACTION_MOVE—手指在屏幕上移动;

MotionEvent封装了触摸点的坐标,可以通过event.getX()方法和event.getRawX()方法取出坐标点。

Android的View结构是树形结构,也就是说View可以放在ViewGroup里面,通过不同的组合来实现不同的样式。那么问题来了,View放在一个ViewGroup里面,这个ViewGroup又放在另一个ViewGroup里面,甚至可能连续嵌套,一层层叠起来。但是,触摸事件就一个,同一个事件,子View和父ViewGroup都有可能想要进行处理,因此产生了“事件拦截“。

举例说明:假设你所在的公司,有一个总经理,级别最高,他下面又个总经理,级别次之,最底层,就是干活的你,没有级别。现在董事会交给总经理一个任务,总经理将这个任务布置给部长,部长将这个任务安排给了你。当你干完活了,你就把任务交给部长,部长觉得任务完成不错,于是签了名字交给总经理,总经理也觉得不错,就也签了名字交给董事会。这样就一个任务完成了。

——总经理——MyViewGroupA,最外层的ViewGroup

——部长——MyViewGroupB,中间层的ViewGroup

——你——MyView,最底层

对于viewgroup,重写如下三种方法:

/**
* 用来进行事件分发。如果事件能够传递给当前View,那么此方法一定被调用,
* 返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法影响,
* 表示是否消耗当前事件
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("chunsoft","ViewGroupA dispatchTouchEvent"+ev.getAction());
return super.dispatchTouchEvent(ev);
}

/**
* 在上述方法内部调用,用力啊判断是否拦截某个事件,返回结果表示是否消耗当前事件,
* 如果不消耗,则在同一个事件序列中,此方法不会再次被调用,返回结果表示是否拦截当前事件
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d("chunsoft","ViewGroupA onInterceptTouchEvent"+ev.getAction());
return super.onInterceptTouchEvent(ev);
}

/**
* 在dispatchTouchEvent方法中被调用,用来处理点击事件,返回结果表示是否消耗当前事件,
* 如果不消耗,在同一事件序列当中,当前View无法再次接收到事件
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("chunsoft","ViewGroupA onTouchEvent"+event.getAction());
return super.onTouchEvent(event);
}


对于View来说,重写如下所示的两个方法:

@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("chunsoft","View onTouchEvent" + event.getAction());
return super.onTouchEvent(event);
}

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d("chunsoft","View dispatchTouchEvent" + event.getAction());
return super.dispatchTouchEvent(event);
}


从上面代码可以看出,ViewGroup级别较高比View多了一个方法—onInterceptTouchEvent(),从名字可以看出这是事件拦截的核心方法,点击下,view,看log会怎样记录我们的操作和程序的响应。如下图,最小的正方形View是自定义View,而其他两个View都是自定义的ViewGroup。



(1)正常点击MyView

点击View后的Log如下所示:

chunsoft: ViewGroupA dispatchTouchEvent
chunsoft: ViewGroupA ViewGroupA onInterceptTouchEvent
chunsoft: ViewGroupB dispatchTouchEvent
chunsoft: ViewGroupB onInterceptTouchEvent
chunsoft: View dispatchTouchEvent
chunsoft: View onTouchEvent
chunsoft: ViewGroupB onTouchEvent
chunsoft: ViewGroupA onTouchEvent


事件传递顺序:

总经理(ViewGroupA)—>部长(ViewGroupB)—>你(View)。事件传递的时候先执行dispatchTouchEvent()方法,再执行onInterceptTouchEvent()方法。

事件传递的返回值,True,拦截,不继续;False,不拦截,继续;默认false;

事件处理顺序:

你(View)—>部长(ViewGroupB)—>总经理(ViewGroupA)

事件处理的返回值:True,处理了,不用审核;False,给上级处理。初始值false

在事件传递中,虽然dispatchTouchEvent()是事件分发的第一步,但是一般情况不会改写,onInterceptTouchEvent()是主要关注的。

(2)总经理(MyViewGroupA)拦截事件传递

假设总经理(MyViewGroupA)发现这个任务太简单,自己完成。因此事件被总经理(MyViewGroupA)使用onInterceptTouchEvent()方法把事件拦截下来,即让MyViewGroupA的onInterceptTouchEvent()方法返回true,log日志如下:

chunsoft: ViewGroupA dispatchTouchEvent
chunsoft: ViewGroupA ViewGroupA onInterceptTouchEvent
chunsoft: ViewGroupA onTouchEvent


(3)部长(MyViewGroupB)拦截事件传递

让部长(MyViewGroupB)使用onInterceptTouchEvent()方法返回true拦截事件传递,log日志如下:

chunsoft: ViewGroupA dispatchTouchEvent
chunsoft: ViewGroupA ViewGroupA onInterceptTouchEvent
chunsoft: ViewGroupB dispatchTouchEvent
chunsoft: ViewGroupB ViewGroupA onInterceptTouchEvent
chunsoft: ViewGroupB onTouchEvent
chunsoft: ViewGroupA onTouchEvent


(3)事件处理

一般情况下,当你(MyView)处理完任务会向上级报告,需要上级确认,所以你的事件处理返回false。突然有一天你受不了老板的压迫,罢工,那么你的任务没人做,也不用报告上级,直接返回true,Log如下所示:

chunsoft: ViewGroupA dispatchTouchEvent
chunsoft: ViewGroupA ViewGroupA onInterceptTouchEvent
chunsoft: ViewGroupB dispatchTouchEvent
chunsoft: ViewGroupB onInterceptTouchEvent
chunsoft: View dispatchTouchEvent
chunsoft: View onTouchEvent


事件传递跟以前一样,但是事件处理,到你(MyView)这就结束了,因为你返回True,表示不用向上级汇报了。

假如你(MyView)发送了报告,如果部长觉得你的报告太丢人,不给总经理看,返回True,即将MyViewGroupB的onTouchEvent返回Ture,Log如下所示,

chunsoft: ViewGroupA dispatchTouchEvent
chunsoft: ViewGroupA ViewGroupA onInterceptTouchEvent
chunsoft: ViewGroupB dispatchTouchEvent
chunsoft: ViewGroupB onInterceptTouchEvent
chunsoft: View dispatchTouchEvent
chunsoft: View onTouchEvent
chunsoft: ViewGroupB onTouchEvent


(5)底层View阻止父层的View截获touch事件

底层的View能够接收到这次的事件有一个前提条件:在父层级允许的情况下。假设不改变父层级的dispatch方法,在系统调用底层onTouchEvent之前会先调用父View的onInterceptTouchEvent方法判断,父层View是不是要截获本次touch事件之后的action。如果onInterceptTouchEvent返回了true,那么本次touch事件之后的所有action都不会再向深层的View传递,统统都会传给父层View的onTouchEvent,就是说父层已经截获了这次touch事件,之后的action也不必询问onInterceptTouchEvent,在这次的touch事件之后发出的action时onInterceptTouchEvent不会再次调用,直到下一次touch事件的来临。如果onInterceptTouchEvent返回false,那么本次action将发送给更深层的View,并且之后的每一次action都会询问父层的onInterceptTouchEvent需不需要截获本次touch事件。只有ViewGroup才有onInterceptTouchEvent方法,因为一个普通的View肯定是位于最深层的View,touch事件能够传到这里已经是最后一站了,肯定会调用View的onTouchEvent。

对于底层的View来说,有一种方法可以阻止父层的View截获touch事件,就是调用getParent().requestDisallowInterceptTouchEvent(true);方法。一旦底层View收到touch的action后调用这个方法那么父层View就不会再调用onInterceptTouchEvent了,也无法截获以后的action。

实例

(1)子控件和父控件的事件响应问题

当父控件中有子控件的时候,并且父控件和子空间都有事件处理(比如单击事件)。这时,点击子控件,父控件的单击事件就无效了。

比如一个LinearLayout里面有一个子控件TextView,但是TextView的大小没有LinearLayout大。

①如果LinearLayout和TextView都设置了单击事件,那么点击TextView区域的时候,触发的是TextView的事件,点击TextView以外的区域的时候,还是触发的LinearLayout的事件。

②如果LinearLayout设置了单击事件,而TextView没有设置单击事件的话,那么不管单击的是TextView区域,还是TextView以外的区域,都是触发的LinearLayout的单击事件。

如果LinearLayout的大小和TextView一样的话,那么

①如果LinearLayout和TextView都设置了单击事件,那么只有TextView的单击事件有效。

②如果LinearLayout设置了单击事件,而TextView没有设置单击事件的话,那么触发的是LinearLayout的单击事件。

(2)onTouch()和onTouchEvent()区别

源码

public boolean dispatchTouchEvent(MotionEvent event){
... ...
if(onFilterTouchEventForSecurity(event)){
ListenerInfo li = mListenerInfo;
if(li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
return true;
}
if(onTouchEvent(event)){
return true;
}
}
... ...
return false;
}


从源码中可以看出,这两个方法都是在View的dispatchTouchEvent中调用的,onTouch优先于onTouchEvent执行。如果在onTouch方法中通过返回true将事件消费掉,onTouchEvent将不会再执行。

onTouch能够得到执行需要两个前提条件,第一mOnTouchListener的值不能为空,第二当前点击的控件必须是enable的。因此如果你有一个控件是非enable的,那么给它注册onTouch事件将永远得不到执行。对于这一类控件,如果我们想要监听它的touch事件,就必须通过在该控件中重写onTouchEvent方法来实现。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息