事件分发机制
来源:互联网 发布:nba火箭vs尼克斯数据 编辑:程序博客网 时间:2024/05/17 07:42
1.Android中事件分发
事件分发顺序:Activity(Window) -> ViewGroup -> View
1.1Activity的dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } //getWindow()获取的就是PhoneWindow对象 if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); } //该方法为空方法 //当此activity在栈顶时,触屏点击按home,back,menu键等都会触发此方法 //所以onUserInteraction()主要用于屏保 public void onUserInteraction() { }
1.2 PhoneWindow的superDispatchTouchEvent(ev)
@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event);//mDecor是DecorView的实例//DecorView是视图的顶层view,继承自FrameLayout,是所有界面的父类}
1.3 DecorView的superDispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
//DecorView继承自FrameLayout
//那么它的父类就是ViewGroup
而super.dispatchTouchEvent(event)方法,其实就应该是ViewGroup的dispatchTouchEvent()
}
1.4 汇总;
当一个点击事件发生时,调用顺序如下:
- 事件最先传到Activity的dispatchTouchEvent()进行事件分发
- 调用Window类实现类PhoneWindow的superDispatchTouchEvent()
- 调用DecorView的superDispatchTouchEvent()
- 最终调用DecorView父类的dispatchTouchEvent(),即ViewGroup的
dispatchTouchEvent()
1.5 回头看Activity的dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
小结:
当一个点击事件发生时,事件最先传到Activity的dispatchTouchEvent()进行事件分发,最终是调用了ViewGroup的dispatchTouchEvent()方法 ,判断ViewGroup的dispatchTouchEvent()返回值是否为true,返回true就不执行Activity的onTouchEvent()方法;返回false,就执行。
2).View事件分发源码分析
2.1 View的dispatchTouchEvent()方法
//mOnTouchListener是在View类下setOnTouchListener方法里赋值的//mViewFlags & ENABLED_MASK) == ENABLED即当前点击的控件是否enable//mOnTouchListener的onTouch(this, event)方法的返回值是否为truepublic boolean dispatchTouchEvent(MotionEvent event) { if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { return true; } return onTouchEvent(event); public void setOnTouchListener(OnTouchListener l) { //即只要我们给控件注册了Touch事件,mOnTouchListener就一定被赋值(不为空) mOnTouchListener = l; } }
第一个条件:mOnTouchListener != null;
第二个条件:(mViewFlags & ENABLED_MASK) == ENABLED;
第三个条件:mOnTouchListener.onTouch(this, event);
小结:
- 只有这三个条件都为true,dispatchTouchEvent返回true,不执行onTouchEvent(event),否则执行否则执行onTouchEvent(event)方法。
- onTouch()方法先于onTouchEvent(event)执行
2.2 onTouchEvent(event)
public boolean onTouchEvent(MotionEvent event) { final int viewFlags = mViewFlags; 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)); } if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } //如果该控件enable且可以点击的就会进入到下两行的switch判断中去; if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { //如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。 switch (event.getAction()) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; 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(); } if (!mHasPerformedLongPress) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actions if we were in the pressed state 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()的源码分析 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; 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 removeTapCallback(); if ((mPrivateFlags & PRESSED) != 0) { // Remove any future long press/tap checks removeLongPressCallback(); // Need to switch from pressed to not pressed mPrivateFlags &= ~PRESSED; refreshDrawableState(); } } break; } //如果该控件是可以点击的,就一定会返回true return true; } //如果该控件是不可以点击的,就一定会返回false return false; }
由上可知
- 在View为disable情况下,如果view可点击则onTouchEvent()返回true,否则onTouchEvent()返回false。
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)); }
- 在View为enable情况下,如果view可点击则onTouchEvent()返回true,如果view不可点击,onTouchEvent()返回false.
public boolean onTouchEvent(MotionEvent event) { if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { //..........省略 //如果该控件enable且可以点击的,就一定会返回false return true; } //如果该控件enable但不可以点击的,就一定会返回false return false; }
结合以上2点可知:
只要view可点击则onTouchEvent()一定返回true,view不可点击,onTouchEvent()一定返回false.
2.3 View的点击事件在哪执行?
一次点击事件必不可少的行为MotionEvent.ACTION_DOWN和MotionEvent.ACTION_UP。那么点击事件一定是在MotionEvent.ACTION_UP中执行,看源码:
switch (event.getAction()) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; if ((mPrivateFlags & PRESSED) != 0 || prepressed) { //.......省略...... if (!mHasPerformedLongPress) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actions if we were in the pressed state if (!focusTaken) { if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { //performClick翻译一下,执行点击? 呵呵 performClick(); } } } //.......省略...... break; } //.......省略...... }
2.3.1 performClick()源码
public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); //只要mOnClickListener不为null,就会去调用mOnClickListener的onClick方法 if (mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); mOnClickListener.onClick(this); return true; } return false; } public void setOnClickListener(OnClickListener l) { if (!isClickable()) { setClickable(true); } mOnClickListener = l; }
总结:
- 执行顺序:onTouch()–>onTouchEvent()–>onClick()
- view为disable时,onTouch()不会执行,onTouchEvent()执行,onClick()不执行
- 如果view为enable且onTouch()返回true,就会让dispatchTouchEvent方法返回true,那么将不会执行onTouchEvent(),onClick()也不会执行。
- 如果view为enable且onTouch()返回false,那么就会执行onTouchEvent();执行onClick()。
- 只要view可点击则onTouchEvent()一定返回true,view不可点击,onTouchEvent()一定返回false.
3.ViewGroup事件分发源码分析
3.1 ViewGroup的dispatchTouchEvent()
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; if (action == MotionEvent.ACTION_DOWN) { if (mMotionTarget != null) { mMotionTarget = null; } //如果我们在onInterceptTouchEvent()中返回false,就会让第二个值为true,从而进入到条件判断的内部//如果我们在onInterceptTouchEvent()中返回true,就会让第二个值为false,从而跳出了这个条件判断。//关于onInterceptTouchEvent()请看下面分析(关注点1) 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; //通过for循环,遍历了当前ViewGroup下的所有子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是不是正在点击的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()方法(具体请看下面的View事件分发机制) //实现了点击事件从ViewGroup到View的传递 if (child.dispatchTouchEvent(ev)) { //调用子View的dispatchTouchEvent后是有返回值的 //如果这个控件是可点击的话,那么点击该控件时,dispatchTouchEvent的返回值必定是true //因此会导致条件判断成立 mMotionTarget = child; //于是给ViewGroup的dispatchTouchEvent方法直接返回了true,这样就导致后面的代码无法执行,直接跳出 //即把ViewGroup的touch事件拦截掉 return true; } } } } } } boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL); if (isUpOrCancel) { mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } final View target = mMotionTarget; //没有任何View接收事件的情况,即点击空白处情况 if (target == null) { ev.setLocation(xf, yf); if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { ev.setAction(MotionEvent.ACTION_CANCEL); mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; } //调用ViewGroup的父类View的dispatchTouchEvent()//因此会执行ViewGroup的onTouch()、onTouchEvent()//实现了点击事件从ViewGroup到View的传递 return super.dispatchTouchEvent(ev); } //之后的代码在一般情况下是走不到的了,我们也就不再继续往下分析。 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); 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; } return target.dispatchTouchEvent(ev); }
3.1.1 disallowIntercept
disallowIntercept:是否禁用事件拦截的功能(默认是false),可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改。
3.1.2 onInterceptTouchEvent(ev) 是否拦截事件(默认是false)。
public boolean onInterceptTouchEvent(MotionEvent ev) { return false; }
3.2 MotionEvent.ACTION_DOWN时,做了些什么??
if (action == MotionEvent.ACTION_DOWN) { if (mMotionTarget != null) { mMotionTarget = null; } //onInterceptTouchEvent()返回false或disallowIntercept==true进入if语句 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; //通过for循环,遍历了当前ViewGroup下的所有子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是不是正在点击的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()方法 if (child.dispatchTouchEvent(ev)) { //如果这个控件是可点击的话,那么点击该控件时,dispatchTouchEvent的返回值必定是true //因此会导致条件判断成立 mMotionTarget = child; //于是给ViewGroup的dispatchTouchEvent方法直接返回了true,这样就导致后面的代码无法执行,直接跳出 //即把ViewGroup的touch事件拦截掉 return true; } } } } } }
首先判断(disallowIntercept || !onInterceptTouchEvent(ev))是否为true,true则进入if语句,通过for循环,遍历了当前ViewGroup下的所有子View,查找点击的View,
- 找到则调用该View的dispatchTouchEvent()方法,返回值为true,则该View消耗了DOWN事件,ViewGroup的dispatchTouchEvent()结束,后续的MOVE等事件都由该View消费。
- 没找到或找到但View的dispatchTouchEvent()返回值为false,则DOWN事件未被任何View消耗。走流程3.3。
3.3 没有任何View消耗事件的情况或onInterceptTouchEvent()为true
//DOWN事件未被任何View消耗则mMotionTarget==null; final View target = mMotionTarget; //没有任何View接收事件的情况,即点击空白处情况 if (target == null) { ev.setLocation(xf, yf); if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { ev.setAction(MotionEvent.ACTION_CANCEL); mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; } //调用ViewGroup的父类View的dispatchTouchEvent()//因此会执行ViewGroup的onTouch()、onTouchEvent()//实现了点击事件从ViewGroup到View的传递 return super.dispatchTouchEvent(ev); }
由上可知:
没有任何View消耗DOWN事件或onInterceptTouchEvent()为true情况下,target == null,会调用ViewGroup父类View的dispatchTouchEvent(),会执行ViewGroup的onTouch()、onTouchEvent()。
- super.dispatchTouchEvent(ev)为true,消耗事件。
- super.dispatchTouchEvent(ev)为false,事件向上传递。
3.4 拦截子View的DOWN事件
onInterceptTouchEvent()设置为true,走流程3.3,如果DOWN事件被ViewGroup消费了,该事件列的其他事件(Move、Up)将直接传递给ViewGroup的onTouch()或onTouchEvent()。
3.5 不拦截子View的DOWN事件,拦截子View的其他事件
子View消费了DOWN事件,在ViewGroup的onInterceptTouchEvent方法返回true拦截该MOVE事件,这个MOVE事件将会被系统变成一个CANCEL事件传递给子View的dispatchTouchEvent()方法,后续又来了一个MOVE事件,该MOVE事件会直接传递给ViewGroup的onTouch()或onTouchEvent().
//子View消费了DOWN事件且onInterceptTouchEvent()==true&&disallowIntercept ==false if (!disallowIntercept && onInterceptTouchEvent(ev)) { final float xc = scrolledXFloat - (float) target.mLeft; final float yc = scrolledYFloat - (float) target.mTop; mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; //设置MotionEvent.ACTION_CANCEL事件 ev.setAction(MotionEvent.ACTION_CANCEL); ev.setLocation(xc, yc); //调用子view的dispatchTouchEvent()并设置mMotionTarget==null //下个事件来,走流程3.3 if (!target.dispatchTouchEvent(ev)) { } mMotionTarget = null; //MotionEvent.ACTION_CANCEL事件被消费 return true; }
4.Touch事件的后续事件(MOVE、UP)层级传递
- 当(Activity、ViewGroup、View)的dispatchTouchEvent在进行事件分发的时候,只有前一个事件返回true,才会收到后一个事件。
- 如果在某个对象(Activity、ViewGroup、View)的dispatchTouchEvent()收到ACTION_DOWN的函数并返回true,那么也能收到ACTION_MOVE和ACTION_UP 。
黑线:ACTION_DOWN事件传递方向
红线:ACTION_MOVE和ACTION_UP事件传递方向
- 如果在某个对象(Activity、ViewGroup、View)的onTouchEvent()消费了DOWN事件,那么ACTION_MOVE和ACTION_UP的事件从上往下传到这个View后就不再往下传递了,而直接传给这个View的onTouchEvent()并结束本次事件传递过程。
参考
- OnTouchEvent事件分发机制
- android事件分发机制
- Android事件分发机制
- Android 事件分发机制
- Android事件分发机制
- Android 事件分发机制
- Android 事件分发机制
- android 事件分发机制
- Android事件分发机制
- android 事件分发机制
- android事件分发机制
- Android 事件分发机制
- android事件分发机制
- 【cocos2dx事件分发机制】
- android 事件分发机制
- android 事件分发机制
- Android 事件分发机制
- Android事件分发机制
- 用XInput库使用xbox360手柄
- Java学习之InputStream中read()与read(byte[] b)
- ElasticSearch单节点安装
- Eclipse+Tomcat启动时两个问题的解决记录
- 【Algorithm】c++实现各种排序算法
- 事件分发机制
- eclipse中更改账号
- rabbitMQ、activeMQ、zeroMQ、Kafka、Redis 比较,资料汇总
- 简析Android网络请求Volley框架的工作原理
- ubuntu linux虚拟机配置多个IP地址
- Pdf-renderer, PDFBox 和JPedal做一个简单的介绍
- 如何在Web项目中的service业务层获取项目根路劲(转载)
- 通道与像素
- 二叉树的相关操作(2)--各类分支查找