View事件机制源码分析

目录介绍

  • 01.Android中事件分发顺序
  • 02.Activity的事件分发机制
    • 2.1 源码分析
    • 2.2 点击事件调用顺序
    • 2.3 得出结论
  • 03.ViewGroup事件的分发机制
    • 3.1 看一下这个案例
    • 3.2 源码分析
    • 3.3 得出结论
  • 04.View事件的分发机制
    • 4.1 源码分析
    • 4.2 得出结论
    • 4.3 验证结论
  • 05.思考一下
    • 5.1 onTouch()和onTouchEvent()的区别
    • 5.2 Touch事件的后续事件传递

好消息

  • 博客笔记大汇总【16年3月到至今】,包括Java基础及深刻知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,固然也在工做之余收集了大量的面试题,长期更新维护而且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢!
  • 连接地址:https://github.com/yangchong211/YCBlogs
  • 若是以为好,能够star一下,谢谢!固然也欢迎提出建议,万事起于忽微,量变引发质变!

View事件系列博客

  • 01.View事件基础
    • 事件分发的对象是谁?事件在哪些对象间进行传递?事件分发过程涉及方法?事件分发机制方法说明?
  • 02.View事件分发场景
    • 事件分发背景描述,事件传递状况
  • 03.View事件机制源码分析
    • Android中事件分发顺序?Activity的事件分发机制?ViewGroup事件的分发机制 ?View事件的分发机制?onTouch()和onTouchEvent()的区别?
  • 04.View事件机制
    • 触摸事件,分发事件,拦截事件,三个事件机制怎么向其调用者传递处理结果,滑动冲突的思路及方法 ,以及具体的滑动冲突解决方案案例
  • 05.View的滑动冲突
    • 经过一个滑动冲突的案例,来说解外部拦截法解决滑动冲突,内部拦截法解决滑动冲突
  • 07.View事件总结1
    • Android事件分发机制,View和ViewGroup分发事件,onTouch()、onTouchEvent()和onClick()执行顺序,View处理事件的优先级,点击事件传递过程,事件传递规则要点
  • 08.View事件总结2
    • View滑动有哪些方法,Activity事件分发过程,Window事件分发过程,DecorView的事件分发,根View的事件分发

01.Android中事件分发顺序

  • Android中事件分发顺序:
    • Activity(Window) -> ViewGroup -> View
  • 其中:
    • super:调用父类方法
    • true:消费事件,即事件不继续往下传递
    • false:不消费事件,事件继续往下传递 / 交由给父控件onTouchEvent()处理
  • 充分理解Android分发机制,本质上是要理解:
    • Activity对点击事件的分发机制
    • ViewGroup对点击事件的分发机制
    • View对点击事件的分发机制

02.Activity的事件分发机制

2.1 源码分析

  • 当一个点击事件发生时,事件最早传到Activity的dispatchTouchEvent()进行事件分发
    • 具体是由Activity的Window来完成
  • 咱们来看下Activity的dispatchTouchEvent()的源码
    public boolean dispatchTouchEvent(MotionEvent ev) { //第一步 //通常事件列开始都是DOWN,因此这里基本是true if (ev.getAction() == MotionEvent.ACTION_DOWN) { //第二步 onUserInteraction(); } //第三步 if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); } 
  • 第一步
    • 通常事件列开始都是DOWN(按下按钮),因此这里返回true,执行onUserInteraction()
  • 第二步
    • 先来看下onUserInteraction()源码
    public void onUserInteraction() { } 
    • 从源码能够看出:
      • 该方法为空方法
      • 从注释得知:当此activity在栈顶时,触屏点击按home,back,menu键等都会触发此方法
      • 因此onUserInteraction()主要用于屏保
  • 第三步
    • Window类是抽象类,且PhoneWindow是Window类的惟一实现类
    • superDispatchTouchEvent(ev)是抽象方法
    • 经过PhoneWindow类中看一下superDispatchTouchEvent()的做用
    @Override public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); //mDecor是DecorView的实例 //DecorView是视图的顶层view,继承自FrameLayout,是全部界面的父类 } 
  • 接下来咱们看mDecor.superDispatchTouchEvent(event):
    public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); //DecorView继承自FrameLayout //那么它的父类就是ViewGroup super.dispatchTouchEvent(event)方法,其实就应该是ViewGroup的dispatchTouchEvent() } 
  • 得出结果
    • 执行getWindow().superDispatchTouchEvent(ev)其实是执行了ViewGroup.dispatchTouchEvent(event)
    • 这样事件就从 Activity 传递到了 ViewGroup

