Android源码分析(二):View的事件分发机制探析
2016-08-27 15:35
706 查看
Android应用开发时,自定义控件时少不了和View的触摸点击事件打交道。针对View的事件分发原理,也看过网上的一些博客,但是看归看,看了之后时间一长就又忘记了,因此为了更好地记忆理解,痛下决心自己写一篇关于View事件分发原理的博客。
1 示例代码看起
1.1 重写Button,代码如下
代码里面只是重写了2个方法,然后在里面打印了一些log,用于看下触摸事件的行走流程。
1.2 布置xml布局
很简单,在LinearLayout里面放置一个我们刚才自定义的MyButton就可以了。
1.3 在Activity的代码设定
1.4 测试示例效果,当精准的点击MyButton的时候,打印结果如下
从图中看得到,当我们点击MyButton的时候,执行顺序是
dispatchTouchEvent(…)—>onTouch(…)—>onTouchEvent(…)—>onClick(…),那么我们就按这个顺序改变一下返回值再看下打印结果如何。
首先进入的是boolleab dispatchTouchEvent(…)函数,它返回的是boolean类型的值,那我们改下返回值,看看结果如何,改动如下:
就是把返回值改为false,再看下打印皆若如何呢?
只打印了一行数据,就是ACTION_DOWN的时候,具体什么原因,待会进入分析流程的时候具体的研究一下。
那要是把返回值改为true,结果如何呢?
dispatchTouchEvent(..)函数方法中,action事件全部走齐了,但是其余的函数并没有进入,所以我们猜测其余的事件函数有可能是在dispatchTouchEvent(..)函数做的调用。
好了,到这里,我们再看一个信息,就是boolean onTouch(…)函数,这个是在myButton设置OnTouchListener事件监听的时候的回调方法,一开始的时候,我们返回的是false,那我们改为true试试结果如何呢?
结果是onTouchEvent(…)与onClick(…)函数并没有执行,这个时候我们猜测,当onTouch(…)返回true时,阻止了事件的传递,所以上面的2个函数就收不到事件。
接下来再看boolean onTouchEvent(…)方法,也是一样的,具有boolean类型的返回值,同样的,改变一下返回值,看下具体的结果如何
当返回值为false时,打印结果如下:
打印结果也令人匪夷所思,只是打印了ACTION_DOWN的时候,以后的所有action事件并没有执行,为什么呢?先在这打个大大的问号。
当返回值为true时,打印结果如下:
越来越接近刚开始的打印结果了,这次除了onClick()事件里面的log没有输出,其余的都有log信息了。
总结一下如下:
(1)当 dispatchTouchEvent(…)函数不管返回具体的true or false,事件都不会分发到下一个函数中,并且为true的时候,才会监听到下一个action事件。
(2)当设置了OnTouchListener监听之后,在OnTouch(…)函数体中,返回true,则阻止事件的往下传递,后面的事件函数不在执行
(3)当onTouchEvent(…)函数不管直接true or false,事件都不会执行到onClick(…)里面,并且同dispatchTouchEvent(…)一样,只有返回true的时候,才能在监听下一个action事件。
2 View触摸事件传递源码分析
我们就先按顺序来看,当我们点击myButton的时候,首先走的是dispatchTouchEvent(…)函数,那么我们就从这个函数入手,一步步往下看。
一番继承关系的查找之后,我们终于在View里面发现了这个函数的代码,进入函数内部来看下它到底干了啥事。
有些关键点已经有所注释,下面看一下这个最是我们需要的部分
一个if语句的判断,先是判断 li 是否为null,当然不会为null,然后就是li.mOnTouchListener是否为null,那它是怎么赋值的呢,搜索这个关键词看看呢,你发现这么一段代码
对了,就是在我们设置触摸监听的时候赋值的,当你调用setOnTouchListener(…)的时候就已经赋值了,下面就是对当前View是否ENABLE的位运算,默认是true,然后就是重点了,判断li.mOnTouchListener.onTouch(this, event)的返回值,当返回true时,进入if语句内部,result=true,那么接下来的if (!result && onTouchEvent(event)){…}就不会再执行了,否则就会执行if语句了,并且进入到onTouchEvent(…)函数内部,我们再进去看一下,它到底做了些什么工作。
里面做的工作有些复杂,但是具体的就是对各种action事件的处理,其中在ACTION_UP中,你会发现这么一句话
看名字也应该想到是什么了,是否要处理click事件,进performClick()看看呢,
看到没,在if语句判断之内,我们看到了onClick(…)方法的回调。很惊讶有没有,onClick(…)是在onTouchEvent(…)里面调用的。
3 分析结论
(1)当View被触摸的时候,首先走的是dispatchTouchEvent方法
(2)在dispatchTouchEvent方法中先执行onTouch方法,然后执行onTouchEvent方法,最后再onTouchEvent里面执行onClick方法
(3)当view控件为disable的或者mOnTouchListener=null或者onTouch方法返回false,才会调用onTouchEvent方法,并且dispatchTouchEvent返回值与onTouchEvent相同
(4)当dispatchTouchEvent进行事件分发的时候,只有上个事件返回true,才能接受下一个action事件
遗留问题待解决:
当dispatchTouchEvent返回false时候,下次事件为什么不能触发,在进行下一篇对ViewGroup事件分发的时候看看是否能找得到答案呢?
1 示例代码看起
1.1 重写Button,代码如下
public class MyButton extends Button{ private static final String TAG = "MyButton"; public MyButton(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent event) { Log.d(TAG, "dispatchTouchEvent: event.action = "+event.getAction()); return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { Log.d(TAG, "onTouchEvent: event.action = "+event.getAction()); return super.onTouchEvent(event); } }
代码里面只是重写了2个方法,然后在里面打印了一些log,用于看下触摸事件的行走流程。
1.2 布置xml布局
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center"> <com.lingyun.lingyunworkspaces.MyButton android:id="@+id/touchTest" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Click Me To Test"/> </LinearLayout>
很简单,在LinearLayout里面放置一个我们刚才自定义的MyButton就可以了。
1.3 在Activity的代码设定
MyButton myButton = (MyButton) findViewById(R.id.touchTest); myButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.d(TAG, "onClick: myButton clicked!"); } }); myButton.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { Log.d(TAG, "onTouch: myButton touched! event.action = "+event.getAction()); return false; } });
1.4 测试示例效果,当精准的点击MyButton的时候,打印结果如下
从图中看得到,当我们点击MyButton的时候,执行顺序是
dispatchTouchEvent(…)—>onTouch(…)—>onTouchEvent(…)—>onClick(…),那么我们就按这个顺序改变一下返回值再看下打印结果如何。
首先进入的是boolleab dispatchTouchEvent(…)函数,它返回的是boolean类型的值,那我们改下返回值,看看结果如何,改动如下:
@Override public boolean dispatchTouchEvent(MotionEvent event) { Log.d(TAG, "dispatchTouchEvent: event.action = "+event.getAction()); return false; }
就是把返回值改为false,再看下打印皆若如何呢?
08-27 14:05:38.323 303-303/? D/MyButton: dispatchTouchEvent: event.action = 0
只打印了一行数据,就是ACTION_DOWN的时候,具体什么原因,待会进入分析流程的时候具体的研究一下。
那要是把返回值改为true,结果如何呢?
08-27 14:14:41.646 3558-3558/? D/MyButton: dispatchTouchEvent: event.action = 0 08-27 14:14:41.687 3558-3558/? D/MyButton: dispatchTouchEvent: event.action = 1
dispatchTouchEvent(..)函数方法中,action事件全部走齐了,但是其余的函数并没有进入,所以我们猜测其余的事件函数有可能是在dispatchTouchEvent(..)函数做的调用。
好了,到这里,我们再看一个信息,就是boolean onTouch(…)函数,这个是在myButton设置OnTouchListener事件监听的时候的回调方法,一开始的时候,我们返回的是false,那我们改为true试试结果如何呢?
08-27 14:19:57.185 4720-4720/? D/MyButton: dispatchTouchEvent: event.action = 0 08-27 14:19:57.186 4720-4720/? D/MyButton: onTouch: myButton touched! event.action = 0 08-27 14:19:57.269 4720-4720/? D/MyButton: dispatchTouchEvent: event.action = 1 08-27 14:19:57.269 4720-4720/? D/MyButton: onTouch: myButton touched! event.action = 1
结果是onTouchEvent(…)与onClick(…)函数并没有执行,这个时候我们猜测,当onTouch(…)返回true时,阻止了事件的传递,所以上面的2个函数就收不到事件。
接下来再看boolean onTouchEvent(…)方法,也是一样的,具有boolean类型的返回值,同样的,改变一下返回值,看下具体的结果如何
当返回值为false时,打印结果如下:
08-27 14:09:03.576 1570-1570/? D/MyButton: dispatchTouchEvent: event.action = 0 08-27 14:09:03.576 1570-1570/? D/MyButton: onTouch: myButton touched! event.action = 0 08-27 14:09:03.576 1570-1570/? D/MyButton: onTouchEvent: event.action = 0
打印结果也令人匪夷所思,只是打印了ACTION_DOWN的时候,以后的所有action事件并没有执行,为什么呢?先在这打个大大的问号。
当返回值为true时,打印结果如下:
08-27 14:11:44.671 2412-2412/? D/MyButton: dispatchTouchEvent: event.action = 0 08-27 14:11:44.671 2412-2412/? D/MyButton: onTouch: myButton touched! event.action = 0 08-27 14:11:44.671 2412-2412/? D/MyButton: onTouchEvent: event.action = 0 08-27 14:11:44.729 2412-2412/? D/MyButton: dispatchTouchEvent: event.action = 1 08-27 14:11:44.729 2412-2412/? D/MyButton: onTouch: myButton touched! event.action = 1 08-27 14:11:44.729 2412-2412/? D/MyButton: onTouchEvent: event.action = 1
越来越接近刚开始的打印结果了,这次除了onClick()事件里面的log没有输出,其余的都有log信息了。
总结一下如下:
(1)当 dispatchTouchEvent(…)函数不管返回具体的true or false,事件都不会分发到下一个函数中,并且为true的时候,才会监听到下一个action事件。
(2)当设置了OnTouchListener监听之后,在OnTouch(…)函数体中,返回true,则阻止事件的往下传递,后面的事件函数不在执行
(3)当onTouchEvent(…)函数不管直接true or false,事件都不会执行到onClick(…)里面,并且同dispatchTouchEvent(…)一样,只有返回true的时候,才能在监听下一个action事件。
2 View触摸事件传递源码分析
我们就先按顺序来看,当我们点击myButton的时候,首先走的是dispatchTouchEvent(…)函数,那么我们就从这个函数入手,一步步往下看。
一番继承关系的查找之后,我们终于在View里面发现了这个函数的代码,进入函数内部来看下它到底干了啥事。
public boolean dispatchTouchEvent(MotionEvent event) { // If the event should be handled by accessibility focus first. //首先判断当前View是否可以接收此次action事件 if (event.isTargetAccessibilityFocus()) { // We don't have focus or no virtual descendant has it, do not handle the event. //当前View没有接收此次action事件的条件,返回false if (!isAccessibilityFocusedViewOrHost()) { return false; } // We have focus and got the event, then use normal event dispatch. event.setTargetAccessibilityFocus(false); } boolean result = false; if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); } final int actionMasked = event.getActionMasked(); if (actionMasked == MotionEvent.ACTION_DOWN) { // Defensive cleanup for new gesture stopNestedScroll(); } //判断是否被覆盖了,没有则返回true if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } } if (!result && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } // Clean up after nested scrolls if this is the end of a gesture; // also cancel it if we tried an ACTION_DOWN but we didn't want the rest // of the gesture. if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && !result)) { stopNestedScroll(); } return result; }
有些关键点已经有所注释,下面看一下这个最是我们需要的部分
ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; }
一个if语句的判断,先是判断 li 是否为null,当然不会为null,然后就是li.mOnTouchListener是否为null,那它是怎么赋值的呢,搜索这个关键词看看呢,你发现这么一段代码
public void setOnTouchListener(OnTouchListener l) { getListenerInfo().mOnTouchListener = l; }
对了,就是在我们设置触摸监听的时候赋值的,当你调用setOnTouchListener(…)的时候就已经赋值了,下面就是对当前View是否ENABLE的位运算,默认是true,然后就是重点了,判断li.mOnTouchListener.onTouch(this, event)的返回值,当返回true时,进入if语句内部,result=true,那么接下来的if (!result && onTouchEvent(event)){…}就不会再执行了,否则就会执行if语句了,并且进入到onTouchEvent(…)函数内部,我们再进去看一下,它到底做了些什么工作。
里面做的工作有些复杂,但是具体的就是对各种action事件的处理,其中在ACTION_UP中,你会发现这么一句话
if (!focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick(); } }
看名字也应该想到是什么了,是否要处理click事件,进performClick()看看呢,
public boolean performClick() { final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); return result; }
看到没,在if语句判断之内,我们看到了onClick(…)方法的回调。很惊讶有没有,onClick(…)是在onTouchEvent(…)里面调用的。
3 分析结论
(1)当View被触摸的时候,首先走的是dispatchTouchEvent方法
(2)在dispatchTouchEvent方法中先执行onTouch方法,然后执行onTouchEvent方法,最后再onTouchEvent里面执行onClick方法
(3)当view控件为disable的或者mOnTouchListener=null或者onTouch方法返回false,才会调用onTouchEvent方法,并且dispatchTouchEvent返回值与onTouchEvent相同
(4)当dispatchTouchEvent进行事件分发的时候,只有上个事件返回true,才能接受下一个action事件
遗留问题待解决:
当dispatchTouchEvent返回false时候,下次事件为什么不能触发,在进行下一篇对ViewGroup事件分发的时候看看是否能找得到答案呢?
相关文章推荐
- notification
- Android学习之ProgressBar
- Android的Binder机制阅读笔记
- 带你重新认识:Android Splash页秒开 Activity白屏 Activity黑屏
- android 编译出错 Unsupported major.minor version 52.0
- android ndk 入门
- Android View框架总结(九)KeyEvent事件分发机制
- Android View框架总结(九)KeyEvent事件分发机制
- Android View measure (五) 支持margin属性,从一个异常说起
- Windows下Nexus 5的Android 5.0以上版本官方ROM的刷机教程
- android studio安装问题
- Android Binder线程
- MVP模式的学习
- android深度理解ListView notifyDataSetChanged()不刷新
- AndroidStudio在断点时打印日志
- android增量更新
- Android 布局优化
- Android基础——属性动画赏析
- android Matrix.setRotate 和 postRotate的区别
- Android菜单(一)----关于菜单