全面图解android事件分发机制

在这里插入图片描述
最近的学习技巧就是画图、画图、画图!感觉这方法真的好实用。

本文源码部分的分析是分模块去分析的,最后串联起来的。

API版本28
源码地址:Android os 在线源码

目录

在这里插入图片描述

事件分发的Touch事件

  1. android触摸事件的分发 = (点击事件)Touch事件的分发
  2. Touch事件被封装MotionEvent对象,定义的有上图所示的4中事件类型
  3. 组成: 一个Touch事件由一个down事件 + 一个up事件也有可能为0个 + 若干个move事件也可为0个
触摸反馈

大致分三种情况:

  1. ACTION_DOWN => ACTION_UP
  2. ACTION_DOWN => ACTION_MOVE => ACTION_MOVE => ..... ACTION_UP
  3. ACTION_DOWN => ACTION_MOVE => ACTION_MOVE => ..... ACTION_CANCEL
    第三种是一种非人为的,导致ACTION_CANCEL
代码上如何实现
  1. 用户触摸屏幕会触发调用View.onTouchEvent(MotionEvent event)

  2. 产生的一些事件的信息:ACTION_DOWN、ACTION_UP、ACTION_MOVE、坐标等信息,都会作为参数传入MotionEvent对象中。

  3. 一系列事件不断产生时,也会不断调用onTouchEvent,onTouchEvent就可以做出不同事件的判断。从而形成触摸反馈!

例:
一个按钮按下就产生ACTION_DOWN事件(按钮的颜色也会发生改变)、抬起产生ACTION_UP(按钮颜色恢复)、滑动产生ACTION_MOVE
一般按下的时候会产生轻微move ,这可以忽略不计的。

分发流程的结构模型

一个Touch事件的产生,分发的结构模型是:树形结构
如图:
在这里插入图片描述

  1. 当Touch事件到根节点时即:1(ViewGroup)时,会遍历它包含的子view,调用子View的dispatchTouchEvent进行事件的分发
  2. 如果子 view为ViewGroup会调用ViewGroup的dispatchTouchEvent方法,继续重复上述1步骤

dispatchTouchEvent函数只负责事件的分发。
如果某一个View消费了事件,那么其他的view就不会消费了,后续的其他move、up事件也都会直接传入,由该view处理。

核心类 & 函数 组成

可以分三大块进分析:

  1. Activity的dispatchTouchEvent事件分发
  2. ViewGroup的dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent事件分发相关函数
  3. View的dispatchTouchEvent、onTouchEvent
Activity的dispatchTouchEvent

判断Touch事件由谁来消费
在这里插入图片描述
源码:
请先熟悉上图:

<--- Analyze 1 --->
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
        // 分析请看:<--- Analyze 2、 Analyze 3 --->
        // getWindow().superDispatchTouchEvent(ev) 返回true 
        //Activity 的dispatchTouchEvent返回true 分发事件结束
            return true;
        }
        //getWindow().superDispatchTouchEvent(ev) 返回false
        // 调用 Activity的onTouchEvent
        // onTouchEvent 分析请看:<--- Analyze 4 --->
        return onTouchEvent(ev);
    }
    
<--- Analyze 2 PhoneWindow.java --->
//mDecor = DecorView顶层View,间接继承ViewGroup
   @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

 <---  Analyze 3  DecorView.java --->
 // DecorView顶层View,间接继承ViewGroup,
 // 故:super.dispatchTouchEvent(event);就是调用ViewGroup的dispatchTouchEvent
     public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }
<---  Analyze 4 Activity onTouchEvent --->
// 消费事件未被Activity下的任何view消费
   public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
        // 分析请看   <--- Analyze 5 --->
            finish();
            return true;
        }

        return false;
    }
   <--- Analyze 5 Window.java --->
    public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
    // 触摸事件在边界外返回true 否则false返回
        final boolean isOutside =
                event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event)
                || event.getAction() == MotionEvent.ACTION_OUTSIDE;
        if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
            return true;
        }
        return false;
    }

ViewGroup的事件分发机制

dispatchTouchEvent方法代码执行流程

可根据此流程去过一遍dispatchTouchEvent方法的源码:
此图是核心流程一个大致的流程图,并不是很细,只做引导作用,算是自己的一个看源码的流程。
下图的绘制是按照代码的并列等级情况去绘制的,不是按照代码、思维逻辑去绘制的,为了方便看源码时对照此图!
等级区分:如图
在这里插入图片描述
有兴趣的可以跟着源码看下:
在这里插入图片描述

