android_100_事件拦截

本文详细探讨了Android中事件拦截的流程,从最外层的父视图开始,逐层向下传递,直到某一视图拦截成功。拦截成功后,事件会由拦截的视图进行处理,是否处理由onTouchEvent的返回值决定。onInterceptTouchEvent负责拦截,onTouchEvent负责事件处理。如果所有视图都不处理事件,最终会交给Activity处理。
摘要由CSDN通过智能技术生成

 

布局:

                      图1

参照上图先说说具体得到的结论:

1) onInterceptTouchEvent负责对touch事件进行拦截,对于嵌套的view最先执行的是事件拦截方法的是:

最外层的那个viewonInterceptTouchEvent方法,然后依次执行:子视图的onInterceptTouchEvent,

然后再执行子视图的子视图的事件拦截方法(当然在这里假设所有嵌套视图的onInterceptTouchEvent都会得到执行,即让每个视图的onInterceptTouchEvent返回false,表示不拦截事件)

参照上图,所以onInterceptTouchEvent执行顺序就是A--->B--->C--->D.也就是由父视图到子视图传递

总之,事件拦截机制是由父视图开始发起对事件的拦截(出事了老子先上,儿子其后)

参照上图当手指触摸事件时,父视图A首先发起对该起事件的拦截,如果A拦截失败(或不拦截,即返回值是false),就交给它的子视图B进行拦截;

如果B拦截失败(或不拦截,即返回值是false),就交给B的子视图C再进行拦截.....直到某一子视图对该次事件拦截成功。

 

2)某一视图拦截事件成功与否的判断标识是onInterceptTouchEvent方法的返回值,当返回true的时候说明拦截成功,返回false的时候说明当前视图对事件拦截失败。

 

3)下面说说拦截成功的情况,假设C视图对当前touch事件拦截成功。那么一旦拦截成功,就意味着此次事件不会再传递到D视图了。

所以此时的D视图的onInterceptTouchEvent就得不到运行(事件没法到达了,还拦截谁呢?)

 

4)事件拦截成功后,紧接着就会对事件进行处理,处理的方法教给onTouchEvent方法处理。

此时假如:C视图拦截成功,那么紧接着就会执行C视图的onTouchEvent方法这是不是就意味着当前touch事件一定是由C视图的onTouchEvent方法来处理的呢?

非也非也,这还要由C视图的onTouchEvent方法的返回值来决定

C视图的onTouchEvent返回true的时候,当前事件就由C全权处理,处理的当然是事件的各种action,什么MotionEvent.ACTION_DOWN、MOVE、UP都交给了ConTouchEvent方法进行处理。

所以此时就可以在ConTouchEvent方法中进行switch(event.getAction)判断执行相关逻辑了。

如果返回的false,说明C视图虽然把此事件拦截下来了,但是却对此事件不做处理或者处理不了,怎么办呢?

儿子不行交回老子来,于是事件就重新交回到了B视图的onTouchEvent方法中

同样的,B对此事件是否处理还是看BonTouchEvent返回值,具体的解释就跟C一样了,不复多言。

 

4)A B C DonInterceptTouchEventonTouchEvent都返回false的情况下,方法执行的顺序依次为A.onInterceptTouchEvent-->B.onInterceptTouchEvent-->C.onInterceptTouchEvent-->D.touchEvent(最深的子视图没重写onInterceptTouchEvent)-->C.touchEvent-->B.touchEvent-->A.touchEvent.也就是说拦截事件是父视图优先有子视图进行拦截,处理事件是子视图优先父视图进行处理。

 

 总结:onInterceptTouchEvent负责对事件进行拦截,拦截成功后交给最先遇到onTouchEvent返回true的那个view进行处理

 

控件View对事件的拦截和处理,简单的总结如下:

           1)父View优先拦截当前事件,拦截不成功就让子View对当前事件进行拦截。

            2)如果拦截成功的话,就会沿着子view到父View的路径查找onTouchEvent返回true的那个子View,让该子View对该事件进行处理;

             3)同时如果某一个View对当前事件拦截成功的话,当前事件就不会再继续分发给这个View的子View。

 

 

 