2.2 点击事件调用顺序

  • 当一个点击事件发生时,调用顺序以下
    • 1.事件最早传到Activity的dispatchTouchEvent()进行事件分发
    • 2.调用Window类实现类PhoneWindow的superDispatchTouchEvent()
    • 3.调用DecorView的superDispatchTouchEvent()
    • 4.最终调用DecorView父类的dispatchTouchEvent(),即ViewGroup的dispatchTouchEvent()

2.3 得出结论

  • 当一个点击事件发生时,事件最早传到Activity的dispatchTouchEvent()进行事件分发,最终是调用了ViewGroup的dispatchTouchEvent()方法
  • 这样事件就从 Activity 传递到了 ViewGroup

03.ViewGroup事件的分发机制

3.1 看一下这个案例

  • 布局以下:
  • 结果测试
    • 只点击Button,发现执行顺序:btn1,btn2
    • 再点击空白处,发现执行顺序:btn1,btn2,viewGroup
  • 从上面的测试结果发现:
    • 当点击Button时,执行Button的onClick(),但ViewGroupLayout注册的onTouch()不会执行
    • 只有点击空白区域时才会执行ViewGroupLayout的onTouch();
    • 结论:Button的onClick()将事件消费掉了,所以事件不会再继续向下传递。

3.2 源码分析

  • ViewGroup的dispatchTouchEvent()源码分析,该方法比较复杂,截取几个重要的逻辑片断进行介绍,来解析整个分发流程。
    // 发生ACTION_DOWN事件或者已经发生过ACTION_DOWN,而且将mFirstTouchTarget赋值,才进入此区域,主要功能是拦截器 final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) { //disallowIntercept:是否禁用事件拦截的功能(默认是false),即不由用 //能够在子View经过调用requestDisallowInterceptTouchEvent方法对这个值进行修改,不让该View拦截事件 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; //默认状况下会进入该方法 if (!disallowIntercept) { //调用拦截方法 intercepted = onInterceptTouchEvent(ev); ev.setAction(action); } else { intercepted = false; } } else { // 当没有触摸targets,且不是down事件时,开始持续拦截触摸。 intercepted = true; } 
    • 这一段的内容主要是为判断是否拦截。若是当前事件的MotionEvent.ACTION_DOWN,则进入判断,调用ViewGroup onInterceptTouchEvent()方法的值,判断是否拦截。若是mFirstTouchTarget != null,即已经发生过MotionEvent.ACTION_DOWN,而且该事件已经有ViewGroup的子View进行处理了,那么也进入判断,调用ViewGroup onInterceptTouchEvent()方法的值,判断是否拦截。若是不是以上两种状况,即已是MOVE或UP事件了,而且以前的事件没有对象进行处理,则设置成true,开始拦截接下来的全部事件。这也就解释了若是子View的onTouchEvent()方法返回false,那么接下来的一些列事件都不会交给他处理。若是VieGroup的onInterceptTouchEvent()第一次执行为true,则mFirstTouchTarget = null,则也会使得接下来不会调用onInterceptTouchEvent(),直接将拦截设置为true。
  • 当ViewGroup不拦截事件的时候,事件会向下分发交由它的子View或ViewGroup进行处理。
    /* 从最底层的父视图开始遍历, ** 找寻newTouchTarget,即上面的mFirstTouchTarget ** 若是已经存在找寻newTouchTarget,说明正在接收触摸事件,则跳出循环。 */ for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex); // 若是当前视图没法获取用户焦点,则跳过本次循环 if (childWithAccessibilityFocus != null) { if (childWithAccessibilityFocus != child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } //若是view不可见,或者触摸的坐标点不在view的范围内,则跳过本次循环 if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } newTouchTarget = getTouchTarget(child); // 已经开始接收触摸事件,并退出整个循环。 if (newTouchTarget != null) { newTouchTarget.pointerIdBits |= idBitsToAssign; break; } //重置取消或抬起标志位 //若是触摸位置在child的区域内,则把事件分发给子View或ViewGroup if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // 获取TouchDown的时间点 mLastTouchDownTime = ev.getDownTime(); // 获取TouchDown的Index if (preorderedList != null) { for (int j = 0; j < childrenCount; j++) { if (children[childIndex] == mChildren[j]) { mLastTouchDownIndex = j; break; } } } else { mLastTouchDownIndex = childIndex; } //获取TouchDown的x,y坐标 mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); //添加TouchTarget,则mFirstTouchTarget != nullnewTouchTarget = addTouchTarget(child, idBitsToAssign); //表示以及分发给NewTouchTarget alreadyDispatchedToNewTouchTarget = true; break; } 
    • dispatchTransformedTouchEvent()方法实际就是调用子元素的dispatchTouchEvent()方法。
    • 其中dispatchTransformedTouchEvent()方法的重要逻辑以下:
    if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } 
    • 因为其中传递的child不为空,因此就会调用子元素的dispatchTouchEvent()。若是子元素的dispatchTouchEvent()方法返回true,那么mFirstTouchTarget就会被赋值,同时跳出for循环。
    //添加TouchTarget,则mFirstTouchTarget != nullnewTouchTarget = addTouchTarget(child, idBitsToAssign); //表示以及分发给NewTouchTarget alreadyDispatchedToNewTouchTarget = true; 
    • 其中在addTouchTarget(child, idBitsToAssign);内部完成mFirstTouchTarget被赋值。若是mFirstTouchTarget为空,将会让ViewGroup默认拦截全部操做。若是遍历全部子View或ViewGroup,都没有消费事件。ViewGroup会本身处理事件。

