事件分发机制

1.Android中事件分发
事件分发顺序:Activity(Window) -> ViewGroup -> View
1.1Activity的dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent ev) {

        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
                    onUserInteraction();
        }
            //getWindow()获取的就是PhoneWindow对象
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
 //该方法为空方法
 //当此activity在栈顶时,触屏点击按home,back,menu键等都会触发此方法
 //所以onUserInteraction()主要用于屏保
 public void onUserInteraction() { 

  }

1.2 PhoneWindow的superDispatchTouchEvent(ev)

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
//mDecor是DecorView的实例
//DecorView是视图的顶层view,继承自FrameLayout,是所有界面的父类
}

1.3 DecorView的superDispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
//DecorView继承自FrameLayout
//那么它的父类就是ViewGroup
而super.dispatchTouchEvent(event)方法,其实就应该是ViewGroup的dispatchTouchEvent()
}

1.4 汇总;
当一个点击事件发生时,调用顺序如下:
- 事件最先传到Activity的dispatchTouchEvent()进行事件分发
- 调用Window类实现类PhoneWindow的superDispatchTouchEvent()
- 调用DecorView的superDispatchTouchEvent()
- 最终调用DecorView父类的dispatchTouchEvent(),即ViewGroup的
dispatchTouchEvent()

1.5 回头看Activity的dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent ev) {

        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

小结:
当一个点击事件发生时,事件最先传到Activity的dispatchTouchEvent()进行事件分发,最终是调用了ViewGroup的dispatchTouchEvent()方法 ,判断ViewGroup的dispatchTouchEvent()返回值是否为true,返回true就不执行Activity的onTouchEvent()方法;返回false,就执行。

2).View事件分发源码分析
2.1 View的dispatchTouchEvent()方法

//mOnTouchListener是在View类下setOnTouchListener方法里赋值的
//mViewFlags & ENABLED_MASK) == ENABLED即当前点击的控件是否enable
//mOnTouchListener的onTouch(this, event)方法的返回值是否为true
public boolean dispatchTouchEvent(MotionEvent event) {  
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
            mOnTouchListener.onTouch(this, event)) {  
        return true;  
    }  
    return onTouchEvent(event);  

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

第一个条件:mOnTouchListener != null;
第二个条件:(mViewFlags & ENABLED_MASK) == ENABLED;
第三个条件:mOnTouchListener.onTouch(this, event);
小结:
- 只有这三个条件都为true,dispatchTouchEvent返回true,不执行onTouchEvent(event),否则执行否则执行onTouchEvent(event)方法。
- onTouch()方法先于onTouchEvent(event)执行

2.2 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;  
        }  
    }  
     //如果该控件enable且可以点击的就会进入到下两行的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;  
                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)) {  
            //请往下看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;  
}  

由上可知

  • 在View为disable情况下,如果view可点击则onTouchEvent()返回true,否则onTouchEvent()返回false。
 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));  
    }  
  • 在View为enable情况下,如果view可点击则onTouchEvent()返回true,如果view不可点击,onTouchEvent()返回false.
public boolean onTouchEvent(MotionEvent event) {  
  if (((viewFlags & CLICKABLE) == CLICKABLE ||  
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { 
        //..........省略
        //如果该控件enable且可以点击的,就一定会返回false
        return true;  
    }  
//如果该控件enable但不可以点击的,就一定会返回false
    return false;  
} 

结合以上2点可知:
只要view可点击则onTouchEvent()一定返回true,view不可点击,onTouchEvent()一定返回false.

2.3 View的点击事件在哪执行?
一次点击事件必不可少的行为MotionEvent.ACTION_DOWN和MotionEvent.ACTION_UP。那么点击事件一定是在MotionEvent.ACTION_UP中执行,看源码:

 switch (event.getAction()) {  
            case MotionEvent.ACTION_UP:  
                boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
                if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  
               //.......省略......
                    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) {  
                            if (mPerformClick == null) {  
                                mPerformClick = new PerformClick();  
                            }  
                            if (!post(mPerformClick)) {  
            //performClick翻译一下,执行点击? 呵呵
                                performClick();  
                            }  
                        }  
                    }  
             //.......省略......
                break;  
                }
             //.......省略......
                }

2.3.1 performClick()源码

public boolean performClick() {  
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  
    //只要mOnClickListener不为null,就会去调用mOnClickListener的onClick方法
    if (mOnClickListener != null) {  
        playSoundEffect(SoundEffectConstants.CLICK);  
        mOnClickListener.onClick(this);  
        return true;  
    }  
    return false;  
}  

public void setOnClickListener(OnClickListener l) {  
    if (!isClickable()) {  
        setClickable(true);  
    }  
    mOnClickListener = l;  
} 

总结:

  • 执行顺序:onTouch()–>onTouchEvent()–>onClick()
  • view为disable时,onTouch()不会执行,onTouchEvent()执行,onClick()不执行
  • 如果view为enable且onTouch()返回true,就会让dispatchTouchEvent方法返回true,那么将不会执行onTouchEvent(),onClick()也不会执行。
  • 如果view为enable且onTouch()返回false,那么就会执行onTouchEvent();执行onClick()。
  • 只要view可点击则onTouchEvent()一定返回true,view不可点击,onTouchEvent()一定返回false.

