Android 事件传递机制 与 消费事件

  • 本文分析从Activity是如何向下分发的,暂不分析事件从InputManagerService如何传递到Activity
  • 下列均属于个人理解 若有不正确欢迎指正
  • 下面分析基于Android11 源码

事件分发中的事件指的是什么

  • 点击事件(Touch事件)即用户点击屏幕所产生的事件
  • 该事件会被封装成MotionEvent对象

事件有哪几种类型

事件类型具体触发动作对应值
MotionEvent.ACTION_DOWN用户按下发生动作0
MotionEvent.ACTION_UP用户抬起时1
MotionEvent.ACTION_MOVE用户滑动屏幕时2
MotionEvent.ACTION_CANCEL事件被中止取消3

事件流指的是什么

  • 上述事件 down(按下) move(滑动) up(抬起) cancel(中止/取消) 组成了事件流
  • 事件从按下开始 中间伴随着N次``滑动事件,以抬起,中止/取消结束

事件分发(传递)又是指什么

  • 将点击事件传递到具体处理事件的view的过程

事件从哪里开始分发的呢

  • 当用户点击屏幕时 由InputManagerService收集事件并交由WindowManagerService分发到Activity处理的
  • 当然我们不在这里分析这个过程主要分析到了Activity之后是如何分发的

1事件分发

1.1分发事件的组件
  • 与Activity 界面构成有关ActivityActivity,PhoneWindow,DecorView,ViewGroup,View组成所以事件应在其中分发
  • 但是PhoneWindow调用的是DecorViewsuperDispatchTouchEvent 也就是调用的ViewGroupDispatchTouchEvent
  • 所以我们一般说的事件分发者 为 Activity,ViewGroup,View 基于分发也从这三个组件分析
  • 下图为事件传递方法调用时序图
    方法调用时序图
1.2 分发涉及到的关键方法
  • 由上图可知 关键方法
ActivityViewGroupView
dispatchTouchEvent
onInterceptTouchEvent没有没有
onTouchEvent没有
  • dispatchTouchEvent

    • 从上面的图种可以看出这是唯一 一个三个组件都存在的方法
    • 该方法用来分发事件
      • 若组件该方法返回true则表示事件已经被消费 事件终止停止分发
        - 这里的消费 若该组件存在子View则事件可能被子View,或自身消费,若不存在子View则被自身消费
      • 若组件该方法返回false则事件,表示该组件没有 消费/处理事件,并将事件处理结果(此时事件处理结果为false)返回给该组件的父级(即调用该组件dispatchTouchEvent方法的组件 )由父级看自身是否处理事件,若不处理仍返回false,并再次向父级的父级传递,若一直不处理则一直传递到Activity这就是事件处理规则
      • 综上所述可得结论
        • 事件传递是由最外层到最内层即Activity-ViewGroup-View
        • 事件处理是从内层开始并逐级返回到外层 若中间不产生事件拦截则从最内层VIew开始处理若产生拦截则从拦截View开始
  • onInterceptTouchEvent

    • 该方法只存在于ViewGroup中 用来拦截事件
    • 该方法返回true表示事件被拦截 要看自身是否处理事件
    • 该方法返回false 表示事件不拦截 ,继续分发
  • onTouchEvent

    • 对于这个只存在于Activity和View中的方法是用来处理事件的
    • 该方法返回true 表示事件被消耗
    • 该方法返回false 表示事件未消耗
  • 下面我们看具体的源码来分析