3.3 得出结论

  • Android事件分发是先传递到ViewGroup,再由ViewGroup传递到View
  • 在ViewGroup中经过onInterceptTouchEvent()对事件传递进行拦截
    • 1.onInterceptTouchEvent方法返回true表明拦截事件,即不容许事件继续向子View传递;
    • 2.返回false表明不拦截事件,即容许事件继续向子View传递;(默认返回false)
    • 3.子View中若是将传递的事件消费掉,ViewGroup中将没法接收到任何事件。

04.View事件的分发机制

4.1 源码分析

  • View中dispatchTouchEvent()的源码分析
    public boolean dispatchTouchEvent(MotionEvent event) { if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { return true; } return onTouchEvent(event); } 
  • 从上面能够看出:
    • 只有如下三个条件都为真,dispatchTouchEvent()才返回true;不然执行onTouchEvent(event)方法
    第一个条件:mOnTouchListener != null第二个条件:(mViewFlags & ENABLED_MASK) == ENABLED第三个条件:mOnTouchListener.onTouch(this, event)
  • 下面,咱们来看看下这三个判断条件:
    • 第一个条件:mOnTouchListener!= null
    //mOnTouchListener是在View类下setOnTouchListener方法里赋值的 public void setOnTouchListener(OnTouchListener l) { //即只要咱们给控件注册了Touch事件,mOnTouchListener就必定被赋值(不为空) mOnTouchListener = l; } 
    • 第二个条件:(mViewFlags & ENABLED_MASK) == ENABLED
    • 该条件是判断当前点击的控件是否enable
    • 因为不少View默认是enable的,所以该条件恒定为true
    • 第三个条件:mOnTouchListener.onTouch(this, event)
    • 回调控件注册Touch事件时的onTouch方法
    //手动调用设置 button.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return false; } }); 
    • 若是在onTouch方法返回true,就会让上述三个条件所有成立,从而整个方法直接返回true。
    • 若是在onTouch方法里返回false,就会去执行onTouchEvent(event)方法。
  • 接下来,咱们继续看:**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; } } //若是该控件是能够点击的就会进入到下两行的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; // 在通过种种判断以后,会执行到关注点1的performClick()方法。 //请往下看关注点1 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)) { //关注点1 //请往下看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; } 
  • 关注点1:
    • performClick()的源码分析
    public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); if (mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); mOnClickListener.onClick(this); return true; } return false; } 
    • 只要mOnClickListener不为null,就会去调用onClick方法;
  • 那么,mOnClickListener又是在哪里赋值的呢?请继续看:
    public void setOnClickListener(OnClickListener l) { if (!isClickable()) { setClickable(true); } mOnClickListener = l; } 
    • 当咱们经过调用setOnClickListener方法来给控件注册一个点击事件时,就会给mOnClickListener赋值(不为空),即会回调onClick()。

4.2 得出结论

  • 1.onTouch()的执行高于onClick()
  • 2.每当控件被点击时:
    • 若是在回调onTouch()里返回false,就会让dispatchTouchEvent方法返回false,那么就会执行onTouchEvent();若是回调了setOnClickListener()来给控件注册点击事件的话,最后会在performClick()方法里回调onClick()。
      • onTouch()返回false(该事件没被onTouch()消费掉) = 执行onTouchEvent() = 执行OnClick()
    • 若是在回调onTouch()里返回true,就会让dispatchTouchEvent方法返回true,那么将不会执行onTouchEvent(),即onClick()也不会执行;
      • onTouch()返回true(该事件被onTouch()消费掉) = dispatchTouchEvent()返回true(不会再继续向下传递) = 不会执行onTouchEvent() = 不会执行OnClick()

