Android 事件分发机制源码分析

参考资料:https://blog.csdn.net/lmj623565791/article/details/38960443  鸿洋

        

先说下讲课的内容:

1.什么是事件分发

2.事件分发的流程

3.事件分发的源码分析

4.用户怎么会触发这个分发的流程的

1.事件分发的定义:所谓事件就是指触摸屏幕的按下、滑动、抬起、取消。所以事件分发就是这些动作由谁处理和怎么处理的过程。

2.事件分发的流程:Activity-->ViewGroup--->子View  涉及的方法主要有:dispatchTouchEvent(分发)、onInterceptTouchEvent( 拦截 )、onTouchEvent(消费处理:里面会有对onClick、onLongClick的处理)

扫描二维码关注公众号,回复: 6866104 查看本文章

3.事件分发的源码:通过对setContentView()源码的分析我们知道了Activity是怎么显示我们自己View的,也知道了View其实是一个树形结构。

首先看下Activity的dispatchTouchEvent()方法:

    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//这是一个空的方法,可以自己重写 onUserInteraction(); }
//去PhoneWindow中调用superDispatchTouchEvent()方法 if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
//PhoneWindowsuperDispatchTouchEvent()方法

我们可以看出调用了根节点DecorView的superDispatchTouchEvent()方法,继续跟进去

会发现最终调用了ViewGroup的dispatchTouchEvent()方法。至此我们总结下:调用Activity的dispatchTouchEvent最终会调用ViewGroup的dispatchTouchEvent方法

并且如果ViewGroup的dispatchTouchEvent()方法返回值==true,Activity的dispatchTouchEvent()直接返回true,onTouchEvent()就没有机会执行,表示该事件被子节点消费了。