2 事件在Activity中的分发

    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        	// ->> 1 此处一般用于做屏保
            onUserInteraction();
        }
        // ->> 2 若getWindow().superDispatchTouchEvent(ev)返回true 则直接返回true 
        // 说明事件被处理,事件停止分发 否则返回false  继续向下调用 onTouchEvent
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true; 
        }
        //->> 3 若getWindow().superDispatchTouchEvent(ev)返回false 则执行onTouchEvent
        return onTouchEvent(ev);
    }
  • 从上面代码中可以看出,当事件传递到了Activity后首先调用了getWindow().superDispatchTouchEvent(ev)该方法最终调用的是ViewGroupdispatchTouchEvent
  • 即 事件先由Activity传递到ViewGroupdispatchTouchEvent
    • ViewGroupdispatchTouchEvent返回True
      • 则事件已经被ViewGroup其子View消费 ActivitydispatchTouchEvent直接返回true 即事件消费停止分发
    • ViewGroupdispatchTouchEvent返回False
      • 则事件没有被消费 继续调用ActivityonTouchEvent
      • onTouchEvent返回true 事件已消费ActivitydispatchTouchEvent返回True 事件消费让调用ActivitydispatchTouchEvent的组件知道该事件已经消费
      • onTouchEvent返回False事件未消费ActivitydispatchTouchEvent返回False 事件未消费,让调用ActivitydispatchTouchEvent的组件知道该事件未消费,看否继续处理

3 ViewGroup中的事件分发

  • 上面说到 事件到了Activity 后交给ViewGroup的dispatchTouchEvent处理 下面看着部分源码
public boolean dispatchTouchEvent(MotionEvent ev) {
        	//......
        	boolean handled = false;
            final boolean intercepted;
			//....
				//->> 1 得到 intercepted的值
                  intercepted = onInterceptTouchEvent(ev);
           	//....
           	//->> 2 检测是否拦截
            if (!canceled && !intercepted) {
            //....
                //->> 3 遍历子View 
                for (int i = childrenCount - 1; i >= 0; i--) {
                //...		
                			// ->> 4 将事件分发至子view看子是否处理事件
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)){
                                //...
                                //->> 5 mFirstTouchTarget 赋值
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                //->> 6 若有事件处理则跳出 
                                break;
                            }
                        }
            }
            //....
            //->> 7 看自身是处理事件
            if (mFirstTouchTarget == null) {
                //->> 8 向下分发事件 此时child为null 看自身是否处理事件
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            }else {
            //->> 9
              handled = true;
            }

            //....
        return handled;
    }
    // 事件向下分发 看是否自身处理事件  child==null 表示自身处理 child!=null 表示child处理
	 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
            	//--> 9 调用super的dispatchTouchEvent 也就是View的dispatchTouchEvent
                handled = super.dispatchTouchEvent(event);
            } else {
            //->> 10 调用child的dispatchTouchEvent 若child是View 则调用View的dispatchTouchEvent
            //若child是viewGroup 则调用ViewGroup的dispatchTouchEvent
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
        return handled;
    }
  • 1 处代码onInterceptTouchEvent返回true 拦截事件2 处检测为拦截if判断不会进入则不会执行3,4,5, 6(不会执行遍历 子view ,询问子view 是否处理事件的过程)
    • 此时直接到了7判断mFirstTouchTarget 毫无疑问 遍历过程都没执行(3,4,5,6) 所以该值一定为null此时执行8调用dispatchTransformedTouchEvent 询问自身是否处理事件 其中参数child=null
    • 接着执行9调用super.dispatchTouchEvent(event) ViewGroup继承自View 即调用的是View的dispatchTouchEvent
      • 若该方法返回true 则表示事件已经消费 此时Activity的dispatchTouchEvent返回true 事件消费
      • 若该方法返回false 则表示事件已经未消费 此时Activity执行onTouchEvent
  • 1处代码返回的是false 表示不拦截
    • 2处代码进入if判断 执行3 遍历子view
    • 执行4 调用dispatchTransformedTouchEvent 询问子view是否处理事件 此时child ! = null
    • 此时执行 10调用child.dispatchTouchEvent
      • childViewGroup 则调用ViewGroupdispatchTouchEvent 所以这里有个递归 一直递归到最内层View 到最内层View的时候处理步骤就于child是View的处理步骤相同了
      • childView 则调用ViewdispatchTouchEvent
        • 若返回true 则表示事件被消费 执行 5 mFirstTouchTarget 赋值
          • 执行 6 break 跳出循环
          • 接着执行 7 判断 mFirstTouchTarget 此时该值不为 null 执行9 handled = true; 执行return 该方法返回true事件消费,此时Activity的dispatchTouchEvent返回true 事件消费
        • 若返回false
          • 则不执行 5 mFirstTouchTarget 赋值 6 break 跳出循环
          • 此时直接到了7判断mFirstTouchTarget 遍历过程虽然执行但是赋值 所以该值为null此时执行8调用dispatchTransformedTouchEvent 询问自身是否处理事件 其中参数child=null
          • 接着执行9调用super.dispatchTouchEvent(event) ViewGroup继承自View 即调用的是View的dispatchTouchEvent
            • 若该方法返回true 则表示事件已经消费 此时Activity的dispatchTouchEvent返回true 事件消费
            • 若该方法返回false 则表示事件已经未消费 此时Activity执行onTouchEvent
            • 从步骤7 开始 这里与onInterceptTouchEvent返回true 拦截事件中处理步骤就相同了

