android 事件分发机制源码解析
来源:互联网 发布:物理补课软件 编辑:程序博客网 时间:2024/05/14 18:45
然后写了个布局,如图.
MainActivity有dispatchTouchEvent, onTouchEvent方法
MyRelativieLayout有dispatchTouchEvent, onInterceptTouchEvent, onTouchEvent方法
TextView和Button有dispatchTouchEvent, onTouchEvent方法.
这里拿MyRelativeLayout1的代码来举例,如下
@Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.e(TAG, "MyRelativeLayout1 dispatchTouchEvent begin >> " + (ev.getAction() == MotionEvent.ACTION_DOWN ? "down" : ev.getAction() == MotionEvent.ACTION_UP ? "up" : ev.getAction() == MotionEvent.ACTION_MOVE ? "move" : "other")); boolean flag = super.dispatchTouchEvent(ev); Log.e(TAG, "MyRelativeLayout1 dispatchTouchEvent end " + flag + " >> " + (ev.getAction() == MotionEvent.ACTION_DOWN ? "down" : ev.getAction() == MotionEvent.ACTION_UP ? "up" : ev.getAction() == MotionEvent.ACTION_MOVE ? "move" : "other")); return flag; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.e(TAG, "MyRelativeLayout1 onInterceptTouchEvent " + (ev.getAction() == MotionEvent.ACTION_DOWN ? "down" : ev.getAction() == MotionEvent.ACTION_UP ? "up" : ev.getAction() == MotionEvent.ACTION_MOVE ? "move" : "other")); boolean flag = super.onInterceptTouchEvent(ev); Log.e(TAG, "MyRelativeLayout1 onInterceptTouchEvent end " + flag + " >> " + (ev.getAction() == MotionEvent.ACTION_DOWN ? "down" : ev.getAction() == MotionEvent.ACTION_UP ? "up" : ev.getAction() == MotionEvent.ACTION_MOVE ? "move" : "other")); return flag; } @Override public boolean onTouchEvent(MotionEvent ev) { Log.e(TAG, "MyRelativeLayout1 onTouchEvent " + (ev.getAction() == MotionEvent.ACTION_DOWN ? "down" : ev.getAction() == MotionEvent.ACTION_UP ? "up" : ev.getAction() == MotionEvent.ACTION_MOVE ? "move" : "other")); boolean flag = super.onTouchEvent(ev); Log.e(TAG, "MyRelativeLayout1 onTouchEvent end " + flag + " >> " + (ev.getAction() == MotionEvent.ACTION_DOWN ? "down" : ev.getAction() == MotionEvent.ACTION_UP ? "up" : ev.getAction() == MotionEvent.ACTION_MOVE ? "move" : "other")); return flag; }
然后点击MyRelativeLayout2区域时打印出
MainActivity: MainActivity dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent end false >> down
MyRelativeLayout2: MyRelativeLayout2 dispatchTouchEvent begin >> down
MyRelativeLayout2: MyRelativeLayout2 onInterceptTouchEvent begin >> down
MyRelativeLayout2: MyRelativeLayout2 onInterceptTouchEvent end false >> down
MyRelativeLayout2: MyRelativeLayout2 onTouchEvent begin >> down
MyRelativeLayout2: MyRelativeLayout2 onTouchEvent end false >> down
MyRelativeLayout2: MyRelativeLayout2 dispatchTouchEvent end false >> down
MyRelativeLayout1: MyRelativeLayout1 onTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onTouchEvent end false >> down
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent end false >> down
MainActivity: MainActivity onTouchEvent begin >> down
MainActivity: MainActivity onTouchEvent end false >> down
MainActivity: MainActivity dispatchTouchEvent end false >> down
MainActivity: MainActivity dispatchTouchEvent begin >> up
MainActivity: MainActivity onTouchEvent begin >> up
MainActivity: MainActivity onTouchEvent end false >> up
MainActivity: MainActivity dispatchTouchEvent end false >> up
这里能看到. down事件走了这么多步,up事件才走了4步.先来张图展示一下流程.
为什么ACTION_UP的流程才走了这么一点?下面看源码回答
点击左边的MyTextView
MainActivity: MainActivity dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent end false >> down
MyTextView: MyTextView dispatchTouchEvent begin >> down
MyTextView: MyTextView onTouchEvent begin >> down
MyTextView: MyTextView onTouchEvent end false >> down
MyTextView: MyTextView dispatchTouchEvent end false >> down
MyRelativeLayout1: MyRelativeLayout1 onTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onTouchEvent end false >> down
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent end false >> down
MainActivity: MainActivity onTouchEvent begin >> down
MainActivity: MainActivity onTouchEvent end false >> down
MainActivity: MainActivity dispatchTouchEvent end false >> down
MainActivity: MainActivity dispatchTouchEvent begin >> up
MainActivity: MainActivity onTouchEvent begin >> up
MainActivity: MainActivity onTouchEvent end false >> up
MainActivity: MainActivity dispatchTouchEvent end false >> up
图和上面的差不多,差别是TextView不是ViewGroup.没有onInterceptTouchEvent事件
点击右边的MyButton
MainActivity: MainActivity dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent end false >> down
MyButton: MyButton dispatchTouchEvent begin >> down
MyButton: MyButton onTouchEvent begin >> down
MyButton: MyButton onTouchEvent end true >> down
MyButton: MyButton dispatchTouchEvent end true >> down
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent end true >> down
MainActivity: MainActivity dispatchTouchEvent end true >> down
MainActivity: MainActivity dispatchTouchEvent begin >> up
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent begin >> up
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent begin >> up
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent end false >> up
MyButton: MyButton dispatchTouchEvent begin >> up
MyButton: MyButton onTouchEvent begin >> up
MyButton: MyButton onTouchEvent end true >> up
MyButton: MyButton dispatchTouchEvent end true >> up
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent end true >> up
MainActivity: MainActivity dispatchTouchEvent end true >> up
当点击了Button这种clickable控件,就会在onTouchEvent中返回true.
之后的代码其实用处就不大了,因为返回true后会绕过后面MyRelativeLayout1和Activity的onTouchEvent.
意思是拦截了当前事件的继续派发,自己处理和消化.
所以down流程走完了,up流程会跟down流程走同样的方法路径.
看了上面log和图,接着来分析下源码.(源码基于android-23)
先看Activity的dispatchTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }很简单.getWindow().superDispatchTouchEvent(ev)意思就是调用子View的dispatchTouchEvent方法.
如果子View没有拦截返回false,就执行Activity的onTouchEvent方法.拦截了就不执行.
getWindow().superDispatchTouchEvent(ev)会把事件传给第一个View——DecorView
然后来看看看ViewGroup的dispatchTouchEvent.几乎所有原生的安卓布局都没有重写这个方法,而是用的他们父类ViewGroup的.
整个diapatchTouchEvent太长了,以下截取部分重要代码...
public boolean dispatchTouchEvent(MotionEvent ev) {final boolean intercepted;//这是2103行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;}//这是2117行if (!canceled && !intercepted) {//这是2133行...if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {// Child wants to receive touch within its bounds.mLastTouchDownTime = ev.getDownTime();if (preorderedList != null) {// childIndex points into presorted list, find original indexfor (int j = 0; j < childrenCount; j++) {if (children[childIndex] == mChildren[j]) {mLastTouchDownIndex = j;break;}}} else {mLastTouchDownIndex = childIndex;}mLastTouchDownX = ev.getX();mLastTouchDownY = ev.getY();newTouchTarget = addTouchTarget(child, idBitsToAssign);alreadyDispatchedToNewTouchTarget = true;break;}...}//这是2235行}
点击非clickable控件流程
1.down流程
如果是点击的非clickable控件,那么down事件会执行intercepted = onInterceptTouchEvent(ev);
intercepted人如其名,表示是否拦截掉事件交给,交给自己的onTouchEvent处理. 不拦截就交给子View处理.
现在讨论的是正常流程,所以默认布局的onInterceptTouchEvent会返回false, intercepted 为false.
然后2133行的 !canceled && !intercepted 判断为真(canceled不在本文讨论范围)
然后会执行里面最重要的依据dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign), 这个就是把事件派发给子View,交由子View处理的方法.
假设子View都没有处理,就会返回false.跳过了里面的逻辑.
2.up流程
up事件因为actionMasked不等于ACTION_DOWN.所以跑了else的部分,使的intercepted = true;
就不会跑2133那里的if代码段,也就不会派发事件给子View.
所以就解释了上面的问题"为什么ACTION_UP的流程才走了这么一点?"
解释一下设计原理就是,我down事件都派发过给子View了,但是儿子们都没处理,所以后续的up事件(包括中间如果有的move事件)都不会再派发到子类了.
省时省力!
所以小总结一句,当1.自己重写onInterceptTouchEvent拦截,或者2.不是down事件并且子View又没一个给力的能兜住事件, 这两种情况就会自己消费了.
点击clickable控件流程
1.down流程
如果是点击的是clickable控件,流程像上面那样,走到了dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)
然后clickable控件返回了true.执行了里面的addTouchTarget(child, idBitsToAssign)
这个方法大意就是,如果子View有clickable控件,就让当前这个View(父View)持有这个TouchTarget.
从MyRelativelayout1执行这个持有方法,持有MyButton
到FrameLayout执行这个持有方法,持有MyRelativelayout1
到LinearLayout持有FrameLayout
到DecorView持有LinearLayout
一步步赋值(持有)上去.
所有ViewGroup都会有一个变量mFirstTouchTarget, 它是存放该ViewGroup中能消费事件的子View(既可clickable的控件). 好根据它来用来判断后面的流程要怎么走.
ps:明明父布局就是MyRelativeLayout1,为什么会有多出来这么多其他控件.因为手机本质是这样的布局.给个图但是不展开讲了
2.up流程
然后在up事件分发的时候,就在mFirstTouchTarget != null判断为真,执行给intercepted赋值为假的逻辑.
然后在2133代码判断后,up流程就会继续派发事件给子View.
以此达到down,move,up所有事件都最终交给子View处理.
当时研究我想,那么问题来了,如果知道是Button的onTouch事件消费了,能不能下次直接从MainActivity直接绕过中间没用的MyRelativeLayout1等等这些没用的东西到达Button呢.
从打印信息看来,目前还没这么智能.
以上是正常流程..
下面讲讲根据自己的需要改写流程的情况.
以下情景是点击右边的MyButton
1.把dispatchTouchEvent改写成直接返回false
@Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.e(TAG, "MyRelativeLayout1.java - dispatchTouchEvent() ---------- return false"); return false; }
就会打印log
MainActivity: MainActivity dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1.java - dispatchTouchEvent() ---------- return false
MainActivity: MainActivity onTouchEvent begin >> down
MainActivity: MainActivity onTouchEvent end false >> down
MainActivity: MainActivity dispatchTouchEvent end false >> down
MainActivity: MainActivity dispatchTouchEvent begin >> up
MainActivity: MainActivity onTouchEvent begin >> up
MainActivity: MainActivity onTouchEvent end false >> upMainActivity: MainActivity dispatchTouchEvent end false >> up
意思是不会交给子View处理,自己也不处理.
返回false也不拦截,所以就会把事件处理交给Activity的onTouchEvent.
2.把dispatchTouchEvent改成返回true
@Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.e(TAG, "MyRelativeLayout1.java - dispatchTouchEvent() ---------- return true"); return true; }就会打印log
MainActivity: MainActivity dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1.java - dispatchTouchEvent() ---------- return true
MainActivity: MainActivity dispatchTouchEvent end true >> down
MainActivity: MainActivity dispatchTouchEvent begin >> up
MyRelativeLayout1: MyRelativeLayout1.java - dispatchTouchEvent() ---------- return trueMainActivity: MainActivity dispatchTouchEvent end true >> up
这样改写就把事件留住,不再分发子View,然后可以把业务逻辑写在这个方法里面
但是这样不规范,一般想要实现这种效果,都是用下面的方法.
3.在onInterceptTouchEvent中返回true, 在onTouchEvent也返回true,并在其中写处理逻辑
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.e(TAG, "MyRelativeLayout1 onInterceptTouchEvent >> " + (ev.getAction() == MotionEvent.ACTION_DOWN ? "down" : ev.getAction() == MotionEvent.ACTION_UP ? "up" : ev.getAction() == MotionEvent.ACTION_MOVE ? "move" : "other")); return true; } @Override public boolean onTouchEvent(MotionEvent ev) { Log.e(TAG, "MyRelativeLayout1 onTouchEvent >> " + (ev.getAction() == MotionEvent.ACTION_DOWN ? "down" : ev.getAction() == MotionEvent.ACTION_UP ? "up" : ev.getAction() == MotionEvent.ACTION_MOVE ? "move" : "other")); Log.e(TAG, "MyRelativeLayout1.java - onTouchEvent() ---------- 写处理逻辑"); return true; }
MainActivity: MainActivity dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent begin >> down
MyRelativeLayout1: MyRelativeLayout1 onInterceptTouchEvent >> down
MyRelativeLayout1: MyRelativeLayout1 onTouchEvent >> down
MyRelativeLayout1: MyRelativeLayout1.java - onTouchEvent() ---------- 写处理逻辑
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent end true >> down
MainActivity: MainActivity dispatchTouchEvent end true >> down
MainActivity: MainActivity dispatchTouchEvent begin >> up
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent begin >> up
MyRelativeLayout1: MyRelativeLayout1 onTouchEvent >> up
MyRelativeLayout1: MyRelativeLayout1.java - onTouchEvent() ---------- 写处理逻辑
MyRelativeLayout1: MyRelativeLayout1 dispatchTouchEvent end true >> up
MainActivity: MainActivity dispatchTouchEvent end true >> up
onInterceptTouchEvent中返回true使得不会再把事件派发到子View,然后回转到onTouchEvent执行自己的业务代码
使用场景经常是一个布局文件里面有很多子View,但是却不想分发自己处理,就会用这种写法.
稍微讲解一下这个流程的源码
public boolean dispatchTouchEvent(MotionEvent ev) {final boolean intercepted;//这是2103行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;}//这是2117行if (!canceled && !intercepted) {//这是2133行...newTouchTarget = addTouchTarget(child, idBitsToAssign);//这个方法会给成员变量mFirstTouchTarget赋值...}//这是2235行if (mFirstTouchTarget == null) {//这是2238行// No touch targets so treat this as an ordinary view.handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);} else {...}}private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,//这是2511行 View child, int desiredPointerIdBits) {...handled = super.dispatchTouchEvent(event);//这是2547...}//这是2581行
前面讲过,onInterceptTouchEvent如果返回true,会给变量intercepted赋值.
接着在2133行判断中为假,跳过里面派发给子View的代码.
没子View什么事,这时mFirstTouchTarget也会为null
然后在经过2238的判断里,进入dispatchTransformedTouchEvent方法
这个方法里面又会调用到父类,既View的dispatchTouchEvent方法,
public boolean dispatchTouchEvent(MotionEvent event) {...boolean result = false;... if (!result && onTouchEvent(event)) {//9294行 result = true; }//9296行...return result;}
里面就会执行熟悉的onTouchEvent方法.
我曾经想着事件都留到这了,这方法应该不需要返回true.事实
事实是假设返回false还是会让事件最终又回到MainActivity的onTouchEvent方法里.
毕竟全部方法都遵循返回false表示不拦截,事件继续往下一层流转.返回true表示在这里处理,不分发.
关于setOnTouchListener和setOnClickListener是在什么时候调用?他们的优先级是怎么样?
public boolean dispatchTouchEvent(MotionEvent event) {...boolean result = false;...if (onFilterTouchEventForSecurity(event)) {//noinspection SimplifiableIfStatementListenerInfo 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;}}...return result;}View类里面有这么一个判断。
li.mOnTouchListener!=null && 中间省略 && li.mOnTouchListener.onTouch(this, event)
所以假设我们给某个控件执行了setOnTouchListener方法,就会在这里执行onTouch方法。
并且重要的一点是,如果onTouch方法中返回true。result置为true,就不会进入onTouchEvent方法了。
ps:如果是ViewGroup,则是在dispatchTransformedTouchEvent中会调用super.onDispatchTouchEvent,同样会进入到这个流程。
public boolean onTouchEvent(MotionEvent event) {...if (mTouchDelegate != null) {if (mTouchDelegate.onTouchEvent(event)) {return true;}}if (((viewFlags & CLICKABLE) == CLICKABLE ||(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {switch (action) {case MotionEvent.ACTION_UP:...performClick();...break;case MotionEvent.ACTION_DOWN:...break;case MotionEvent.ACTION_CANCEL:...break;case MotionEvent.ACTION_MOVE:...break;}return true;}return false;}在View的onTouchEvent中,里面会判断这个View是不是CLICKABLE和LONG_CLICKABLE。Button这种默认CLICKABLE,TextView默认不是CLICKABLE。所有的View都不是LONG_CLICKABLE的。
然后我们可以通过xml属性或者java代码,或者更直接的,一旦调用了setOnClickListener或setOnLongClickListener方法,就会把对应的这两个CLICKABLE和LONG_CLICKABLE设为true。
进入这个判断里面,里面会根据各种传来MotinEvent事件进行判断,如果符合判断就会调用performClick方法,里面有执行mOnClickListener.onClick这个回调的方法。
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;}
ps:无意中发现有一个View的onTouch方法中有一个TouchDelegate,人如其名,是用来给View设置一个点击代理的。代理返回true的话,就会跳过下面的逻辑
所以得出OnTouchListener的优先级是高于onTouchEvent方法的,自然也就高于onClick,onLongClick这些方法。
对于View.onTouchEvent,如果自己重写这个方法,那么给他们设置的onClickListener和onLongClickListener监听单击和长按事件就作废了。
想要两者兼得,请根据业务逻辑好好构思怎么写。
ps:
View中有一个很重要的方法requestDisallowInterceptTouchEvent,可以在子控件中控制父控件是否拦截。一般用法如下
if (getCurrentItem() != 0) {getParent().requestDisallowInterceptTouchEvent(true);// 用getParent去请求,// 不拦截} else {// 如果是第一个页面,需要显示侧边栏, 请求父控件拦截getParent().requestDisallowInterceptTouchEvent(false);// 拦截}
- Android View 事件分发机制 源码解析
- Android View 事件分发机制 源码解析
- Android View 事件分发机制 源码解析
- Android View 事件分发机制 源码解析
- Android View 事件分发机制 源码解析
- Android View 事件分发机制 源码解析
- Android 6.0事件分发机制源码解析
- Android View 事件分发机制 源码解析
- Android View 事件分发机制 源码解析
- android 事件分发机制源码解析
- Android事件分发机制源码解析
- Android View 事件分发机制 源码解析
- Android事件分发机制源码完全解析
- Android事件分发机制源码解析
- Android View 事件分发机制 && Android ViewGroup 事件分发机制 源码解析 --总结
- Android 源码解析 图解 Android 事件分发机制
- Android事件分发机制源码解析(一)-View的事件分发机制
- Android事件分发机制源码解析(二)-ViewGroup的事件分发机制
- ZOJ 2334 可并堆<斜堆>
- 基于 base64_encode的加密算法
- 利用matlab对彩色图像打马赛克
- 2016 年 7 个最佳的 Java 框架
- iOS手势
- android 事件分发机制源码解析
- 【ife】任务十三:零基础JavaScript编码(一)
- 第二行代码第二章笔记
- BeanUtils常用方法
- vsfftpd 530 login Login incorrect
- .net基础中间件开发完毕总结
- 数据回显---SpringMVC学习笔记(九)
- DesignCompiler获取当前设计中第一层子模块的面积
- 360云盘岛国资源共享群