3.ViewGroup事件分发源码分析
3.1 ViewGroup的dispatchTouchEvent()

public boolean dispatchTouchEvent(MotionEvent ev) {  
    final int action = ev.getAction();  
    final float xf = ev.getX();  
    final float yf = ev.getY();  
    final float scrolledXFloat = xf + mScrollX;  
    final float scrolledYFloat = yf + mScrollY;  
    final Rect frame = mTempRect;  
    boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
    if (action == MotionEvent.ACTION_DOWN) {  
        if (mMotionTarget != null) {  
            mMotionTarget = null;  
        }  
//如果我们在onInterceptTouchEvent()中返回false,就会让第二个值为true,从而进入到条件判断的内部
//如果我们在onInterceptTouchEvent()中返回true,就会让第二个值为false,从而跳出了这个条件判断。
//关于onInterceptTouchEvent()请看下面分析(关注点1)
        if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
            ev.setAction(MotionEvent.ACTION_DOWN);  
            final int scrolledXInt = (int) scrolledXFloat;  
            final int scrolledYInt = (int) scrolledYFloat;  
            final View[] children = mChildren;  
            final int count = mChildrenCount;  

          //通过for循环,遍历了当前ViewGroup下的所有子View
            for (int i = count - 1; i >= 0; i--) {  
                final View child = children[i];  
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
                        || child.getAnimation() != null) {  
                    child.getHitRect(frame);  
                    //判断当前遍历的View是不是正在点击的View
                    //如果是,则进入条件判断内部
                    if (frame.contains(scrolledXInt, scrolledYInt)) {  
                        final float xc = scrolledXFloat - child.mLeft;  
                        final float yc = scrolledYFloat - child.mTop;  
                        ev.setLocation(xc, yc);  
                        child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  

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

        //调用子View的dispatchTouchEvent后是有返回值的
        //如果这个控件是可点击的话,那么点击该控件时,dispatchTouchEvent的返回值必定是true
        //因此会导致条件判断成立
                            mMotionTarget = child;  
        //于是给ViewGroup的dispatchTouchEvent方法直接返回了true,这样就导致后面的代码无法执行,直接跳出
        //即把ViewGroup的touch事件拦截掉
                            return true;  
                        }  
                    }  
                }  
            }  
        }  
    }  
    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  
            (action == MotionEvent.ACTION_CANCEL);  
    if (isUpOrCancel) {  
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
    }  
    final View target = mMotionTarget;  
//没有任何View接收事件的情况,即点击空白处情况
    if (target == null) {  
        ev.setLocation(xf, yf);  
        if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
            ev.setAction(MotionEvent.ACTION_CANCEL);  
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
        }  
//调用ViewGroup的父类View的dispatchTouchEvent()
//因此会执行ViewGroup的onTouch()、onTouchEvent()
//实现了点击事件从ViewGroup到View的传递
        return super.dispatchTouchEvent(ev);  
    }  


//之后的代码在一般情况下是走不到的了,我们也就不再继续往下分析。
    if (!disallowIntercept && onInterceptTouchEvent(ev)) {  
        final float xc = scrolledXFloat - (float) target.mLeft;  
        final float yc = scrolledYFloat - (float) target.mTop;  
        mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
        ev.setAction(MotionEvent.ACTION_CANCEL);  
        ev.setLocation(xc, yc);  
        if (!target.dispatchTouchEvent(ev)) {  
        }  
        mMotionTarget = null;  
        return true;  
    }  
    if (isUpOrCancel) {  
        mMotionTarget = null;  
    }  
    final float xc = scrolledXFloat - (float) target.mLeft;  
    final float yc = scrolledYFloat - (float) target.mTop;  
    ev.setLocation(xc, yc);  
    if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
        ev.setAction(MotionEvent.ACTION_CANCEL);  
        target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
        mMotionTarget = null;  
    }  
    return target.dispatchTouchEvent(ev);  
}  

3.1.1 disallowIntercept
disallowIntercept:是否禁用事件拦截的功能(默认是false),可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改。

3.1.2 onInterceptTouchEvent(ev) 是否拦截事件(默认是false)。

public boolean onInterceptTouchEvent(MotionEvent ev) {  
    return false;  
} 