1)android对事件分发的顺序为:Activity-->PhoneWindow->DecorView->yourView;

2)android控件对事件处理的优先级:onTouch > onTouchEvent > onClick

android既然可以对事件进行拦截,肯定有某个方法对事件进行的分发。完成事件分发功能的方法由Activity的dispatchTouchEvent(MotionEvent ev)来负责:

 

 

 

上述分发事件的方法dispatchTouchEvent,先把事件分发给Window,这个Window其实就是PhoneWindow,而PhoneWindow

 

直接把此次事件传给DecorView,这个View是所有视图的根视图,Activity界面中你能见到的各个View都是DecorView的子View。

到此为止事件已经分发到View上面,View获取到事件后有两个选择:处理和不处理该事件,如果处理该事件那事件就不会继续向其子View分发下去;

否则就继续分发下去交给子View对该事件做同样的判断,其实就是个递归的过程

 

结论:当Activity中所有的视图View都不处理该事件的是就交给Activity的onTouchEvent方方来处理

 

 

 

 

1)ViewGroup永远不会对事件拦截,因为其的onInterceptTouchEvent(MotionEvent ev)始终返回的是false!

这样DecorView对到来的事件MotionEvent就只有分发到子View并由子View进行拦截和处理此事件了.

 

2)View包括直接继承于View的子类因为其父类View没有onInterceptTouchEvent方法,所以没法对事件进行拦截,如果这种View获取到了事件,那么就会执行onTouchEvent方法(当然这也是有条件的,这个前提条件在对下面onTouch方法作用的时候会有说明)。

 

 

 

结论:

onTouch方法优先于onTouchEvent执行,

这个onTouch是否能实行取决于你的View有有没有调用setOnTouchListener方法设置OnTouchListener。

如果onTouch方法返回的true,那么View的dispatchTouchEvent方法就返回true而结束执行,onTouchEvent方法就不会得到执行;

因为onClick方法也是在onTouchEvent方法中执行的,所以此时onClick方法也不会执行了。

 

因此,如果不对Button设置onTouchListener的话程序会执行View的onTouchEvent方法:

 

 

可以得到一个结论:如果View类的的dispatchTouchEvent返回true的话,就表明有某个View对该起事件负责(进行处理)

如果这个view是可点击的(clickable=true或者longClickable=true)那么这样的View最终都会处理当前事件而让View的dispatchTouchEvent返回true,这样的View就是下面将要说道的处理事件的target!!!否则,即如果一个View既不是clickable也不是longClickable的话,那么这个View不会处理该事件。

 

 

 

 

 

 

for循环之所以能得以执行使用两个前提条件的:

1)事件为ACTION_DOWN事件,在Down事件中才去进行分拦截发事件并寻找处理事件的target View

2)disallowIntercept 禁止拦截    为true,这个属性可通过requestDisallowInterceptTouchEvent(boolean disallowIntercept  请求父类不拦截)来设置,通过观察其具体实现,可以知道该方法的作用就是子View干预父View的事件分发过程,

 

当然对于ACTION_DOWN事件子类是不能干预父类的,因为if条件为(disallowIntercept ||!onInterceptTouchEvent(ev))为或操作;或者当前的View没有拦截成功该事件。???????

(不允许拦截,或者没有拦截成功)

 

 

如果当前的ViewGroup(为了方便再次称之为ParentView)允许对此次事件进行拦截或者ParentView没有对此事件拦截成功(ParentView的onInterceptTouchEvent返回false)简而言之就是如果ParentView不拦截该事件,就把该事件分发到ParentView的若干子类中去,循环遍历它的子类,来寻找是否有某个子类对处理该事件。

 

如果找到了这样的View,就对该View用一个变量mMotionTarget进行标识。

 

如果在当前的ParentView的子View中没有找到处理该事件的子View会怎么办呢?在ViewGroup里面的dispatchTouchEvent在上面的for循环之后:

 

