关于事件分发,其实看了很多资料,看的时候都是看得懂,但是实际运用于事件冲突的时候,却不知道如何下手,希望能写完这篇文章跟事件冲突的文章后,能有所进步。
先上代码 ,很实用的伪代码
//伪代码
public void dispatchTouchEvent(MotionEvent ev){
boolean consume = false ;//用这个变量 实现单通道出口
if(interceptTouchEvent(ev)){//拦截方法 默认是不拦截的
consume = onTouchEvent(ev);
}else{
consume = child.dispatchTouchEvent(ev);//父类如果不拦截 就会将事件传递到子类去,这样一直传下去 ,直到子类处理 返回true ,如果一直不处理即返回false ,那又会逐级上传
}
return consume ; //如果为true 表示这个事件被当前的view所消费了
}
MotionEvent 点击事件对象 三个重点方法 dispatchTouchEvent() onInterceptTouchEvent() onTouchEvent()
dispatchTouchEvent(): 用于传递事件 ,如果一个view能接收到事件,那它的这个方法必定会被触发,可以理解为这是view的事件入口,返回值代表着是否消耗了这个事件。
onInterceptTouchEvent():用于拦截事件,只有viewgroup中才有这个方法,另外 如果这个view拦截了这个事件,那么在接下来的事件当中,他不会被触发,拦截后消费掉这个事件,那之后的方法也会直接跳到这个view头上,然而拦截后却不消费掉这个方法(即onTuchEvent()方法返回false),那么当前的view是无法再接收到事件了,之后的事件会跳转到父view去。 具体的内容,可以看代码。
onTouchEvent(): 用来处理点击事件的,只要事件被拦截 或者本身是view,那么就会触发这个事件,如果返回true ,就是真正的消费了事件,一般dispatchTouchEvent的返回值 就是这个方法的返回值。如果用户对view设置了onTouchListener ,那么会调用onTouch方法 ,这个方法的优先级会比onTouchEvent的方法高。如果onTouch返回true,表示事件被消费了,onTouchEvent直接不会被调用,当返回false的时候 后者才会被正常的调用起来。而onTouchEvent方法中会有判断是否设置了onClickListener ,设置了的话 onClick会被调用。
简单的来说 优先级 onTouch> onTouchEvent>onClick
点击事件的传递路径 :Activity —>Window ——>View 关于Window 跟DecorView 回头再慢慢说。 如果下面的对象都没有消费这个事件的话,又会逐级的上传,这个过程中,如果还是没有人消费这个事件,最终会调用Activity的onTouchEvent方法。
文中总结的结论 挑重点复述:
- 一个系列事件是以down事件开始,中间穿插着不定量的move事件,最后以up方法结尾
- 正常情况下,一个事件序列只能被一个view拦截,正常的情况他是拦截后,会把后续的事件都传给这个view处理,当然也可以在onTouchEvent方法中直接将方法强制传给其它view处理(有个疑问:父类子类都可以吗?)。
- View拦截事件后,他的onInterceptTouchEvent不会被重复调用,后续的事件会直接传递到他身上。
- 如果一个view要处理事件,那么从down方法开始 就必须返回true,否者后续的事件会被交给父类去处理。如果down方法返回true,后续的事件返回false,那么这个方法会直接消失,并不会交给父类去做,并且后续的事件依然存在,依然会传递过来,最后这些消失的事件会统一交给activity处理。
- Viewgroup默认不拦截事件,即onInterceptTouchEvent方法默认返回false,所以有需求的时候需要自己重写这个方法。
- View没有onInterceptTouchEvent方法。
- View的onTouchEvent这个方法默认返回true,除非他是不可点击的(clickable or longClickable都为false),前者根据控件确认值,而后者默认都是false。
- enable这个属性不会影响onTouchEvent的值,因为这个方法只会去判断clickable跟longClickable 是否为false,这两者只要有一个为true,这个方法就会被正常的进行,返回true。
- onClick发生的前提 是这个view是可点击的 ,并且他收到了down跟up方法,这其实也说明这个方法是在up之后才会被触发。(当你设置了onClickListener接口后,会自动就将clickable设置为true,)
- 事件传递都是由外向内的,都是从父元素慢慢的分发传递到子元素去的,但是子view中可以调用requestDisallowTouchEvent方法可以干预父元素的事件传递,但是down事件除外。(因为down事件的时候会直接重置这个request状态,所以即使你设置了, 也是无效的)。
如上所说的,一个事件最初是从activity开始的,他会在dispatchTouchEvent方法中调用他的内部Window,Window会将事件传递给DecorView,这个就是setContentView中的view的父容器,这个存在可以通过 Activity.getWindow().getDecorView()方法获取
//Activity中
public boolean dispatchTouchEvent(MotionEvent ev){
if(ev.getAction()==MotionEvent.ACTION_DOWN ){
onUseInteraction();//???
}
if(getWindow().superDispatchTouchEvent(ev)){//同上,调用window的方法 逐级传递
//如果条件成立 所以里面的存在把事件给消费了 ,那么就直接返回true
return true ;
}
return onTouchEvent(ev);//子元素没有一个消费掉了这个方法,所以会调用activity的onTouchEvent
}
//PhoneWindow的superDispatchTouchEvent
public boolean superDispatchTouchEvent (MotionEvent ev){
return mDecor.superDispatchTouchEvent(ev);//直接调用DecorView的方法 根本没有做其它多余的操作 ,就是传递事件
}
关于window就不详说了,是个抽象类,他的实现类是PhoneWindow
我们之前在setContentView中设置的view ,除了自己用id来找以外,还可以通过这个mDecorView获取
((ViewGroup)getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0); 不过这么写真的麻烦 ,只是说明一下而已。
DecorView继承FrameLayout ,另外 事件传递的过程中,除非事件被拦截,不然onTouchListener不起作用。
所以说 想做事件传递分发的时候,基本都是重写 Viewgroup的dispathchTouchEvent方法
//viewgroup的 dispatchTouchEvent的方法片段
final boolean intercepted ;
//为down 或者后者不为null 后者是否为null取决于是否有子元素处理了事件,如果有处理,那么这个对象就会指向子元素
if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget !=null){
//判断子类有没使用requestDisallowTouchEvent方法把 猜猜应该是设置了之后返回的是false 那么父类就不拦截了
final boolean disallowIntercept = (mGroupFlags &FLAG_DISALLOW_INTERCEPT )!=0;
//能作用除down以外的其它动作,如果是down的话 ,viewgroup还是会调用intercept方法判断是否拦截的
if(!disallowIntecept){
intecepted = onInterceptTouchEvent(ev);
ev.setAction(action);//防止动作被改变 所以重置它
}else{
//不拦截 子类才能搞事情 是吗?
intercepted = false ;
}
}else{
//不是down事件 子view也没处理事件 那么就会直接跳到这里来 并没有经过onInterceptTouchEvent
intercepted = false ;
}
//如上代码表明 onInterceptTouchEvent不是每次都会被调用的
view里面的方法 ,看的出来 没有intercept方法,并且onTouch方法优先级很高
看view的onTouchEvent方法中 ,看出 可用不可用 都不影响view消费掉这个事件,如果它不可用,他会判断他的clickable 跟longclickable方法 哪个为true 只要为true 就能玩 还有其它的一些代码也证明了这一点。
//关于setClickListener 跟 setLongClcikListener 只要设置了这个监听 ,那两个状态就自动会被修改。
好吧 还需要修改 需要再去多看看其它的文章