4.3 验证结论

  • 在回调onTouch()里返回true
    TextView textView = findViewById(R.id.tv_13);
    //设置OnTouchListener() textView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { Log.d("小杨逗比","执行了onTouch(), 动做是:" + event.getAction()); return true; } }); //设置OnClickListener textView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.d("小杨逗比","执行了onClick()"); } }); 
    • 打印日志以下所示
    • 注意action为0是ACTION_DOWN,为2是ACTION_MOVE,为1是ACTION_UP。
    2019-04-04 13:37:58.301 13616-13616/org.yczbj.ycrefreshview D/小杨逗比: 执行了onTouch(), 动做是:0 2019-04-04 13:37:58.315 13616-13616/org.yczbj.ycrefreshview D/小杨逗比: 执行了onTouch(), 动做是:2 2019-04-04 13:37:58.405 13616-13616/org.yczbj.ycrefreshview D/小杨逗比: 执行了onTouch(), 动做是:2 2019-04-04 13:37:58.408 13616-13616/org.yczbj.ycrefreshview D/小杨逗比: 执行了onTouch(), 动做是:1 
  • 在回调onTouch()里返回false
    • 打印结果以下所示
    2019-04-04 13:41:26.961 14006-14006/org.yczbj.ycrefreshview D/小杨逗比: 执行了onTouch(), 动做是:0 2019-04-04 13:41:26.978 14006-14006/org.yczbj.ycrefreshview D/小杨逗比: 执行了onTouch(), 动做是:2 2019-04-04 13:41:27.072 14006-14006/org.yczbj.ycrefreshview D/小杨逗比: 执行了onTouch(), 动做是:2 2019-04-04 13:41:27.074 14006-14006/org.yczbj.ycrefreshview D/小杨逗比: 执行了onTouch(), 动做是:1 2019-04-04 13:41:27.076 14006-14006/org.yczbj.ycrefreshview D/小杨逗比: 执行了onClick() 
  • 总结:onTouch()返回true就认为该事件被onTouch()消费掉,于是不会再继续向下传递,即不会执行OnClick()。

05.思考一下

5.1 onTouch()和onTouchEvent()的区别

  • 这两个方法都是在View的dispatchTouchEvent中调用,但onTouch优先于onTouchEvent执行。
  • 若是在onTouch方法中返回true将事件消费掉,onTouchEvent()将不会再执行。
  • 特别注意:请看下面代码
    //&&为短路与,即若是前面条件为false,将再也不往下执行 //因此,onTouch可以获得执行须要两个前提条件: //1. mOnTouchListener的值不能为空 //2. 当前点击的控件必须是enable的。 mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event) 
  • 所以若是你有一个控件是非enable的,那么给它注册onTouch事件将永远得不到执行。对于这一类控件,若是咱们想要监听它的touch事件,就必须经过在该控件中重写onTouchEvent方法来实现。

5.2 Touch事件的后续事件(MOVE、UP)层级传递

  • 若是给控件注册了Touch事件,每次点击都会触发一系列action事件(ACTION_DOWN,ACTION_MOVE,ACTION_UP等)
  • 当dispatchTouchEvent在进行事件分发的时候,只有前一个事件(如ACTION_DOWN)返回true,才会收到后一个事件(ACTION_MOVE和ACTION_UP)
    • 即若是在执行ACTION_DOWN时返回false,后面一系列的ACTION_MOVE和ACTION_UP事件都不会执行
  • 从上面对事件分发机制分析知:
    • dispatchTouchEvent()和 onTouchEvent()消费事件、终结事件传递(返回true)
    • 而onInterceptTouchEvent 并不能消费事件,它至关因而一个分叉口起到分流导流的做用,对后续的ACTION_MOVE和ACTION_UP事件接收起到很是大的做用
    • 请记住:接收了ACTION_DOWN事件的函数不必定能收到后续事件(ACTION_MOVE、ACTION_UP)
  • 这里给出ACTION_MOVE和ACTION_UP事件的传递结论
    • 若是在某个对象(Activity、ViewGroup、View)的dispatchTouchEvent()消费事件(返回true),那么收到ACTION_DOWN的函数也能收到ACTION_MOVE和ACTION_UP
    • 若是在某个对象(Activity、ViewGroup、View)的onTouchEvent()消费事件(返回true),那么ACTION_MOVE和ACTION_UP的事件从上往下传到这个View后就再也不往下传递了,而直接传给本身的onTouchEvent()并结束本次事件传递过程。

其余介绍

01.关于博客汇总连接

02.关于个人博客

相关文章
相关标签/搜索