如果在ParentView的子类中没有找到能处理问题的那个view,就调用parentView的父View的dispatchTouchEvent方法。

 

我们应该知道之所以parentView能分发和拦截事件,前提是它的父类本来没有拦截事件的能力或如本身拦截事件的方法返回了false,所以沿着view树最终会调用的View类的dispatchTouchEvent,那么又回归到View类对事件的处理那一部分了。

 

注意前面讲的for循环查找的重大前提是:是在down事件中,且我们要明白在手指接触屏幕到手指离开屏幕会产生一系列事件,一个down(ACTION_DOWN)事件,数个move(ACTION_MOVE)事件,和一个 UP事件。

寻找到目标事件之后,之后的一些列事件都交给这个target处理,比如move事件等。

当然倒是可以通过让target调用requestDisallowInterceptTouchEvent方法来干预父类关于事件分发过程。

或者在在适当的情况下让target父View的onInterceptEvent返回true或者false,来解决滑动问题事件的冲突问题:

 

最后某个View(target)如果开始处理事件,在手指离开屏幕之前的什么move事件啦,up事件啦都会交给这个View(target)来处理,因为在ViewGroup的diapatchTouchEvent代码的最后会执行:

 

 

  1. //把事件交给目标视图来处理  
  2.   return target.dispatchTouchEvent(ev);  

并且本View的onInterceptTouchEvent是不会调用了。

也就是说如果有一个view处理该事件,那么down之后的一系列move事件和up事件都自动交给该view处理,因为该view已经是targetView 了,所以不会对后续事件序列或者事件集合进行拦截操作,只会调用dispatchTouchEvent和onTunchEvent来处理该事件!而onInterceptTouchEvent事件不会调用。

 

简单的举个例子,如图:

假设上图中由D进行事件的处理,也就是说D的onInterceptTouchEvent和onTouchEvent均返回true,

D的父View A ,B ,C的这两个方法都返回false,那么在这个布局中D就是上面所说的 target.

在首次的Down事件中会执行查找target的操作:(注:下图中分发事件指的是执行了dispatchTouchEvent,拦截事件指的是onInterceptTouchEvent,处理事件为onTouchEvent方法)

  1. @Override  
  2.     public boolean dispatchTouchEvent(MotionEvent ev) {  
  3.         Log.e(null, "B--分发事件");  
  4.         return super.dispatchTouchEvent(ev);  
  5.     }  
  6.     @Override  
  7.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  8.         Log.e(null, "B--拦截事件");  
  9.         return super.onInterceptTouchEvent(ev);  
  10.     }  
  11.     @Override  
  12.     public boolean onTouchEvent(MotionEvent event) {  
  13.         Log.e(null, "B--处理事件");  
  14.         return super.onTouchEvent(event);  
  15.     }  

 

 

 

注意D此时的打印,D为此事件的target View,拦截和处理了该次down事件,

 

那么如果此时手指继续移动的话将会打印如下的Log:

注意上图是手指滑动后打印的log,可以发现D只负责分发和处理该事件,而没有像down事件那样进行拦截了,所以上面的log打印可以清晰的说明上面的结论:

 

1)targetView只会对一个事件序列拦截一次,即只拦截down事件或者说由down事件负责查找targetView

2)一旦targetView被找到,down事件之后的一些列事件都由target View负责处理,而target并不对后续事件进行再次拦截

3)当然在down事件之后的后续事件还是会先由父View进行分发拦截,也即是说文章开头所说的事件序列中把每一个事件单独来看的话,都会由父View来进行拦截和分发的,只不过到后续事件到传到target的时候直接进行处理而少了拦截的过程而已,因为在父类查找target的时候已经拦截过一次,这点很重要,也是解决滑动冲突的关键点,比如滑动的时候根据合适的时机来判断是否让父View进行事件拦截和处理。只不过省下了对targetView的寻找,因为在down事件中已经寻找到了target并有mMotionTarget变量进行了标识,(通过上面的对ViewGroup的dispatchTouchEvent源码的解析就可以表明出来)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值