#Android源码#Android Touch事件分发机制思考
2016-01-30 21:31
645 查看
——– 写在前面 ————————————————
用一天来思考Touch事件的分发,值了!参考了一些知名的博客,由于并没有引用其中的原文就不具体写引用了。Google一下就有了。以下是我的看法,有错漏的欢迎指正。转载请注明出处:Lshare版权所有:http://blog.csdn.net/Geek_Geek/article/details/50612209
一个完整触摸事件包括按下(1个)、移动(0-n个)和抬起(1个)三个事件组成。所以,当你在屏幕上点击一下,实际上是触发了两个事件,一个是按下,一个是抬起。也有一种事件是取消,比如当你按下了按钮,然后移动到别处(按钮区域外)抬起,就会识别为事件取消。
一个事件只能有一个责任人。
事件以按下为起点,谁消费了按下事件,后续的事件就交给谁处理。
View可以自己处理事件,也可以分发给子View处理。
这样就完了好像有点扫兴。那就再说点吧!
——-华丽的分割线————————————————-
先阐明一个简单的道理:ViewGroup是View的子类,继承有View的非私有方法。普通的View无法包裹子布局,而ViewGroup可以包裹子布局,用脑图简单表示下
再卖个关子,在继续往下读之前,不妨新建一个HelloWord项目,然后用
删繁就简,成这样了
简单描述下就是,Activity中持有一个叫PhoneWindow的窗口用来与外部交互,而PhoneWindow持有一个DecorView用来显示可交互的界面,而DecorView持有一个子布局LinearLayout,LinearLayout持有两个子布局,一个是ActionBarContainer(当请求无标题
根据上面的道理:只有ViewGroup可以包裹子布局。可以得出:DecorView、ActionBarContainer都是ViewGroup的子类,因为ViewGroup是View的子类,自然也有绘图和处理事件的职责。
注:关于PhoneWindow和DecorView的源代码可以到http://androidxref.com/这里去搜索。
那么问题来了,Touch事件在这种层级式的视图中是怎么被处理的??
1. 从子布局到父布局,即事件先传递给子布局,然后再传递给父布局
2. 从根布局到子布局,即事件先传递给根布局,然后再传递给子布局
按照人的习惯思维,似乎第一种方案更好,所谓“近水楼台先得月”嘛。那第二种我们不也可以理解为“岂有儿子比老子先拿之礼”,老子不要了才给儿子。还有如果用第一种方案的话,老子就不能拦截儿子的事件了,而儿子可以瞒着不告诉老子。
——-华丽的分割线————————————————-
事实上,Android在设计事件处理时是采用第二种的。
一个是
脑图分析如下
脑图分析如下:
鉴于源码太长,个人认知有限,先理解一部分,其他的待日后研究。其中的部分代码展示了ViewGroup对事件是否要拦截的判断,当intercepted为true时表示要拦截,dispatchTouchEvent会返回false,表示不向下分发了。
子类通常具有比父类更多的方法。ViewGroup具有对其子View的生杀大权,自然有拦截事件不向子View分发的方法,即
默认情况下
重写
上述两个方法的源码如下:
事件的处理是至上而下的,从Activity到ViewGroup再到非ViewGroup。
理清事件处理关键在
ViewGroup作为容器类View,对事件的处理多了
返回true,通常表示处理或消费了事件,不再传递。
用一天来思考Touch事件的分发,值了!参考了一些知名的博客,由于并没有引用其中的原文就不具体写引用了。Google一下就有了。以下是我的看法,有错漏的欢迎指正。转载请注明出处:Lshare版权所有:http://blog.csdn.net/Geek_Geek/article/details/50612209
一、概述
Android中的事件分发是遵循类似责任链模式的,就是从根节点开始逐层往里分发事件,直到找到责任人(即响应事件的View)或找不到责任人事件“丢弃”为止。二、什么是Touch事件?
通过屏幕交互的事件,比如单击、长按、滚动等等归根结底都是TouchEvent(触摸事件)。在触屏时代,人与屏幕交互不都是触摸事件吗?一个完整触摸事件包括按下(1个)、移动(0-n个)和抬起(1个)三个事件组成。所以,当你在屏幕上点击一下,实际上是触发了两个事件,一个是按下,一个是抬起。也有一种事件是取消,比如当你按下了按钮,然后移动到别处(按钮区域外)抬起,就会识别为事件取消。
三、对Touch事件的基本认知
一个事件只能被消费(consume)一次,事件消费了就不再传递。一个事件只能有一个责任人。
事件以按下为起点,谁消费了按下事件,后续的事件就交给谁处理。
View可以自己处理事件,也可以分发给子View处理。
四、谁来处理Touch事件?
这个问题简单,就是View。看官方对View类的功能定义就知道A View occupies a rectangular area on the screen and is responsible for drawing and event handling. //翻译: //View在屏幕上占据一块矩形区域, //负责绘图和事件处理。
这样就完了好像有点扫兴。那就再说点吧!
——-华丽的分割线————————————————-
先阐明一个简单的道理:ViewGroup是View的子类,继承有View的非私有方法。普通的View无法包裹子布局,而ViewGroup可以包裹子布局,用脑图简单表示下
再卖个关子,在继续往下读之前,不妨新建一个HelloWord项目,然后用
\sdk\tools\hierarchyviewer.bat查看MainActivity的视图层级。是这样的
删繁就简,成这样了
简单描述下就是,Activity中持有一个叫PhoneWindow的窗口用来与外部交互,而PhoneWindow持有一个DecorView用来显示可交互的界面,而DecorView持有一个子布局LinearLayout,LinearLayout持有两个子布局,一个是ActionBarContainer(当请求无标题
requestWindowFeature(Window.FEATURE_NO_TITLE)时为ViewStub),另一个是FrameLayout。FrameLayout的子布局就是我们使用
setContentView(int resId)挂载的布局啦!!!
根据上面的道理:只有ViewGroup可以包裹子布局。可以得出:DecorView、ActionBarContainer都是ViewGroup的子类,因为ViewGroup是View的子类,自然也有绘图和处理事件的职责。
注:关于PhoneWindow和DecorView的源代码可以到http://androidxref.com/这里去搜索。
那么问题来了,Touch事件在这种层级式的视图中是怎么被处理的??
五、怎么处理Touch事件?
有两种方案:1. 从子布局到父布局,即事件先传递给子布局,然后再传递给父布局
2. 从根布局到子布局,即事件先传递给根布局,然后再传递给子布局
按照人的习惯思维,似乎第一种方案更好,所谓“近水楼台先得月”嘛。那第二种我们不也可以理解为“岂有儿子比老子先拿之礼”,老子不要了才给儿子。还有如果用第一种方案的话,老子就不能拦截儿子的事件了,而儿子可以瞒着不告诉老子。
——-华丽的分割线————————————————-
事实上,Android在设计事件处理时是采用第二种的。
六、View中对事件的处理
在View中定义了跟事件处理相关的两个重要函数一个是
dispatchTouchEvent方法,另一个是
onTouchEvent方法,源代码如下:
/** * Pass the touch screen motion event down to the target view, or this * view if it is the target. * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. */ public boolean dispatchTouchEvent(MotionEvent event) { //省略几行代码 if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { return true; } if (onTouchEvent(event)) { return true; } } //省略几行代码 return false; }
脑图分析如下
/** * Implement this method to handle touch screen motion events. * <p> * If this method is used to detect click actions, it is recommended that * the actions be performed by implementing and calling * {@link #performClick()}. This will ensure consistent system behavior, * including: * <ul> * <li>obeying click sound preferences * <li>dispatching OnClickListener calls * <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when * accessibility features are enabled * </ul> * * @param event The motion event. * @return True if the event was handled, false otherwise. */ public boolean onTouchEvent(MotionEvent event) { final int viewFlags = mViewFlags; if ((viewFlags & ENABLED_MASK) == DISABLED) { if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); } if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { //省略一段switch语句 return true; } return false; }
脑图分析如下:
七、ViewGroup中对事件的处理
ViewGroup是View的子类,所以自然继承了View的上述两个方法。ViewGroup还重写了dispatchTouchEvent方法,为什么?因为啊,ViewGroup包含了多个View,事件分发时总要先判断事件落在哪个View中吧,不像非ViewGroup那样只要判断:有么有没有注册监听器啊?注册的是不是触摸监听器啊?触摸监听的回调返回值是true吗?
鉴于源码太长,个人认知有限,先理解一部分,其他的待日后研究。其中的部分代码展示了ViewGroup对事件是否要拦截的判断,当intercepted为true时表示要拦截,dispatchTouchEvent会返回false,表示不向下分发了。
// Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was // changed } else { intercepted = false; } } else { // There are no touch targets and this action is not an initial // down // so this view group continues to intercept touches. intercepted = true; }
子类通常具有比父类更多的方法。ViewGroup具有对其子View的生杀大权,自然有拦截事件不向子View分发的方法,即
onInterceptTouchEvent,源码如下,意思就是默认不拦截。
/** * Implement this method to intercept all touch screen motion events. This * allows you to watch events as they are dispatched to your children, and * take ownership of the current gesture at any point. * ......共37行注释 **/ public boolean onInterceptTouchEvent(MotionEvent ev) { return false; }
八、Activity对Touch事件的处理
我们在谁来处理Touch事件?那里分析得知:Activity持有一个Window,而Window持有一个DecorView。而事件是至上而下分发的,所以Activity对事件拥有最高的优先处理权,它可以决定是否要将时间分发给Window。默认情况下
dispatchTouchEvent返回值是true,分发的;当没有任何的View来处理触摸事件时,会系统调用
onTouchEvent方法。
重写
dispatchTouchEvent可以让它返回false,从而截断了所有的触摸事件,此时不会调用
onTouchEvent方法。
上述两个方法的源码如下:
/** * Called to process touch screen events. You can override this to * intercept all touch screen events before they are dispatched to the * window. Be sure to call this implementation for touch screen events * that should be handled normally. * * @param ev The touch screen event. * * @return boolean Return true if this event was consumed. */ public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
/** * Called when a touch screen event was not handled by any of the views * under it. This is most useful to process touch events that happen * outside of your window bounds, where there is no view to receive it. * * @param event The touch screen event being processed. * * @return Return true if you have consumed the event, false if you haven't. * The default implementation always returns false. */ public boolean onTouchEvent(MotionEvent event) { if (mWindow.shouldCloseOnTouch(this, event)) { finish(); return true; } return false; }
九、总结
写了这么多,似乎该有点总结。事件的处理是至上而下的,从Activity到ViewGroup再到非ViewGroup。
理清事件处理关键在
onDispatchTouchEvent方法。在这个方法中会做一个抉择:是要直接分发给子View处理,或是先交给自己
onTouchEvent方法处理后再抉择。
ViewGroup作为容器类View,对事件的处理多了
onInterceptTouchEvent这个阻断方法,其实我们只要看
onDispatchTouchEvent就行了,因为它会在这个方法中调用
onInterceptTouchEvent做是否阻断的判定。
返回true,通常表示处理或消费了事件,不再传递。
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories