读Android事件分发机制详解:史上最全面、最易懂文章之后解惑

超级好文:https://www.jianshu.com/p/38015afcdb58

问题一:事件传递的节点代码是啥?即activity如何把事件传递给ViewGrope,ViewGrope如何把事件传递给View?具体代码

1.activity传给ViewGrope:

activity的dispatchTouchEvent()调用getWindow().superDispatchTouchEvent(ev),然后phonewindow的superDispatchTouchEvent调用mDecor.superDispatchTouchEvent(event),decorView的superDispatchTouchEvent调用super.dispatchTouchEvent(event),这个super就是ViewGrope

2.ViewGrop往下传:

如果不拦截,就遍历当前ViewGrop下的所有子View并用frame.contains(scrolledXInt, scrolledYInt)判断当前遍历的view是不是正在点击的view,若是则调用child.dispatchTouchEvent(ev),如果child是ViewGrope则递归如果是View则实现了事件由ViewGrope传递到View的传递

问题二:代码回调的细节?

1.首先View的dispatchTouchEvent如果没有设置 onTouchListener走默认流程会走View的onTouchEvent,

这时会分两种情况:

(1)如果该View消耗了这个事件onTouchEvent会返回true并且回到View的dispatchTouchEvent也返回true,这时回到了ViewGroup的dispatchTouchEvent,关键代码:

  // 条件判断的内部调用了该View的dispatchTouchEvent()
                        // 即 实现了点击事件从ViewGroup到子View的传递(具体请看下面的View事件分发机制)
                        if (child.dispatchTouchEvent(ev))  { 

                        mMotionTarget = child;  
                        return true; 
                        // 调用子View的dispatchTouchEvent后是有返回值的
                        // 若该控件可点击,那么点击时,dispatchTouchEvent的返回值必定是true,因 
                         此会导致条件判断成立
                        // 于是给ViewGroup的dispatchTouchEvent()直接返回了true,即直接跳出
                        // 即把ViewGroup的点击事件拦截掉,所谓的拦截是不走下面的判定代码了,
                        子View消耗掉了点击事件,例如父View和子View同时设置click事件如果点击子 
                        View父View是不会相应的。

                                } 

 

可以看到ViewGroup的dispatchTouchEvent会返回true,然后一直回溯到Phonewindow的superDispatchTouchEvent,然后activity的dispatchTouchEvent也就成了true

 

(2)如果该View没有消耗这个事件onTouchEvent会返回false,这时View的dispatchTouchEvent会返回false,然后到上面代码ViewGrope的child.dispatchTouchEvent(ev)返回false会继续调用下面的代码,下面的代码调用了ViewGroup父类的dispatchtouchevent即:View.dispatchTouchEvent()因此会执行ViewGroup的onTouch()->> onTouchEvent() ->> performClick() ->> onClick(),即自己处理该事件,事件不会往下传递(具体请参考View事件的分发机制中的View.dispatchTouchEvent())
            // 此处需与上面区别:子View的dispatchTouchEvent()

这时会出现两种情况:1.ViewGrope消耗了这个事件,之后就会像上面(1)分析的那样走流程

2.ViewGrope没有消耗会返回false,然后递归如果其中有一个ViewGrope消耗就会重复走上面(2)的流程,如果所有的ViewGrope都没有消耗事件一直返回false,最终phoneWindow的superDispatchTouchEvent(ev)会返回false,然后就会走activity的onTouchEvent。

 

        // 重点分析3
        // 若点击的是空白处(即无任何View接收事件) / 拦截事件(手动复写onInterceptTouchEvent(),从而让其返回true)
        if (target == null) {  
            ev.setLocation(xf, yf);  
            if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
                ev.setAction(MotionEvent.ACTION_CANCEL);  
                mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
            }  
            
            return super.dispatchTouchEvent(ev);
            //如果子View不消耗事件,上面的child.dispatchTouchEvent(ev)返回false,代码会
            // 调用ViewGroup父类的dispatchTouchEvent()如上,即View.dispatchTouchEvent()
            // 因此会执行ViewGroup的onTouch() ->> onTouchEvent() ->> performClick() ->> onClick(),即自己处理该事件,事件不会往下传递(具体请参考View事件的分发机制中的View.dispatchTouchEvent())
            // 此处需与上面区别:子View的dispatchTouchEvent()
        } 

3.在View的dispatchtouchevent中会先判断是否设置了ontouchListener,如果设置了ontouchListener的ontouch方法返回值如果是true就不会执行View的ontouchEvent直接返回true

/**
  * 源码分析:View.dispatchTouchEvent()
  */
  public boolean dispatchTouchEvent(MotionEvent event) {  

        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
                mOnTouchListener.onTouch(this, event)) {  
            return true;  
        } 
        return onTouchEvent(event);  
  }
  // 说明:只有以下3个条件都为真,dispatchTouchEvent()才返回true;否则执行onTouchEvent()
  //     1. mOnTouchListener != null
  //     2. (mViewFlags & ENABLED_MASK) == ENABLED
  //     3. mOnTouchListener.onTouch(this, event)
  // 下面对这3个条件逐个分析


/**
  * 条件1:mOnTouchListener != null
  * 说明:mOnTouchListener变量在View.setOnTouchListener()方法里赋值
  */
  public void setOnTouchListener(OnTouchListener l) { 

    mOnTouchListener = l;  
    // 即只要我们给控件注册了Touch事件,mOnTouchListener就一定被赋值(不为空)
        
} 

/**
  * 条件2:(mViewFlags & ENABLED_MASK) == ENABLED
  * 说明:
  *     a. 该条件是判断当前点击的控件是否enable
  *     b. 由于很多View默认enable,故该条件恒定为true
  */

/**
  * 条件3:mOnTouchListener.onTouch(this, event)
  * 说明:即 回调控件注册Touch事件时的onTouch();需手动复写设置,具体如下(以按钮Button为例)
  */
    button.setOnTouchListener(new OnTouchListener() {  
        @Override  
        public boolean onTouch(View v, MotionEvent event) {  
     
            return false;  
        }  
    });
    // 若在onTouch()返回true,就会让上述三个条件全部成立,从而使得View.dispatchTouchEvent()直接返回true,事件分发结束
    // 若在onTouch()返回false,就会使得上述三个条件不全部成立,从而使得View.dispatchTouchEvent()中跳出If,执行onTouchEvent(event)

 

 

 

 

左侧虚线:具备相关性 & 逐层返回

default1:代码顺序会调用到

getWindow().superDispatchTouchEvent(ev)

default2:我个人没有发现这条线,感觉有点像ViewGrope的dispatchTouchevent的false那条线,这个问题遗留一下

 

4.遇到一个问题:只用手点一下,理论上来说整个事件流程是:actionDown和actionUp,但是在断点跟踪的时候走了actionMove对我的知识体系产生了冲击,后来了解到:

MotionEvent.ACTION_MOVE:当有点在屏幕上移动时触发。值得注意的是,由于它的灵敏度很高,而我们的手指又不可能完全静止(即使我们感觉不到移动,但其实我们的手指也在不停地抖动),所以实际的情况是,基本上只要有点在屏幕上,此事件就会一直不停地被触发。

在以后开发过程中要注意这一点,用touchSlop来判断是否发生了move,不能单用系统是否调用了move事件

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值