4 View中的事件处理

  • 根据上述VIewGroup的事件分发最终是触发了ViewdispatchTouchEvent看下其中部分源码
  • 有意思的来了 这里为什么说的是事件处理 没说分发这个后面会说到
  • 上源码
public boolean dispatchTouchEvent(MotionEvent event) {
      	//...
        if (onFilterTouchEventForSecurity(event)) {
			//...
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    //->> 1 看onTouchlistener是否未空,是否处理 返回true 处理 直接执行最后return 
                    //   返回false 未处理执行 2 onTouchEvent
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
			//->> 2 
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
        return result;
    }
     public boolean onTouchEvent(MotionEvent event) {
  			//....
  			//->> 3 判断是否可点击 若不可点击 直接返回 false 可点击则执行4 处理clickListener
  			// 设置则判断是否up 事件 是则处理click
  		if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                //->> 4  up事件这里处理了ClickListener
                      performClickInternal();
                    break;  
            }
            return true;
        }
        return false;
    }
   private boolean performClickInternal() {
         return performClick();
    }
   public boolean performClick() {
   		// ->> 5 点击事件不为空则返回true 否则返回false
        if (li != null && li.mOnClickListener != null) {
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }
        return result;
    }
  • 根据上述代码 事件传递到view中后我们意外的发现view中只有处理事件的代码并没有继续向下分发
  • 那我们看下事件是如何处理
  • 首先在代码1onTouchlistener是否未null若为null 则向下执行2 onTouchEvent 若不为nullTouchListener是否处理事件
    • 若处理则返回true,标记事件已经消费 不在向下执行,此时ViewGroup的dispatchTouchEvent返回true Activity的dispatchTouchEvent返回true
    • 若不处理则返回false 事件未消费则向下执行2 onTouchEvent此时看代码3 判断view是否可点击
      • 若可点击返回true 则表示事件处理并执行代码4,5 去处理ClickListener,此时ViewGroup的dispatchTouchEvent返回true Activity的dispatchTouchEvent返回true
      • 若不可点击直接返回false 此时ViewGroup的dispatchTouchEvent返回false Activity执行onTouchEvent
  • 接上面问题 我们发现View中其并没有做事件分发只是做了事件处理,ViewGroup则重写了View的dispatchTouchEvent 增加了一个事件拦截,和将事件传递到子View
  • 所以我们理解是 VIewGroup存在事件分发 view 是处理事件

5 总结

  • 根据上述图个人理解
    + 事件传递是由最外层到最内层即Activity-ViewGroup-View
    + 事件处理是从内层开始并逐级返回到外层 若中间不产生事件拦截则从最内层VIew开始处理若产生拦截则从拦截View开始
    + 可以理解成事件分发从外到内事件处理从内到外。。。

6 下图即为整个事件分发处理以及处理返回的过程

事件传递

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值