下面来看ViewGroup的dispatchTouchEvent()----->ACTION_DOWN方法的一些关键代码:

 1  @Override
 2     public boolean dispatchTouchEvent(MotionEvent ev) {
 3         if (!onFilterTouchEventForSecurity(ev)) {
 4             return false;
 5         }
 6  
 7         final int action = ev.getAction();
 8         final float xf = ev.getX();
 9         final float yf = ev.getY();
10         final float scrolledXFloat = xf + mScrollX;
11         final float scrolledYFloat = yf + mScrollY;
12         final Rect frame = mTempRect;
13  
14         boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
15  
16         if (action == MotionEvent.ACTION_DOWN) {
//每次按下的时候都会将mMotionTarget = null;
17 if (mMotionTarget != null) { 18 // this is weird, we got a pen down, but we thought it was 19 // already down! 20 // XXX: We should probably send an ACTION_UP to the current 21 // target. 22 mMotionTarget = null; 23 } 24 // If we're disallowing intercept or if we're allowing and we didn't 25 // 如果不允许拦截或者允许拦截了但是没有拦截就会去遍历查找里面的子View
// 如果子View不允许拦截那么onInterceptTouchEvent()将没有机会得到执行
26 if (disallowIntercept || !onInterceptTouchEvent(ev)) { 27 // reset this event's action (just to protect ourselves) 28 ev.setAction(MotionEvent.ACTION_DOWN); 29 // We know we want to dispatch the event down, find a child 30 // who can handle it, start with the front-most child. 31 final int scrolledXInt = (int) scrolledXFloat; 32 final int scrolledYInt = (int) scrolledYFloat; 33 final View[] children = mChildren; 34 final int count = mChildrenCount; 35 //采用倒序的遍历方式,首先取出来最新通过addView()进来的 36 for (int i = count - 1; i >= 0; i--) { 37 final View child = children[i]; 38 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE 39 || child.getAnimation() != null) { 40 child.getHitRect(frame); 41 if (frame.contains(scrolledXInt, scrolledYInt)) { 42 // offset the event to the view's coordinate system 43 final float xc = scrolledXFloat - child.mLeft; 44 final float yc = scrolledYFloat - child.mTop; 45 ev.setLocation(xc, yc); 46 child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
// 如果找到了按压的子View就调用子View的dispatchTouchEvent()方法,如果返回true,证明该事件被其子view消费了,
// 就把子view对象赋值给mMotionTarget,并且return true;
47 if (child.dispatchTouchEvent(ev)) { 48 // Event handled, we have a target now. 49 mMotionTarget = child; 50 return true; 51 } 52 // The event didn't get handled, try the next view. 53 // Don't reset the event's location, it's not 54 // necessary here. 55 } 56 } 57 } 58 } 59 }

总结下down事件:

首先每次down事件触发时都会将mMotionTarget=null;后面的move和up事件会使用这个值

其次判断子View 有没有要求ViewGroup进行拦截以及ViewGroup有没有进行拦截

最后遍历里面的子View 来决定事件是否分发下去。

下面来看ViewGroup的dispatchTouchEvent()----->ACTION_MOVE方法的一些关键代码:

 1 @Override
 2     public boolean dispatchTouchEvent(MotionEvent ev) {
 3         final int action = ev.getAction();
 4         final float xf = ev.getX();
 5         final float yf = ev.getY();
 6         final float scrolledXFloat = xf + mScrollX;
 7         final float scrolledYFloat = yf + mScrollY;
 8         final Rect frame = mTempRect;
 9  
10         boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
11  
12        //...ACTION_DOWN
13  
14        //...ACTIN_UP or ACTION_CANCEL
15  
16         // The event wasn't an ACTION_DOWN, dispatch it to our target if
17         // we have one.
18         //如果子view消费了down事件,那么此值就不为空
19     final View target = mMotionTarget;
20       
21  
22         // if have a target, see if we're allowed to and want to intercept its
23         // events
24         if (!disallowIntercept && onInterceptTouchEvent(ev)) {
25             //允许拦截且拦截了。注意:如果子View不允许拦截那么onInterceptTouchEvent()方法将不会被执行
26         }
27         // finally offset the event to the target's coordinate system and
28         // dispatch the event.
29         final float xc = scrolledXFloat - (float) target.mLeft;
30         final float yc = scrolledYFloat - (float) target.mTop;
31         ev.setLocation(xc, yc);
32  
33         return target.dispatchTouchEvent(ev);
34     }

可以看到,正常流程下,ACTION_MOVE在检测完是否拦截以后,直接调用了子View.dispatchTouchEvent,事件分发下去

下面来看ViewGroup的dispatchTouchEvent()----->ACTION_UP方法的一些关键代码:

 
 1 public boolean dispatchTouchEvent(MotionEvent ev) {
 2         if (!onFilterTouchEventForSecurity(ev)) {
 3             return false;
 4         }
 5  
 6         final int action = ev.getAction();
 7         final float xf = ev.getX();
 8         final float yf = ev.getY();
 9         final float scrolledXFloat = xf + mScrollX;
10         final float scrolledYFloat = yf + mScrollY;
11         final Rect frame = mTempRect;
12  
13         boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
14  
15         if (action == MotionEvent.ACTION_DOWN) {...}
16     
17     boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
18                 (action == MotionEvent.ACTION_CANCEL);
19  
20     if (isUpOrCancel) {
21             mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
22         }
23     final View target = mMotionTarget;
24     if(target ==null ){...}
25     if (!disallowIntercept && onInterceptTouchEvent(ev)) {...}
26         //up之后重置mMotionTarget
27         if (isUpOrCancel) {
28             mMotionTarget = null;
29         }
30  
31         // finally offset the event to the target's coordinate system and
32         // dispatch the event.
33         final float xc = scrolledXFloat - (float) target.mLeft;
34         final float yc = scrolledYFloat - (float) target.mTop;
35         ev.setLocation(xc, yc);
36  
37         return target.dispatchTouchEvent(ev);
38     }
 

正常情况下,即我们上例整个代码的流程我们已经走完了:

1、ACTION_DOWN中,ViewGroup捕获到事件,然后判断是否拦截,如果没有拦截,则找到包含当前x,y坐标的子View,赋值给mMotionTarget,然后调用    mMotionTarget.dispatchTouchEvent

2、ACTION_MOVE中,ViewGroup捕获到事件,然后判断是否拦截,如果没有拦截,则直接调用mMotionTarget.dispatchTouchEvent(ev)

3、ACTION_UP中,ViewGroup捕获到事件,然后判断是否拦截,如果没有拦截,则直接调用mMotionTarget.dispatchTouchEvent(ev)

上面的总结都是基于:如果没有拦截;那么如何拦截呢?

 1 @Override
 2     public boolean onInterceptTouchEvent(MotionEvent ev)
 3     {
 4         int action = ev.getAction();
 5         switch (action)
 6         {
 7         case MotionEvent.ACTION_DOWN:
 8             //如果你觉得需要拦截
 9             return true ; 
10         case MotionEvent.ACTION_MOVE:
11             //如果你觉得需要拦截
12             return true ; 
13         case MotionEvent.ACTION_UP:
14             //如果你觉得需要拦截
15             return true ; 
16         }
17         
18         return false;

默认是不拦截的,即返回false;如果你需要拦截,只要return true就行了,这要该事件就不会往子View传递了,并且如果你在DOWN retrun true ,则DOWN,MOVE,UP子View都不会捕获事件;如果你在MOVE return true , 则子View在MOVE和UP都不会捕获事件。
因为当onInterceptTouchEvent(ev) return true的时候,会把mMotionTarget 置为null ;

如果没有找到合适的子View

我们的实例,直接点击ViewGroup内的按钮,当然直接很顺利的走完整个流程;

但是有两种特殊情况

1、ACTION_DOWN的时候,子View.dispatchTouchEvent(ev)返回的为false ;

如果你仔细看了,你会注意到ViewGroup的dispatchTouchEvent(ev)的ACTION_DOWN代码是这样的

      if (child.dispatchTouchEvent(ev))  {
                                    // Event handled, we have a target now.
                                    mMotionTarget = child;
                                    return true;
                                }


只有在child.dispatchTouchEvent(ev)返回true了,才会认为找到了能够处理当前事件的View,即mMotionTarget = child;

但是如果返回false,那么mMotionTarget 依然是null

mMotionTarget 为null会咋样呢?

其实ViewGroup也是View的子类,如果没有找到能够处理该事件的子View,或者干脆就没有子View;

那么,它作为一个View,就相当于View的事件转发了~~直接super.dispatchTouchEvent(ev);

源码是这样的:

     final View target = mMotionTarget;
            if (target == null) {
                // We don't have a target, this means we're handling the
                // event as a regular view.
                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~

最后看下view的事件分发机制

      public boolean dispatchTouchEvent(MotionEvent event) {
        if (!onFilterTouchEventForSecurity(event)) {
            return false;
        }
       //设置了onTouch监听并且返回值为true表示在mOnTouchListener里面消费了
        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
                mOnTouchListener.onTouch(this, event)) {
            return true;
        }
        //否则在onTouchEvent里面去处理(实际上就是在onClick和OnLongClick)
        return 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;
            }
        }
        //如果view可以点击或者长按就会消费这个事件(Button)
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            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)) {
//里面会执行onClick方法 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;
//执行一个延迟115ms的任务 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; } return true; } return false; } private final class CheckForTap implements Runnable { public void run() { mPrivateFlags &= ~PREPRESSED; mPrivateFlags |= PRESSED; refreshDrawableState();
//如果这个view 可以长按就发送一个延迟500-115ms的任务
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) { postCheckForLongClick(ViewConfiguration.getTapTimeout()); } } } private void postCheckForLongClick(int delayOffset) { mHasPerformedLongPress = false; if (mPendingCheckForLongPress == null) { mPendingCheckForLongPress = new CheckForLongPress(); } mPendingCheckForLongPress.rememberWindowAttachCount(); postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout() - delayOffset); } class CheckForLongPress implements Runnable { private int mOriginalWindowAttachCount; public void run() { if (isPressed() && (mParent != null) && mOriginalWindowAttachCount == mWindowAttachCount) { if (performLongClick()) { mHasPerformedLongPress = true; } } } }

