view的事件分发过程由3个重要的方法完成
- public boolean dispatchTouchEvent
用于事件的分发。如果事件能传递到当前view,则该方法一定会调用。返回表示是否消耗当前事件 - public boolean onInterceptTouchEvent
用于判断是否拦截某个事件。返回表示是否拦截当前事件 - public boolean onTouchEvent
处理点击事件,是否消耗当前事件
三者的关系用伪代码表示 先去询问是否拦截事件,拦截调用自己的事件处理方法,否则调用子view的分发方法
public boolean dispatchTouchEvent(MotionEvent ev){
boolean resume = false;
if(onInterceptTouchEvent){
consume = onTouchEvent(ev);
}else{
consume = child.dispatchTouchEvent(ev);
}
return resume;
}
事件的流程
点击事件产生后,事件最先传递到Activity,由activity来派发事件(activity没有拦截事件的方法),具体的工作由内部的phoneWindow来完成,window又会传递给decorview,decorview是setcontview所设置的view的父容器。decorview然后传给viewgroup。viewgroup分发给view。然后view处理。如果view没有消耗掉事件,事件会反方向回传,最终传回给Activity。由此构成了一个经典的责任链模式的机制
005Xtdi2jw1f88i0q8uozj30nm0kqwhm.jpg图中如果view1的onTouchEvent返回true,则通过dispatchTouchEvent告诉上级事件被消耗了
事件的一些规则
- 某个view一旦拦截,那么它的拦截方法不会再被调用。同一序列的事件均由该view处理
- 某个view一旦开始处理,那么ACTION_DOWN事件必须消耗,否则同一事件序列里的其他事件将由父容器来处理了。
- view没有拦截方法,传递到view,那么onTouchEvent必会调用
- view的onTouchEvent默认会消耗事件,除非他是不可点击的clickable和longclickable均为false
- view的enable属性不影响onTouchEvent返回
- 通过requestDisallowInterceptTouchEvent可以在子元素中干预父元素的分发过程,但是ACTION_DOWN事件除外
- View的事件的调度顺序是 onTouchListener > onTouchEvent > onLongClickListener > onClickListener
- ViewGroup默认不拦截
requestDisallowInterceptTouchEvent
getParent().requestDisallInterceptRouchEvent(true) 来设置标志位FLAG_DISALLOW_INTERCEPT,让父布局禁用拦截事件功能。调用的要求是子View必须能拿到点击事件。比如父容器的拦截事件中把所有的Action都拦截了,那么子view拿不到事件。ViewGroup在遇到down事件时,会重置FLAG_DISALLOW_INTERCEPT标志位,也总会调用拦截方法来询问自己是否要拦截事件
看别人的案例。第一种情况是子View的down事件处理中调用了requestDisallowInterceptTouchEvent,设置了标志位,禁用了父容器的拦截方法,所以父容器拦截事件没有调用。第二种父容器拦截down事件,所以参看规则1,后续不再调用拦截方法。所以子view根本拿不到事件
滑动冲突
外部拦截法,通过重写父容器的拦截方法。父容器需要当前事件时拦截。外部拦截法,父容器都不拦截,子View配合requestDisallowInterceptTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev){
switch(ev.getAction){
case DOWN:
parent.requestDisallowInterceptTouchEvent(true);//禁用父容器的需要拦截方法
break;
case MOVE:
if(父容器需要事件){
parent.requestDisallowInterceptTouchEvent(false);
}
break;
}
return super.dispatchTouchEvent(ev);
}
网友评论