View的事件分发机制
点击事件在父子容器中的传递规则
step1,对于一个根ViewGroup来说,点击事件首先会传递给它,这是它的dispatchTouchEvent方法就会被调用;
step2,如果这个根ViewGroup的onInterceptTouchEvent方法返回true就表示它要拦截当前事件,接着事件就会交给这个根ViewGroup来处理(也就是根ViewGroup的onTouchEvent方法会被调用);
step3,如果这个根ViewGroup的onInterceptTouchEvent方法返回false就表示它不拦截当前事件,那么当前事件就会传递给它的子元素,接着子元素的dispatchTouchEvent方法就会被调用;
step4,如此反复知道事件被处理;
/**点击事件传递规则|伪代码表示*/
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume=false;
if(onInterceptTouchEvent(ev)){
consume=onTouchEvent(ev);
}
else{
consume=child.dispatchTouchEvent(ev);
}
}
OnTouchListener>onTouchEvent>OnClickListener
/**1,先调用OnTouchListener,后调用onTouchEvent高*/
'当一个view需要处理事件时,如果设置了OnTouchListener,那么OnTouchListener中的onTouch方法会被调用。(这时候,如果onTouch方法返回false,onTouch方法会被调用;如果onTouch方法返回true,onTouch方法不会被调用)'
/**2,先调用onTouchEvent,后调用OnClickListener*/
'在onTouchEvent方法中,如果一个当前设置的有OnClickListener,那么它的onClick方法会被调用。'
点击事件传递过程
/**activity-->window-->view*/
'当一个点击事件产生后,它的传递过程遵循如下顺序activity-->window-->view。'
事件总是先传递给activity,activity再传递给window,window传递再传递给顶级View。顶级View收到事件后,会按照事件分发机制去分发事件。
事件传递机制的11个结论
/**1,同一个系列事件从down事件开始,经历若干move事件,最终以up事件结束*/
'同一个系列事件是指,从手指接触屏幕那一刻起到手指离开屏幕那一刻结束,在这个过程所产生的的一系列事件,这个事件系列以down事件开始 ,中间有数量不定的move事件,最终以up事件结束。'
/**2,正常情况下一个事件系列只能被一个View拦截且抵消。*/
'一旦一个View拦截了此事件,那么同一个事件系列内的所有事件都会直接交给它处理。因此同一个系列事件中事件不能分别由两个View同时处理(但是通过特殊手段可以做到,比如一个View将本该自己处理的事件通过onTouchEvent强行传递给其他View处理)。'
/**3,某个View一旦决定拦截,那么这一事件系列只能由它来处理*/
'某个View一旦决定拦截,那么这一事件系列只能由它来处理(如果这一系列事件能够传递给它的话)。并且它的onIntercept方法不会再被调用。就是说当一个View拦截一个事件后,系统就会把同一个事件系列内的其他方法都交给它来处理,因此就不再调用它的onIntercept方法去询问它是否要拦截了'
/**4,不消耗ACTION_DOWN事件就失去其他事件处理机会*/
'某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一事件系列中的其他事件都不会再交给它处理,并且事件将重新交由它的父元素去处理(父元素的onTouchEnevt方法会被调用)。'
/**5,View不消耗‘除ACTION_DOWN事件以外的其他事件’那么点击事件会消失*/
'如果View不消耗‘除ACTION_DOWN事件以外的其他事件’,那么点击事件会消失。此时父元素的onTouchEvent方法不会被调用。并且当前View可以持续收到后续的事件,最终这些消失的点击事件会传递给activity处理'
/**6,ViewGroup默认不拦截任何事件*/
'因为ViewGroup的源码中onInterceptTouchEvent方法默认返回false'
/**7,View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用*/
/**8,View的onTouchEvent方法默认都会消耗事件*/
'View的onTouchEvent方法默认都会消耗事件(返回true)除非它是不可以点击的(clickable和longClickable同时为false)。View的longClickable属性默认都为false。clickable属性要分情况(Button的clickable属性默认为true,TextView的clickable属性默认为false,)。'
/**9,View的enable属性不影响onTouchEnent的默认返回值*/
'那怕一个View是disable状态的,只要它的clickable和longClickable同有一个为true,那么它的onTouchEnent就返回true。'
/**10,onClick会发生的前提是当前View是可点击的并且它收到了down和up事件。*/
/**11,事件传递过程从父到子*/
'事件总是先传递给父元素,然后再由父元素分发给子View,通过requestDisallowIntercepetTounchEnent方法可以在子元素中干预父元素的事件分发过程,但是ACTION_DOWN事件除外'
Touch事件分发机制图解
Android:30分钟弄明白Touch事件分发机制
http://www.cnblogs.com/linjzong/p/4191891.html
'Touch事件分发中只有两个主角:ViewGroup和View。'
Activity的Touch事件事实上是调用它内部的ViewGroup的Touch事件,可以直接当成ViewGroup处理。
View在ViewGroup内,ViewGroup也可以在其他ViewGroup内,这时候把内部的ViewGroup当成View来分析。
'ViewGroup的相关事件有三个:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。'
View的相关事件只有两个:dispatchTouchEvent、onTouchEvent。'
'先分析ViewGroup的处理流程:'
首先得有个结构模型概念:ViewGroup和View组成了一棵树形结构,最顶层为Activity的ViewGroup,下面有若干的ViewGroup节点,每个节点之下又有若干的ViewGroup节点或者View节点,依次类推。如图:
/**
当一个Touch事件(触摸事件为例)到达根节点,即Acitivty的ViewGroup时,它会依次下发,下发的过程是调用子View(ViewGroup)的dispatchTouchEvent方法实现的。简单来说,就是ViewGroup遍历它包含着的子View,调用每个View的dispatchTouchEvent方法,而当子View为ViewGroup时,又会通过调用ViwGroup的dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法。上述例子中的消息下发顺序是这样的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只负责事件的分发,它拥有boolean类型的返回值,当返回为true时,顺序下发会中断。在上述例子中如果⑤的dispatchTouchEvent返回结果为true,那么⑥-⑦-③-④将都接收不到本次Touch事件。
ViewGroup的dispatchTouchEvent是真正在执行“分发”工作,而View的dispatchTouchEvent方法,并不执行分发工作,或者说它分发的对象就是自己。
*/
public boolean dispatchTouchEvent(MotionEvent ev){
....//其他处理,在此不管
return onTouchEvent(event);
}
/**
一般情况下,我们不该在普通View内重写dispatchTouchEvent方法,因为它并不执行分发逻辑。当Touch事件到达View时,我们该做的就是是否在onTouchEvent事件中处理它。
那么,ViewGroup的onTouchEvent事件是什么时候处理的呢?当ViewGroup所有的子View都返回false时,onTouchEvent事件便会执行。由于ViewGroup是继承于View的,它其实也是通过调用View的dispatchTouchEvent方法来执行onTouchEvent事件。
在目前的情况看来,似乎只要我们把所有的onTouchEvent都返回false,就能保证所有的子控件都响应本次Touch事件了。但必须要说明的是,这里的Touch事件,只限于Acition_Down事件,即触摸按下事件,而Aciton_UP和Action_MOVE却不会执行。事实上,一次完整的Touch事件,应该是由一个Down、一个Up和若干个Move组成的。Down方式通过dispatchTouchEvent分发,分发的目的是为了找到真正需要处理完整Touch请求的View。当某个View或者ViewGroup的onTouchEvent事件返回true时,便表示它是真正要处理这次请求的View,之后的Aciton_UP和Action_MOVE将由它处理。当所有子View的onTouchEvent都返回false时,这次的Touch请求就由根ViewGroup,即Activity自己处理了。
*/
View的滑动冲突
滑动冲突的产生
在界面中只要内外两层都可以滑动,这个时候就会产生滑动冲突。
常见滑动冲突场景
滑动冲突处理规则
场景1,需求内部View左右滑动,外部View上下滑动
处理规则:当用户左右滑动时,需要让外部的View拦截点击事件。当用户上下滑动时,需要让内部的View拦截点击事件。根据滑动方向判断到底该由谁拦截事件。场景2,需要根据业务规则来判断谁来拦截事件。
场景3,可以综合1和2的规则来判断谁来拦截事件。
滑动冲突解决方式
方式1,外部拦截法
外部拦截法是指点击事件都先经过父容器的点击处理,如果父容器需要此事就拦截,如果不需要此事就不拦截。
/**外部拦截法需要重写父类的onInterceptTouchEvent方法*/
public boolean onInterceptTouchEvent(MotionEvent event){
boolean intercepted=false;
int x=(int)event.getX();
int y=(int)event.getY();
switch(event.getAction()){
/**
ACTION_DOWN事件父容器必须返回false
因为父容器一旦拦截了ACTION_DOWN,那么后续的
ACTION_MOVE和ACTION_UP都会直接交给父容器处理,
这时候事件就没法传给子元素了。
*/
case MotionEvent.ACTION_DOWN:
intercepted=false;
break;
/**ACTION_MOVE事件可以根据需要决定是否拦截,如果要父容器
要拦截就返回true,如果不拦截就返回false*/
case MotionEvent.ACTION_MOVE:
if("父容器需要当前点击事件"){
intercepted=true;
}else{
intercepted=false;
}
break;
/**ACTION_UP事件父容器必须返回false。
假设事件交由子元素处理,如果父容器ACTION_UP时
返回了true,就会导致子元素无法接收到ACTION_UP
事件,这个子元素的onClick事件就无法触发。但是
ACTION_UP作为最后一个事件必定可以传给
父容器,就算父容器的onInterceptTouchEvent
方法在ACTION_UP时返回了false*/
case MotionEvent.ACTION_UP:
intercepted=false;
break;
}
return intercepted;
}
方式2,内部拦截法
内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器处理。这种方法和android中的事件分发机制不一样,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,使用起来较外部拦截发复杂。