//down分析

按下的时候 mPrivateFlags 设置为 PREPRESSED,mHasPerformedLongPress = false;并且发送一个115ms的延迟任务,如果115ms内还在view上面
就会执行CheckForTap 任务,然后把mPrivateFlags 设置成PRESSED状态并发送一个延迟500-115ms的任务,如果在500-115ms内还在view上面就会

执行CheckForLongPress 任务,如果子view设置了长按事件就会触发,并且如果返回值==true,那么 mHasPerformedLongPress = true

//move 分析

如果移除了控件范围removeTapCallback(),如果已经发出了长按检测就移除 removeLongPressCallback();

//up

可以看出如果长按没有触发或者出发后返回值==false,才会触发onClick事件


参考资料: https://xiaozhuanlan.com/topic/8946537021

 (内核->frameWork层)
触摸屏幕后硬件设备便会产生硬件终端,Kernel收到硬件终端之后,会对其进行加工,包装成event事件之后添加到/dev/input/目录下
android 会不断的监控/dev/input/目录下的所有设备节点,一旦发现有新的设备节点可读时便会立马读出事件并处理
这里的复杂步骤涉及到frameWork层:
牵扯到;WindowManagerService InputManagerService


   Activity 中实现Window.Callback接口后重写


在DecorView(FragmentLayout的子类)中会调用这个方法
//这个方法是整个事件分发的应用层的入口函数

实际上首先调用的是DecorView的dispatchPointerEvent()方法,因为没有覆写View的dispatchPointerEvent(),所以会调用view的dispatchPointerEvent()方法

因为DecorView覆写了dispatchTouchEvent方法所有就会调用DecorView的dispatchTouchEvent()方法,闭环形成

 

猜你喜欢

转载自www.cnblogs.com/lianzhen/p/11244957.html