Android中ViewGroup、View事件分发机制源码分析总结(雷惊风)
2017-04-01 11:24
791 查看
1.概述
很长时间没有回想Android中的事件分发机制了,打开目前的源码发现与两三年前的实现代码已经不一样了,5.0以后发生了变化,更加复杂了,但是万变不离其宗,实现原理还是一样的,在这里将5.0以前的时间分发机制做一下源码剖析及总结。会涉及到几个方法,dispatchTouchEvent()表示事件开始分发方法,在ViewGroup与View中都有,onInterCeptTouchEvent()表示是否拦截当前事件,比如ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCLE,只在ViewGroup中存在,因为一个View的子类已经是最内层View,而ViewGroup的子类还会包含子View,所以它需要可以设置是否拦截当前事件传递到子View中,onToutchEvent()表示消费当前事件,OnToutchListener,OnClickListener,OnLongClickListener。
2.源码剖析
我会将详细的代码注释加在源码中,首先说一下在Activity与Window中的分发过程。当我们点击屏幕上的一个View时,事件的分发就开始了,首先会调用当前Activity的dispatchTouchEvent(MotionEvent ev)方法,我们打开Activity类看一下它的实现:
总结:点击--->Activity.dispatchTouchEvent(event)--->PhoneWindow.superDispatchTouchEvent(event)--->DecorView.superDispatchTouchEvent(event)--->ViewGroup.dispatchTouchevent(event);
说完了一个事件从Activity到ViewGroup的传递流程,下边说一下一个事件在ViewGroup中是如何分发的,看一下源码:
事件分发机制在ViewGroup中的派发流程:
在ViewGroup的dispatchTouchEvent()中,
1.ACTION_DOWN:首先判断拦截相关设置,不拦截:获取每一个子类判断点击区域是否在当前子类上,
在的话调用其dispatchTouchEvent()方法,方法返回true说明当前View处理当前事件序列,记录到mMotionTarget中返回true,
表明找到了处理当前事件的View,停止事件分发。方法返回false,说明当前子View及其子孙View不处理当前事件,如果所有子View都不处理,
后续代码会调用当前ViewGroup的super.dispatchTouchEvent(ev),即执行自己的OnTouch(),OnClick()等(自己处理),如果返回了true,
表明自己处理当前事件,如果返回false,自己不处理当前事件。无论返回true还是false最终都是返回到Activity中。
2.ACTION_MOVE:判断拦截相关:不拦截,调用target.dispatchTouchEvent(ev)进行事件分发;拦截:将target的MotionEvent置为Cancel,
mMotionTarget赋值为null,返回true,表明自己消费了事件。这次不会调用自己的super.dispatchTouchEvent(ev)。
再次MOVE分发时由于mMotionTarget为空了,所以会调用自己的super.dispatchTouchEvent(ev),即自身处理。
3.ACTION_CANCEL或ACTION_UP:回复状态默认值,判断是否拦截,拦截返回true;不拦截向下派发事件。
下边看一下在Viewgroup的dispatchTouchEvent方法中继续向下分发事件代码if(child.dispatchTouchEvent(ev))执行后如果child也是一个ViewGroup子类那么跟上边的逻辑是一样的,那么如果child是一个View的子类,比如Button、TextView、ImageView等时,是如何分发事件的,即事件在View类中的传递流程,接着看一下源码:
如果longClickListener中返回true,mHasPerformedLongPress就是true;
事件分发在View中的流程总结:
调用View的dispatchTouchEvent(),判断是否设置了OnTouchListener并且View为Enable的,如果都满足,调用OnTouchListener.onTouch(this, event),如果返回true,
直接返回true,表明当前View处理当前事件,不会调用自己的onTouchEvent(),如果onTouth()方法返回false,则调用onTouchEvent()。
在onTouchEvent()中如果当前View为可点击或者可长按则一定返回true,否则返回false。
1.ACTION_DOWN:初始化状态为PREPRESSED,设置mHasPerformedLongPress为flase,发送100ms任务,如果100ms到后,还没有UP或者移出当前View,
则将标志置为PRESSED,如果支持长按事件,发送另一个400ms计时任务,如果到时间后依然没有UP,LongClickListener不为空,调用执行,返回true,将
mHasPerformedLongPress设置为true。
2.ACTION_MOVE:检测用户有没有移出View,并移除相关时间任务。
3.ACTION_UP:100ms内仍然是PREPRESSED状态,不会触发Click;在100到500ms之间,取消仍然运行的计时任务,执行 performClick去执行Onclick;
如果在500ms以后,没有设置onLongClickListener或者返回false,仍然会执行performClick;返回true,onClick不执行;LongClick在Click前执行。
在事件分发过程中OnTouchListener优先OnClickListener与OnLongClickListener执行,如果返回false,OnClickListener与OnLongClickListener才有可能执行到,返回true不执行。在500ms内抬起手指不会执行OnLongClickListener,会执行OnClickListener,在500ms以后如果OnLongClickListener返回false,OnClickListener会执行,返回true不执行。这就是他们几个的执行顺序及规则,当然这是建立在我们注册了这三个监听。今天的总结就到这里,希望对初学者有帮助,如果哪里不对,欢迎大神拍砖,谢谢!
很长时间没有回想Android中的事件分发机制了,打开目前的源码发现与两三年前的实现代码已经不一样了,5.0以后发生了变化,更加复杂了,但是万变不离其宗,实现原理还是一样的,在这里将5.0以前的时间分发机制做一下源码剖析及总结。会涉及到几个方法,dispatchTouchEvent()表示事件开始分发方法,在ViewGroup与View中都有,onInterCeptTouchEvent()表示是否拦截当前事件,比如ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCLE,只在ViewGroup中存在,因为一个View的子类已经是最内层View,而ViewGroup的子类还会包含子View,所以它需要可以设置是否拦截当前事件传递到子View中,onToutchEvent()表示消费当前事件,OnToutchListener,OnClickListener,OnLongClickListener。
2.源码剖析
我会将详细的代码注释加在源码中,首先说一下在Activity与Window中的分发过程。当我们点击屏幕上的一个View时,事件的分发就开始了,首先会调用当前Activity的dispatchTouchEvent(MotionEvent ev)方法,我们打开Activity类看一下它的实现:
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }只挑主要的说,可以看到调用了getWindow().superDispatchTouchEvent()方法,如果这个方法返回true,则表示事件被消费,直接返回true,如果返回false,会执行自己的onTouchEvent()表明如果View树中都没有消费事件,当前Activity消费事件。我们知道在Phone上边PhoneWindow是Window的唯一子类,那我们就去PhoneWindow中找找相关方法:
//phoneWindow public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); }在PhoneWindow中可以看到调用了mDocor的super***方法,大家应该都知道mDecor为window中的最顶级View类,他是PhoneWindow的内部类,继承了FrameLayout。我们进入mDecor的内部看看它的实现:
//DecorView public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); }在DecorView中又调用了父类方法,我们最终在GroupView中找到了相关实现,这样我们就完成了一个点击事件从Activity到ViewGroup的过程。无论是ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCLE任何一个都是如此的一直传递流程。
总结:点击--->Activity.dispatchTouchEvent(event)--->PhoneWindow.superDispatchTouchEvent(event)--->DecorView.superDispatchTouchEvent(event)--->ViewGroup.dispatchTouchevent(event);
说完了一个事件从Activity到ViewGroup的传递流程,下边说一下一个事件在ViewGroup中是如何分发的,看一下源码:
//ViewGroup中; public boolean dispatchTouchEvent(MotionEvent ev) { final int action = ev.getAction(); final float xf = ev.getX(); final float yf = ev.getY(); final float scrolledXFloat = xf + mScrollX; final float scrolledYFloat = yf + mScrollY; final Rect frame = mTempRect; boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; //ViewGroup中按下事件处理; if (action == MotionEvent.ACTION_DOWN) { //如果mMotionTarget保存了View,因为是从新开始的DOWN,所以清空; if (mMotionTarget != null) { mMotionTarget = null; } //是否禁用拦截事件功能,如果禁用了拦截功能(不拦截)或者onInterceptTouchEvent()返回false,说明不拦截事件。 if (disallowIntercept || !onInterceptTouchEvent(ev)) { ev.setAction(MotionEvent.ACTION_DOWN); final int scrolledXInt = (int) scrolledXFloat; final int scrolledYInt = (int) scrolledYFloat; final View[] children = mChildren; final int count = mChildrenCount; //循环每一个子View; for (int i = count - 1; i >= 0; i--) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { child.getHitRect(frame); //判断当前子View是否包含点击区域; if (frame.contains(scrolledXInt, scrolledYInt)) { final float xc = scrolledXFloat - child.mLeft; final float yc = scrolledYFloat - child.mTop; ev.setLocation(xc, yc); child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; //调用子View的dispatchTouchEvent() ,向子事件分发事件; //1.如果返回true,说明子类或者子类的某个子类中消费了事件,将这个child记录到mMotionTarget中,每一个ViewGroup都记录了 //消费了这个事件的子View,像链式,最后当前ViewGroup返回true,返回给调用这个ViewGroup的外层ViewGroup,一直到Activity中 //的dispatchTouchEvent()中,最后Activity中也返回true; //2.如果返回false,说明子类或者子类的子类中没有消费这个事件,所以mMotionTarget不会被赋值,会继续向下执行代码; if (child.dispatchTouchEvent(ev)) { mMotionTarget = child; return true; } } } } } } boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL); if (isUpOrCancel) { mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } final View target = mMotionTarget; //ACTION_DOWN事件时,被拦截,或者子类没有消费ACTION_DOWN,调用自己的onTouchEvent()执行onTouch、onClick()方法等; //ACTION_UP时,子类没有消费ACTION_DOWN; if (target == null) { ev.setLocation(xf, yf); if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { ev.setAction(MotionEvent.ACTION_CANCEL); mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; } //调用super方法即调用到View中的dispatchTouchEvent()方法,还是调用的当前View, //在View中会调用自己的onTouchEvent()执行onTouch、onClick()方法等处理; //这里两种情况:1.自身没有处理返回false;2.自身处理了返回true,本身处理了,不会保存到mMotionTarget中,而它的外层ViewGroup会保存他; //true/false都会一直返回到Activity中。 return super.dispatchTouchEvent(ev); } //ACTION_UP或者MOVE时,子类中消费了ACTION_DOWN事件,没有禁用拦截功能(可以拦截),并且拦截事件; // 无论 target 是否为 null ,ACTION_DOWN事件的处理都不能走到这里,在之下都是ACTION_MOVE和ACTION_UP的逻辑 // 如果执行到这里,说明有响应ACTION_DOWN事件的view对象,这就看我们是否被允许拦截和要不要拦截了 // 如果允许拦截并且拦截了ACTION_MOVE和ACTION_UP事件,则将ACTION_CANCEL事件分发给target // 然后直接返回true,表示已经响应了该次事件 if (!disallowIntercept && onInterceptTouchEvent(ev)) { final float xc = scrolledXFloat - (float) target.mLeft; final float yc = scrolledYFloat - (float) target.mTop; mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; ev.setAction(MotionEvent.ACTION_CANCEL); ev.setLocation(xc, yc); //将子类事件重置为ACTION_CANCEL; if (!target.dispatchTouchEvent(ev)) { } mMotionTarget = null; return true; } if (isUpOrCancel) { mMotionTarget = null; } final float xc = scrolledXFloat - (float) target.mLeft; final float yc = scrolledYFloat - (float) target.mTop; ev.setLocation(xc, yc); if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { ev.setAction(MotionEvent.ACTION_CANCEL); target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; mMotionTarget = null; } // 如果没有拦截ACTION_MOVE和ACTION_UP事件,则直接派发给target return target.dispatchTouchEvent(ev); }注释加了不少,看几遍就能明白。
事件分发机制在ViewGroup中的派发流程:
在ViewGroup的dispatchTouchEvent()中,
1.ACTION_DOWN:首先判断拦截相关设置,不拦截:获取每一个子类判断点击区域是否在当前子类上,
在的话调用其dispatchTouchEvent()方法,方法返回true说明当前View处理当前事件序列,记录到mMotionTarget中返回true,
表明找到了处理当前事件的View,停止事件分发。方法返回false,说明当前子View及其子孙View不处理当前事件,如果所有子View都不处理,
后续代码会调用当前ViewGroup的super.dispatchTouchEvent(ev),即执行自己的OnTouch(),OnClick()等(自己处理),如果返回了true,
表明自己处理当前事件,如果返回false,自己不处理当前事件。无论返回true还是false最终都是返回到Activity中。
2.ACTION_MOVE:判断拦截相关:不拦截,调用target.dispatchTouchEvent(ev)进行事件分发;拦截:将target的MotionEvent置为Cancel,
mMotionTarget赋值为null,返回true,表明自己消费了事件。这次不会调用自己的super.dispatchTouchEvent(ev)。
再次MOVE分发时由于mMotionTarget为空了,所以会调用自己的super.dispatchTouchEvent(ev),即自身处理。
3.ACTION_CANCEL或ACTION_UP:回复状态默认值,判断是否拦截,拦截返回true;不拦截向下派发事件。
下边看一下在Viewgroup的dispatchTouchEvent方法中继续向下分发事件代码if(child.dispatchTouchEvent(ev))执行后如果child也是一个ViewGroup子类那么跟上边的逻辑是一样的,那么如果child是一个View的子类,比如Button、TextView、ImageView等时,是如何分发事件的,即事件在View类中的传递流程,接着看一下源码:
//View中的dispatchTouchEvent; public boolean dispatchTouchEvent(MotionEvent event) { if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { return true; } return onTouchEvent(event); }重要源码就这些,在这里可以看到首先判断我们有没有给当前View设置OnTouchListener事件,并且当前View是enabled状态的,如果前两个条件都满足,则会调用我们OnTouchListener中的onTouch()方法,如果上边三个条件都为true,则直接返回true,表明当前View消费当前事件,如果我们在onTouch()方法中返回false,那么就会跳过判断执行自己的onTouchEvent()方法,这里先提前说一下,调用onTouchEvent()方法也有继续判断当前View是否消费当前事件的作用,在其内部会根据我们点击在控件上停留时间判断是否执行OnClick,OnLongClick事件。下边看一下onTouchEvent()源码:
//View中onTouchEvent(); public boolean onTouchEvent(MotionEvent event) { final int viewFlags = mViewFlags; //当前View是Disabled状态且是可点击则会消费掉事件 if ((viewFlags & ENABLED_MASK) == DISABLED) { // 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)); } //将事件交给代理者处理,直接return true if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } //View可以点击或者长按,只要进入判断,一定反回true,即消费事件; if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; //100ms内或者以后都会进入代码块; if ((mPrivateFlags & PRESSED) != 0 || prepressed) { // take focus if we don't have it already and we should in // touch mode. boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); } //如果是在0到500ms之间或者500秒之后执行了LongClick事件但返回的false(不拦截事件); if (!mHasPerformedLongPress) { // This is a tap, so remove the longpress check //取消长按事件任务监听; removeLongPressCallback(); // Only perform take click actions if we were in the pressed state //在pressed状态,只执行Click事件; 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(); } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); } if (prepressed) { mPrivateFlags |= PRESSED; refreshDrawableState(); postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } removeTapCallback(); } break; case MotionEvent.ACTION_DOWN: if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } //设置被点击状态; mPrivateFlags |= PREPRESSED; //没有触发长按事件; mHasPerformedLongPress = false; //发送100毫秒延时消息,执行CheckForTap类中方法。 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); break; case MotionEvent.ACTION_CANCEL: mPrivateFlags &= ~PRESSED; refreshDrawableState(); removeTapCallback(); break; case MotionEvent.ACTION_MOVE: final int x = (int) event.getX(); final int y = (int) event.getY(); // Be lenient about moving outside of buttons int slop = mTouchSlop; if ((x < 0 - slop) || (x >= getWidth() + slop) || (y < 0 - slop) || (y >= getHeight() + slop)) { // Outside button //手指滑出了View范围,取消100ms计时任务; removeTapCallback(); //判断是否包含PRESSED标识,如果包含,移除长按的检查任务; if ((mPrivateFlags & PRESSED) != 0) { // Remove any future long press/tap checks removeLongPressCallback(); // Need to switch from pressed to not pressed mPrivateFlags &= ~PRESSED; refreshDrawableState(); } } break; } return true; } return false; }有些相对来说重要的类或者代码我会在下边贴出来,帮助大家更好地理解,在这里代码就不详细的讲了,在最后我还会总结,相关代码:
//点击状态变为PRESSED类; private final class CheckForTap implements Runnable { public void run() { mPrivateFlags &= ~PREPRESSED; //设置PRESSED标识 mPrivateFlags |= PRESSED; //刷新背景 refreshDrawableState(); //如果View支持长按事件,调用postCheckForLongClick()方法; if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) { postCheckForLongClick(ViewConfiguration.getTapTimeout()); } } }
//ViewConfiguration.getLongPressTimeout() 为500ms;再次发送500-100=400ms延时消息 private void postCheckForLongClick(int delayOffset) { mHasPerformedLongPress = false; if (mPendingCheckForLongPress == null) { mPendingCheckForLongPress = new CheckForLongPress(); } mPendingCheckForLongPress.rememberWindowAttachCount(); postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout() - delayOffset); }
//长按事件发生处理器; class CheckForLongPress implements Runnable { private int mOriginalWindowAttachCount; public void run() { if (isPressed() && (mParent != null) && mOriginalWindowAttachCount == mWindowAttachCount) { //执行LongClick监听; if (performLongClick()) { mHasPerformedLongPress = true; } } } }总结:也就是说如果用户设置了LongClickListener,从用户触发ACTION_DOWN开始500ms才能触发事件,如果达不到500ms,视为点击事件;
如果longClickListener中返回true,mHasPerformedLongPress就是true;
//还在100ms内,取消监听; private void removeTapCallback() { if (mPendingCheckForTap != null) { mPrivateFlags &= ~PREPRESSED; removeCallbacks(mPendingCheckForTap); } }
//取消长按事件任务监听; private void removeLongPressCallback() { if (mPendingCheckForLongPress != null) { removeCallbacks(mPendingCheckForLongPress); } }总结:主要就是判断是否移出当前View,如果移出,根据时间移出相关计时任务;
private final class UnsetPressedState implements Runnable { public void run() { setPressed(false); } } public void setPressed(boolean pressed) { if (pressed) { mPrivateFlags |= PRESSED; } else { mPrivateFlags &= ~PRESSED; } refreshDrawableState(); dispatchSetPressed(pressed); } 清除PRESSED,刷新背景,向下传递pressed;
事件分发在View中的流程总结:
调用View的dispatchTouchEvent(),判断是否设置了OnTouchListener并且View为Enable的,如果都满足,调用OnTouchListener.onTouch(this, event),如果返回true,
直接返回true,表明当前View处理当前事件,不会调用自己的onTouchEvent(),如果onTouth()方法返回false,则调用onTouchEvent()。
在onTouchEvent()中如果当前View为可点击或者可长按则一定返回true,否则返回false。
1.ACTION_DOWN:初始化状态为PREPRESSED,设置mHasPerformedLongPress为flase,发送100ms任务,如果100ms到后,还没有UP或者移出当前View,
则将标志置为PRESSED,如果支持长按事件,发送另一个400ms计时任务,如果到时间后依然没有UP,LongClickListener不为空,调用执行,返回true,将
mHasPerformedLongPress设置为true。
2.ACTION_MOVE:检测用户有没有移出View,并移除相关时间任务。
3.ACTION_UP:100ms内仍然是PREPRESSED状态,不会触发Click;在100到500ms之间,取消仍然运行的计时任务,执行 performClick去执行Onclick;
如果在500ms以后,没有设置onLongClickListener或者返回false,仍然会执行performClick;返回true,onClick不执行;LongClick在Click前执行。
在事件分发过程中OnTouchListener优先OnClickListener与OnLongClickListener执行,如果返回false,OnClickListener与OnLongClickListener才有可能执行到,返回true不执行。在500ms内抬起手指不会执行OnLongClickListener,会执行OnClickListener,在500ms以后如果OnLongClickListener返回false,OnClickListener会执行,返回true不执行。这就是他们几个的执行顺序及规则,当然这是建立在我们注册了这三个监听。今天的总结就到这里,希望对初学者有帮助,如果哪里不对,欢迎大神拍砖,谢谢!
相关文章推荐
- Android应用开发原理之从ViewGroup源码分析ViewGroup的事件分发机制
- Android View和ViewGroup事件分发机制源码分析
- Android View 事件分发机制 && Android ViewGroup 事件分发机制 源码解析 --总结
- Android事件分发机制源码分析下----ViewGroup事件分发分析
- Android开发总结笔记 ViewGroup的事件分发机制 3-10
- Android触摸事件派发机制源码分析之ViewGroup
- 【Android】源码分析 - View事件分发机制
- Android源码解析ViewGroup的touch事件分发机制
- Android源码分析--View的事件分发机制
- Android ViewGroup事件分发机制总结
- Android触摸事件分发处理机制详解与源码分析一(View篇)
- Android ViewGroup 触摸屏事件派发机制和源码分析
- Android中View和ViewGroup事件分发拦截机制完美分析
- Android触摸屏ViewGroup事件派发机制详解与源码分析
- Android事件分发机制源码分析上----View事件分发分析
- android 事件分发机制(源码分析)—(详细)(逐步总结)(值得一看)
- [置顶] android之View和ViewGroup事件分发机制分析(一)(View的事件分发机制)
- Android View框架总结(八)ViewGroup事件分发机制
- Android开发-事件分发机制实验分析ViewGroup、View事件分发,结合职责链模式
- Android—— View事件分发机制的源码分析