【Android学习笔记】Android中View的事件分发机制
2016-05-21 16:44
375 查看
这边博文不是原创,应该算作读书笔记,综合了下面几篇博文,做了一些删减合并,得到适合我自己看的笔记。
Android:30分钟弄明白Touch事件分发机制
Android 编程下 Touch 事件的分发和消费机制
View事件分发及消费源码分析
从 onTouchEvent() 的返回值来看 View 的事件传递
下面这张图阐释了ViewGroup和View的关系
ViewGroup的相关事件有三个:onInterceptTouchEvent(事件拦截
)、dispatchTouchEvent(事件分发)、onTouchEvent(事件响应)。
View的相关事件只有两个:dispatchTouchEvent(事件分发)、onTouchEvent(事件响应)。
一个表层的结论:onTouchEvent 返回 true 说明该 View 消耗了触摸事件,后续的触摸事件也由它来进行处理。返回 false 说明该 View 对触摸事件不感兴趣,事件继续传递下去。
在此可以看出,ViewGroup的dispatchTouchEvent是真正在执行“分发”工作,而View的dispatchTouchEvent方法,并不执行分发工作,或者说它分发的对象就是自己,决定是否把touch事件交给自己处理,而处理的方法,便是onTouchEvent,onTouchEvent会根据View的处理方式返回boolean类型的变量。
一般情况下,我们不该在普通View内重写dispatchTouchEvent方法,因为它并不执行分发逻辑。当Touch事件到达View时,我们该做的就是是否在onTouchEvent事件中处理它。
当ViewGroup所有的子View都返回false时,ViewGroup的onTouchEvent方法便会执行。由于ViewGroup是继承于View的,它其实也是通过调用View的dispatchTouchEvent方法来执行onTouchEvent事件。
当所有子View的onTouchEvent都返回false时,这次的Touch请求就由根ViewGroup,即Activity自己处理了。这里的Touch事件,只限于Acition_Down事件,而Aciton_UP和Action_MOVE却不会执行。事实上,一次完整的Touch事件,应该是由一个Down、一个Up和若干个Move组成的。Down方式通过dispatchTouchEvent分发,分发的目的是为了找到真正需要处理完整Touch请求的View。当某个View或者ViewGroup的onTouchEvent事件返回true时,便表示它是真正要处理这次请求的View,之后的Aciton_UP和Action_MOVE将由它处理;Down返回false的View不会触发Up和Move,只有Down返回true时才会触发Up和Move事件。
(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处理。
情况一用到的比较多,情况二个人还未找到使用场景。
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事件。
Android:30分钟弄明白Touch事件分发机制
Android 编程下 Touch 事件的分发和消费机制
View事件分发及消费源码分析
从 onTouchEvent() 的返回值来看 View 的事件传递
一、以Touch事件为例的分发机制
Touch事件分发中只有两个主角:ViewGroup和View。Activity的Touch事件事实上是调用它内部的ViewGroup的Touch事件,可以直接当成ViewGroup处理。下面这张图阐释了ViewGroup和View的关系
ViewGroup的相关事件有三个:onInterceptTouchEvent(事件拦截
)、dispatchTouchEvent(事件分发)、onTouchEvent(事件响应)。
View的相关事件只有两个:dispatchTouchEvent(事件分发)、onTouchEvent(事件响应)。
1、dispatchTouchEvent
首先,最先接收到触摸事件的是 Activity,然后是Activity的 ViewGroup,然后会依次下发,下发的过程调用子View(ViewGroup)的dispatchTouchEvent方法实现。简单来说,就是ViewGroup遍历它包含着的子View,调用每个View的dispatchTouchEvent方法,而当子View为ViewGroup时,又会通过调用ViwGroup的dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法。上述图片例子中的消息下发顺序是这样的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只负责事件的分发,它拥有boolean类型的返回值,当返回为true时,顺序下发会中断。在上述例子中如果⑤的dispatchTouchEvent返回结果为true,那么⑥-⑦-③-④将都接收不到本次Touch事件。dispatchTouchEvent的返回值来自onTouchEvent。来个简单版的代码加深理解:/** * ViewGroup */ 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 */ public boolean dispatchTouchEvent(MotionEvent ev) { return onTouchEvent(event); }
一个表层的结论:onTouchEvent 返回 true 说明该 View 消耗了触摸事件,后续的触摸事件也由它来进行处理。返回 false 说明该 View 对触摸事件不感兴趣,事件继续传递下去。
在此可以看出,ViewGroup的dispatchTouchEvent是真正在执行“分发”工作,而View的dispatchTouchEvent方法,并不执行分发工作,或者说它分发的对象就是自己,决定是否把touch事件交给自己处理,而处理的方法,便是onTouchEvent,onTouchEvent会根据View的处理方式返回boolean类型的变量。
一般情况下,我们不该在普通View内重写dispatchTouchEvent方法,因为它并不执行分发逻辑。当Touch事件到达View时,我们该做的就是是否在onTouchEvent事件中处理它。
当ViewGroup所有的子View都返回false时,ViewGroup的onTouchEvent方法便会执行。由于ViewGroup是继承于View的,它其实也是通过调用View的dispatchTouchEvent方法来执行onTouchEvent事件。
当所有子View的onTouchEvent都返回false时,这次的Touch请求就由根ViewGroup,即Activity自己处理了。这里的Touch事件,只限于Acition_Down事件,而Aciton_UP和Action_MOVE却不会执行。事实上,一次完整的Touch事件,应该是由一个Down、一个Up和若干个Move组成的。Down方式通过dispatchTouchEvent分发,分发的目的是为了找到真正需要处理完整Touch请求的View。当某个View或者ViewGroup的onTouchEvent事件返回true时,便表示它是真正要处理这次请求的View,之后的Aciton_UP和Action_MOVE将由它处理;Down返回false的View不会触发Up和Move,只有Down返回true时才会触发Up和Move事件。
2、onInterceptTouchEvent
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、ViewGroup.dispatchTouchEvent(event)
private boolean dispatchTouchEvent(MotionEvent event) { int action = event.getAction(); //判断ViewGroup是否拦截touch事件。当为ACTION_DOWN或者找到能够接收touch事件的子View时,由onInterceptTouchEvent(event)决定是否拦截。其他情况,即ACTION_MOVE/ACTION_UP且没找到能够接收touch事件的子View时,直接拦截。 boolean intercepted; if (action == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { intercepted = onInterceptTouchEvent(event); } else { intercepted = true; } //如果ViewGroup不拦截touch事件。在ACTION_DOWN时遍历所有子View,查找能够接收touch事件的子View。如果找到则设置mFirstTouchTarget,并跳出循环。 boolean alreadyDispatchedToNewTouchTarget = false; if (!intercepted) { if (action == MotionEvent.ACTION_DOWN) { for (int i = childrenCount - 1; i >= 0; i--) { if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; } if (dispatchTransformedTouchEvent(event, child)) { //找到mFirstTouchTarget newTouchTarget = addTouchTarget(child); alreadyDispatchedToNewTouchTarget = true; break; } } } } //事件下发及消费。如果没找到能够接收touch事件的子View,则由ViewGroup自己处理及消费。如果找到能够接收touch事件的子View,则由子View递归处理touch事件及消费。 boolean handled = false; if (mFirstTouchTarget == null) { handled = dispatchTransformedTouchEvent(event, null); } else { if (alreadyDispatchedToNewTouchTarget) { handled = true; } else { while (touchTarget) { handled = dispatchTransformedTouchEvent(event, child); } } } return handled; } //ViewGroup事件下发。如果无接收touch事件的子View,则由ViewGroup的父类(即View)下发touch事件。如果child非空,则交由子View下发touch事件,子View可以是ViewGroup或View。 boolean dispatchTransformedTouchEvent(MotionEvent event, View child) { boolean handled; if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } return handled; }
2、View.dispatchTouchEvent(event)
//View的Touch事件分发。当外部设置了mOnTouchListener时,先交由mOnTouchListener.onTouch(event)消费。若未消费,则交给View的onTouchEvent(event)消费。onTouchEvent的实现是,如果设置了mOnClickListener,则执行mOnClickListener.onClick()点击事件。返回值为true,表示消费,否则未消费。 boolean dispatchTouchEvent(MotionEvent event) { boolean result = false; if (mOnTouchListener != null && mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } return result; } boolean onTouchEvent(MotionEvent event) { performClick(); }
三、总结
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事件。
相关文章推荐
- android基础之依赖注入
- android进程间通信--Binder
- Android深入浅出自定义控件(二)
- Retrofit – Java(Android) 的REST 接口封装类库
- android中各种只读的小文件放置位置
- 【转】android出现注: 某些输入文件使用或覆盖了已过时的 API。 注: 有关详细信息, 请使用 -Xlint:deprecation 重新编译。 注: 某些输入文件使用了未经检查或不安全的操作。 注
- Android 窗口机制
- AndroidStudio插件--->推荐与使用
- Android之Window与WindowManager
- android 软键盘弹出隐藏挤压界面等问题
- AndroidStudio中ADBWIFI插件
- Android ViewPager使用详解
- 使用Android studio创建你的第一个项目
- android studio for android learning (四) 兼容性问题
- android高级编程之加载大图片到内存
- Android 利用xml实现各种样式等
- Android开发之Intent.Action
- Android动画的使用点滴
- AndroidStudio配置SVN以及使用代码管理
- Android自定义View之绘制闪动的文字