3.2 MotionEvent.ACTION_DOWN时,做了些什么??

  if (action == MotionEvent.ACTION_DOWN) {  
        if (mMotionTarget != null) {  
            mMotionTarget = null;  
        }  
//onInterceptTouchEvent()返回false或disallowIntercept==true进入if语句
        if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
            ev.setAction(MotionEvent.ACTION_DOWN);  
            final int scrolledXInt = (int) scrolledXFloat;  
            final int scrolledYInt = (int) scrolledYFloat;  
            final View[] children = mChildren;  
            final int count = mChildrenCount;  

          //通过for循环,遍历了当前ViewGroup下的所有子View
            for (int i = count - 1; i >= 0; i--) {  
                final View child = children[i];  
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
                        || child.getAnimation() != null) {  
                    child.getHitRect(frame);  
                    //判断当前遍历的View是不是正在点击的View
                    //如果是,则进入条件判断内部
                    if (frame.contains(scrolledXInt, scrolledYInt)) {  
                        final float xc = scrolledXFloat - child.mLeft;  
                        final float yc = scrolledYFloat - child.mTop;  
                        ev.setLocation(xc, yc);  
                        child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  

          //条件判断的内部调用了该View的dispatchTouchEvent()方法                
          if (child.dispatchTouchEvent(ev))  { 
        //如果这个控件是可点击的话,那么点击该控件时,dispatchTouchEvent的返回值必定是true
        //因此会导致条件判断成立
                            mMotionTarget = child;  
        //于是给ViewGroup的dispatchTouchEvent方法直接返回了true,这样就导致后面的代码无法执行,直接跳出
        //即把ViewGroup的touch事件拦截掉
                            return true;  
                        }  
                    }  
                }  
            }  
        }  
    }  

首先判断(disallowIntercept || !onInterceptTouchEvent(ev))是否为true,true则进入if语句,通过for循环,遍历了当前ViewGroup下的所有子View,查找点击的View,

  • 找到则调用该View的dispatchTouchEvent()方法,返回值为true,则该View消耗了DOWN事件,ViewGroup的dispatchTouchEvent()结束,后续的MOVE等事件都由该View消费。
  • 没找到或找到但View的dispatchTouchEvent()返回值为false,则DOWN事件未被任何View消耗。走流程3.3。

3.3 没有任何View消耗事件的情况或onInterceptTouchEvent()为true

  //DOWN事件未被任何View消耗则mMotionTarget==null;
  final View target = mMotionTarget;  
//没有任何View接收事件的情况,即点击空白处情况
    if (target == null) {  
        ev.setLocation(xf, yf);  
        if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
            ev.setAction(MotionEvent.ACTION_CANCEL);  
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
        }  
//调用ViewGroup的父类View的dispatchTouchEvent()
//因此会执行ViewGroup的onTouch()、onTouchEvent()
//实现了点击事件从ViewGroup到View的传递
        return super.dispatchTouchEvent(ev);  
    }  

由上可知:
没有任何View消耗DOWN事件或onInterceptTouchEvent()为true情况下,target == null,会调用ViewGroup父类View的dispatchTouchEvent(),会执行ViewGroup的onTouch()、onTouchEvent()。

  • super.dispatchTouchEvent(ev)为true,消耗事件。
  • super.dispatchTouchEvent(ev)为false,事件向上传递。

3.4 拦截子View的DOWN事件
onInterceptTouchEvent()设置为true,走流程3.3,如果DOWN事件被ViewGroup消费了,该事件列的其他事件(Move、Up)将直接传递给ViewGroup的onTouch()或onTouchEvent()。

3.5 不拦截子View的DOWN事件,拦截子View的其他事件
子View消费了DOWN事件,在ViewGroup的onInterceptTouchEvent方法返回true拦截该MOVE事件,这个MOVE事件将会被系统变成一个CANCEL事件传递给子View的dispatchTouchEvent()方法,后续又来了一个MOVE事件,该MOVE事件会直接传递给ViewGroup的onTouch()或onTouchEvent().

//子View消费了DOWN事件且onInterceptTouchEvent()==true&&disallowIntercept ==false
  if (!disallowIntercept && onInterceptTouchEvent(ev)) {  
        final float xc = scrolledXFloat - (float) target.mLeft;  
        final float yc = scrolledYFloat - (float) target.mTop;  
        mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
        //设置MotionEvent.ACTION_CANCEL事件
        ev.setAction(MotionEvent.ACTION_CANCEL);  
        ev.setLocation(xc, yc);  
   //调用子view的dispatchTouchEvent()并设置mMotionTarget==null
   //下个事件来,走流程3.3
        if (!target.dispatchTouchEvent(ev)) {  
        }  
        mMotionTarget = null; 
       //MotionEvent.ACTION_CANCEL事件被消费  
        return true;  
    }  

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

  • 当(Activity、ViewGroup、View)的dispatchTouchEvent在进行事件分发的时候,只有前一个事件返回true,才会收到后一个事件。
  • 如果在某个对象(Activity、ViewGroup、View)的dispatchTouchEvent()收到ACTION_DOWN的函数并返回true,那么也能收到ACTION_MOVE和ACTION_UP 。

黑线:ACTION_DOWN事件传递方向
红线:ACTION_MOVE和ACTION_UP事件传递方向

这里写图片描述
- 如果在某个对象(Activity、ViewGroup、View)的onTouchEvent()消费了DOWN事件,那么ACTION_MOVE和ACTION_UP的事件从上往下传到这个View后就不再往下传递了,而直接传给这个View的onTouchEvent()并结束本次事件传递过程。这里写图片描述
参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值