30分钟搞清楚Android Touch事件分发机制
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 * @param ev * @return */ public boolean dispatchtouchevent(motionevent ev){ ....//其他处理,在此不管 view[] views=getchildview(); for(int i=0;i<views.length;i++){ //判断下touch到屏幕上的点在该子view上面 if(...){ if(views[i].dispatchtouchevent(ev)) return true; } } ...//其他处理,在此不管 } /** * view * @param ev * @return */ public boolean dispatchtouchevent(motionevent ev){ ....//其他处理,在此不管 return false; }
在此可以看出,viewgroup的dispatchtouchevent是真正在执行“分发”工作,而view的dispatchtouchevent方法,并不执行分发工作,或者说它分发的对象就是自己,决定是否把touch事件交给自己处理,而处理的方法,便是ontouchevent事件,事实上子view的dispatchtouchevent方法真正执行的代码是这样的
/** * view * @param ev * @return */ 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自己处理了。
看看改进后的viewgroup的dispatchtouchevent方法
view mtarget=null;//保存捕获touch事件处理的view public boolean dispatchtouchevent(motionevent ev) { //....其他处理,在此不管 if(ev.getaction()==keyevent.action_down){ //每次down事件,都置为null if(!onintercepttouchevent()){ mtarget=null; view[] views=getchildview(); for(int i=0;i<views.length;i++){ if(views[i].dispatchtouchevent(ev)) mtarget=views[i]; return true; } } } //当子view没有捕获down事件时,viewgroup自身处理。这里处理的touch事件包含down、up和move if(mtarget==null){ return super.dispatchtouchevent(ev); } //...其他处理,在此不管 if(onintercepttouchevent()){ //...其他处理,在此不管 } //这一步在action_down中是不会执行到的,只有move和up才会执行到。 return mtarget.dispatchtouchevent(ev); }
viewgroup还有个onintercepttouchevent,看名字便知道这是个拦截事件。这个拦截事件需要分两种情况来说明:
1.假如我们在某个viewgroup的onintercepttouchevent中,将action为down的touch事件返回true,那便表示将该viewgroup的所有下发操作拦截掉,这种情况下,mtarget会一直为null,因为mtarget是在down事件中赋值的。由于mtarge为null,该viewgroup的ontouchevent事件被执行。这种情况下可以把这个viewgroup直接当成view来对待。
2.假如我们在某个viewgroup的onintercepttouchevent中,将acion为down的touch事件都返回false,其他的都返回true,这种情况下,down事件能正常分发,若子view都返回false,那mtarget还是为空,无影响。若某个子view返回了true,mtarget被赋值了,在action_move和aciton_up分发到该viewgroup时,便会给mtarget分发一个action_delete的motionevent,同时清空mtarget的值,使得接下去的action_move(如果上一个操作不是up)将由viewgroup的ontouchevent处理。
情况一用到的比较多,情况二个人还未找到使用场景。
从头到尾总结一下:
1.touch事件分发中只有两个主角:viewgroup和view。viewgroup包含onintercepttouchevent、dispatchtouchevent、ontouchevent三个相关事件。view包含dispatchtouchevent、ontouchevent两个相关事件。其中viewgroup又继承于view。
2.viewgroup和view组成了一个树状结构,根节点为activity内部包含的一个viwgroup。
3.触摸事件由action_down、action_move、aciton_up组成,其中一次完整的触摸事件中,down和up都只有一个,move有若干个,可以为0个。
4.当acitivty接收到touch事件时,将遍历子view进行down事件的分发。viewgroup的遍历可以看成是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的view,这个view会在ontouchuevent结果返回true。
5.当某个子view返回true时,会中止down事件的分发,同时在viewgroup中记录该子view。接下去的move和up事件将由该子view直接进行处理。由于子view是保存在viewgroup中的,多层viewgroup的节点结构时,上级viewgroup保存的会是真实处理事件的view所在的viewgroup对象:如viewgroup0-viewgroup1-textview的结构中,textview返回了true,它将被保存在viewgroup1中,而viewgroup1也会返回true,被保存在viewgroup0中。当move和up事件来时,会先从viewgroup0传递至viewgroup1,再由viewgroup1传递至textview。
6.当viewgroup中所有子view都不捕获down事件时,将触发viewgroup自身的ontouch事件。触发的方式是调用super.dispatchtouchevent函数,即父类view的dispatchtouchevent方法。在所有子view都不处理的情况下,触发acitivity的ontouchevent方法。
7.onintercepttouchevent有两个作用:1.拦截down事件的分发。2.中止up和move事件向目标view传递,使得目标view所在的viewgroup捕获up和move事件。
补充:
“触摸事件由action_down、action_move、aciton_up组成,其中一次完整的触摸事件中,down和up都只有一个,move有若干个,可以为0个。”,这里补充下其实up事件是可能为0个的。
以上就是本文的全部内容,希望对大家理解touch事件分发机制有所帮助。
下一篇: c#文件下载示例的4种方法分享