dispatchTouchEvent源码分析

源码的注释对应上图的一个流程:

版本是28的,源码实在有点多,这里会省略一些代码

  1. 事件链是指一种责任链模式
  2. 整个事件分发机制也是一种责任链模式
    如果想了解责任链模式请移步: 设计模式:责任链模式+思维导图+uml

源码相关注释:

 @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
       // .... 省略N行代码
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;
            // step 2  如果是新ACTION_DOWN事件,需要对这次DWON事件之前的事件进行重置
            //   1.取消、清空所有触摸事件    2. 重置所有触摸状态    
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            // step 3 检查是否拦截  如果intercepted=false,就触犯Activity的onTouchEvent
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                   //step 3  拦截 返回true
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }
			// .... 省略N行代码
			//step 4 没有取消、没有拦截情况
            if (!canceled && !intercepted) {
            // .... 省略N行代码
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                   // .... 省略N行代码
                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                     // .... 省略N行代码
                        final View[] children = mChildren;
                        // step 5 遍历找到消费事件的子View
                        for (int i = childrenCount - 1; i >= 0; i--) {
                           // .....省略N行代码
                            // step 6 判断子View是否对事件响应
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
  								// .....省略N行代码
                                // step 7  将消费的子view添加进事件链
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                           // .....省略N行代码       
                        }
                          // .....省略N行代码  
                    }
                    // .....省略N行代码  
                }
            }

            // step  8  判断事件链是否为null
            if (mFirstTouchTarget == null) {
               //如果事件没有拦截,子View的消费事件会被添加到事件链中,
               //mFirstTouchTarget == null 则表明所有的子View没有消费事件 或者触摸点不在任何子view内  或者 当前view将事件拦截了
                 // step 9
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
             	// 这里当确定down事件后,触发move事件时,就直接找到这里了
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {//step 10遍历事件链找到处理事件的子view
                   //  .....省略N行代码  
                }
            }
    //  .....省略N行代码  
        return handled;
    }

**注:step 10当一个down事件确定后,后续的move事件触发时,就直接找到了这一步,不会在跟前面逻辑一样重新遍历一遍。(TouchTarget的设计时一种责任链模式

dispatchTransformedTouchEvent 主要负责确定子View是否消费事件

  private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        final int oldAction = event.getAction();
        // 取消事件
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
               // 调用 父类的dispatchTouchEvent   即:View的dispatchTouchEvent
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
		// .... 省略N行代码
        final MotionEvent transformedEvent;
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                   // 调用 父类的dispatchTouchEvent   即:View的dispatchTouchEvent
                    handled = super.dispatchTouchEvent(event);
                } else {
                   // .... 省略N行代码
					// 调用子View的dispatchTouchEvent
                    handled = child.dispatchTouchEvent(event);
                  // .... 省略N行代码
                }
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            transformedEvent = event.split(newPointerIdBits);
        }

        // Perform any necessary transformations and dispatch.
        if (child == null) {
        // 调用 父类的dispatchTouchEvent   即:View的dispatchTouchEvent
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
           // .... 省略N行代码
          // 调用子View的dispatchTouchEvent
            handled = child.dispatchTouchEvent(transformedEvent);
        }
        // .... 省略N行代码
        return handled;
    }
流程总结

根据上面流程、源码的分析大体流程可以总结如下:
在这里插入图片描述

View的事件分发

dispatchTouchEvent
 public boolean dispatchTouchEvent(MotionEvent event) {
          // .....省略N行代码
        boolean result = false;
       // .....省略N行代码
		//step 1. onFilterTouchEventForSecurity 过滤触摸事件:响应触摸事件返回 true 否则false
        if (onFilterTouchEventForSecurity(event)) {
        //handleScrollBarDragging 滚动条是否消费了事件,消费则result = true
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
           // step 2 设置OnTouchListener监听且onTouch返回true ,不在调用 onTouchEvent
           //  
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViexwFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
			// step 3   进入onTouchEvent
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
         // .....省略N行代码 
        return result;
    }

onTouchEvent
    public boolean onTouchEvent(MotionEvent event) {
        // ....省略N行代码
        //若该控件可点击,则进入switch判断中
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
            // 手指抬起
                case MotionEvent.ACTION_UP:
                    // ...省略N行
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) 
                            removeLongPressCallback();

                            if (!focusTaken) {
                               if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                //performClickInternal 内部调用了performClick()
                                    performClickInternal();
                                }
                            }
                        }
                    // ...省略N行
                        
                    break;
            //  按下
                case MotionEvent.ACTION_DOWN:
                    // ...省略N行   
                    break;
				// 非人为取消事件
                case MotionEvent.ACTION_CANCEL:
                    // .... 省略N行
                    break;
	           // 滑动事件
                case MotionEvent.ACTION_MOVE:
                    // .... 省略N行
                    break;
            }
 // 若该控件可以点击,则返回true
            return true;
        }
 // 若该控件可以不可点击,则返回false
        return false;
    }

performClick
public boolean performClick() {
     // .... 省略N行
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        // 只要调用OnClickListener设置监听,就可以给控件注册点击监听  返回true
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }
    // .... 省略N行
        return result;
    }
流程总结

在这里插入图片描述

总结

下图是核心函数的流程图,对应Activity、ViewGroup、View

  1. Activity
    在这里插入图片描述

  2. ViewGroup
    在这里插入图片描述

  3. View

在这里插入图片描述

Activity、ViewGroup、View 分发总结

在这里插入图片描述
到这里事件分发已经完成核心的一半了!
上图只是分析了Activity、ViewGroup、和View分发的大致流程!

下面细节分析子View消费事件 & 不消费事件,具体是怎么传递的!

请回到ViewGroup的源码分析部分:step 6 、 step 7、step 8、 step 9

  • step 6 、step 7执行,说明有子View消费了事件,即 true = dispatchTransformedTouchEvent()
//step 6 通过dispatchTransformedTouchEvent找到消费事件的View
 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // step 7 添加事件链
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }


private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        // mFirstTouchTarget 赋值
        mFirstTouchTarget = target;
        return target;
    }

  • step 8、step 9执行,则说明 子 view没有消费事件,即 false =dispatchTransformedTouchEvent()

    证明上述:
    在执行step 7 时,调用addTouchTarget方法,这里进行了mFirstTouchTarget的赋值。而满足执行step 7的条件就是子 view消费了事件

如源码中那样,当一个子view消费或不消费,都是以一个boolean值向上传递,再根据此boolean值,确定当前dispatchTouchEvent的返回值的。

如图:
请看 紫色线 绿色线
在这里插入图片描述
这里它是利用递归模式。我们常说事件分发机制是一个"U型结构",应该说是一个伪U型结构

完整事件分发流程图:

在这里插入图片描述

事件分发 MOVE事件

在ViewGroup的dispatchTouchEvent方法中step 4中代码:

if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {  }

这里判断了down事件,所以move事件来临时,直接触发的step 10

到这里事件分发机制就结束了!
以上是我个人对事件分发机制的一种理解,如有不对的地方请指出,会及时更正!

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
#前言 之前笔者其实已经写过事件分发机制的文章:[快速理解android事件传递拦截机制概念](http://blog.csdn.net/double2hao/article/details/51541061) 但是,现在看来其实更像是一篇知识概括,多出可能未讲清楚,于是打算重写事件分发,用一篇文章大致讲清楚。 首先,形式上笔者最先思考的是使用源码,此者能从原理上讲解分发机制,比起侃侃而谈好得多。但是源码的复杂往往会让新手产生畏惧难以理解,于是笔者最终还是打算使用实例log来让读者理解android事件分发。 #重要函数 笔者此次主要提及最常用的几个函数: (其间区别看源码很容易理解,此处直接给上结果) **onClick():**这个函数是是View提供给我们的OnClickListener这个接口中的函数,在这里可以自定义对点击事件的处理逻辑。会在onTouchEvent()中进行调用。 **onTouch():**这个函数是View提供给我们的OnTouchListener这个接口中的函数,在这里面可以自定义对触摸事件的处理逻辑。 **onTouchEvent():**这个函数是view内部的触摸事件的处理方式,其间包括获取焦点,调用onClick()等等。 **dispatchTouchEvent():**这个是View的事件分发函数,在ViewGroup中进行重写。在View中其间会调用onTouchEvent(),在ViewGroup中其间会调用onInterceptTouchEvent()和onTouchEvent()。 **onInterceptTouchEvent():**这个函数是事件拦截函数,是ViewGroup才有的函数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值