Android事件分发机制

事件分发机制主要是指触摸事件在Activity、ViewGroup、View之间传递并消费的机制,分发顺序为 Activity > ViewGroup > View;

主要方法:
VIewGroup相关:onInterceptTouchEvent()、dispatchTouchEvent()、onTouchEvent()
View相关:dispatchTouchEvent()、onTouchEvent()

1. Activity事件分发机制

一般情况下用户按下Activity时,Activity会执行dispatchTouchEvent方法,开始分发流程,我们看下dispatchTouchEvent()方法源码:

    //activity的事件分发方法
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

dispatchTouchEvent方法里就两个处理:

(1)MotionEvent.ACTION_DOWN事件执行时会调用onUserInteraction()方法;我们看看这个方法做了啥;

    public void onUserInteraction() {
    }

这个方法实现是空的,不过我们可以从注释和其他途径可以了解到,该方法主要的作用是实现屏保功能,并且当此 Activity 在栈顶的时候,触屏点击 Home、Back、Recent 键等都会触发这个方法。

(2)接下来是if()语句执行了getWindow().superDispatchTouchEvent(ev)方法,能看出来他是获取了Window并调用了其superDispatchTouchEvent方法,但你会发现Window是个抽象类,superDispatchTouchEvent是个抽象方法,看不出任何东西,然后通过源码追踪你会发现其实getWindow获取的是Window的子类PhoneWindow;

    final void attach(){
    ...
    mWindow = new PhoneWindow(this, window,     activityConfigCallback);
    ...
    }

所以我们来看看PhoneWindow的superDispatchTouch方法:

    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

直接调用了DecorView的superDispatchTouchEvent方法,DecorView继承自FrameLayout,是顶层View,所有界面的父类,而FrameLayout是ViewGroup的子类,所以最终调用的是ViewGroup的dispatchTouchEvent方法。

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
    ...
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }
    ...
}
2. ViewGroup的事件分发机制

我们来看看ViewGroup的dispatchTouchEvent方法(Android5.0前的方法,5.0之后的方法比较复杂,但原理一样):

public boolean dispatchTouchEvent(MotionEvent ev) { 
    ... // 仅贴出关键代码
        // 重点分析1:ViewGroup每次事件分发时,都需调用onInterceptTouchEvent()询问是否拦截事件
            if (disallowIntercept || !onInterceptTouchEvent(ev)) {  

            // 判断值1:disallowIntercept = 是否禁用事件拦截的功能(默认是false),可通过调用requestDisallowInterceptTouchEvent()修改
            // 判断值2: !onInterceptTouchEvent(ev) = 对onInterceptTouchEvent()返回值取反
                    // a. 若在onInterceptTouchEvent()中返回false(即不拦截事件),就会让第二个值为true,从而进入到条件判断的内部
                    // b. 若在onInterceptTouchEvent()中返回true(即拦截事件),就会让第二个值为false,从而跳出了这个条件判断
                    // c. 关于onInterceptTouchEvent() ->>分析1

                ev.setAction(MotionEvent.ACTION_DOWN);  
                final int scrolledXInt = (int) scrolledXFloat;  
                final int scrolledYInt = (int) scrolledYFloat;  
                final View[] children = mChildren;  
                final int count = mChildrenCount;  

        // 重点分析2
            // 通过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,从而找到当前被点击的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()
                        // 即 实现了点击事件从ViewGroup到子View的传递(具体请看下面的View事件分发机制)
                        if (child.dispatchTouchEvent(ev))  { 

                        mMotionTarget = child;  
                        return true; 
                        // 调用子View的dispatchTouchEvent后是有返回值的
                        // 若该控件可点击,那么点击时,dispatchTouchEvent的返回值必定是true,因此会导致条件判断成立
                        // 于是给ViewGroup的dispatchTouchEvent()直接返回了true,即直接跳出
                        // 即把ViewGroup的点击事件拦截掉

                                }  
                            }  
                        }  
                    }  
                }  
            }  
            boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  
                    (action == MotionEvent.ACTION_CANCEL);  
            if (isUpOrCancel) {  
                mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
            }  
            final View target = mMotionTarget;  

        // 重点分析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);
            // 调用ViewGroup父类的dispatchTouchEvent(),即View.dispatchTouchEvent()
            // 因此会执行ViewGroup的onTouch() ->> onTouchEvent() ->> performClick() ->> onClick(),即自己处理该事件,事件不会往下传递(具体请参考View事件的分发机制中的View.dispatchTouchEvent())
            // 此处需与上面区别:子View的dispatchTouchEvent()
        } 

        ... 
}

(1)ViewGroup分发事件时首先判断当前Viewgroup有没有拦截事件onInterceptTouchEvent(ev),若返回值为true则表明已经拦截了此事件,跳出判断,事件不会往下传递,若是flase则相反,事件会往下传递。
(2)获取到ViewGroup的所有子View及子ViewGroup,通过for循环遍历;先判断次子View是否是正在点击的View(通过位置判断),如果是的话调用此子View的dispatchTouchEvent方法,这个方法是有返回值的,如果返回值为true就表明事件已被消费,跳出整个循环,不再往下传递,事件到此结束。
(3)如果点击的是空白处或事件被拦截的情况下会调用父类的dispatchTouchEvent()方法,即View.dispatchTouchEvent(),自己处理该事件,不会再往下传递。

分发流程:
ViewGroup分发流程
3. View事件的分发机制

View的分发机制也是从dispatchTouchEvent()开始的,我们分析下源码;

public boolean dispatchTouchEvent(MotionEvent event) {  
        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
                mOnTouchListener.onTouch(this, event)) {  
            return true;  
        } 
        return onTouchEvent(event);  
  }

方法里有一个if判断,里面有三个条件,若满足了这三个条件会返回true,即消费了该事件,否则执行onTouchEvent(),我们逐一分析这三个条件;
(1)mOnTouchListener != null,需要再View.setOnTouchListener()里注册Touch事件;
(2)(mViewFlags & ENABLED_MASK) == ENABLED,判断当前View是否enable,很多View默认enable;
(3)mOnTouchListener.onTouch(this, event) 回调以注册的Touch方法:

button.setOnTouchListener(new OnTouchListener() {  
        @Override  
        public boolean onTouch(View v, MotionEvent event) {  
     
            return false;  
        }  
    });

若onTouch返回了true,上述三个条件全部成立,从而使得View.dispatchTouchEvent()直接返回true,事件分发结束
若onTouch返回了false,上述三个条件不成立,执行onTouchEvent(event);

4. View.onTouchEvent()源码分析
public boolean onTouchEvent(MotionEvent event) {  
    final int viewFlags = mViewFlags;  

    if ((viewFlags & ENABLED_MASK) == DISABLED) {  
         
        return (((viewFlags & CLICKABLE) == CLICKABLE ||  
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));  
    }  
    if (mTouchDelegate != null) {  
        if (mTouchDelegate.onTouchEvent(event)) {  
            return true;  
        }  
    }  

    // 若该控件可点击,则进入switch判断中
    if (((viewFlags & CLICKABLE) == CLICKABLE ||  
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  

                switch (event.getAction()) { 

                    // a. 若当前的事件 = 抬起View(主要分析)
                    case MotionEvent.ACTION_UP:  
                        boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  

                            ...// 经过种种判断,此处省略

                            // 执行performClick() ->>分析1
                            performClick();  
                            break;  

                    // b. 若当前的事件 = 按下View
                    case MotionEvent.ACTION_DOWN:  
                        if (mPendingCheckForTap == null) {  
                            mPendingCheckForTap = new CheckForTap();  
                        }  
                        mPrivateFlags |= PREPRESSED;  
                        mHasPerformedLongPress = false;  
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
                        break;  

                    // c. 若当前的事件 = 结束事件(非人为原因)
                    case MotionEvent.ACTION_CANCEL:  
                        mPrivateFlags &= ~PRESSED;  
                        refreshDrawableState();  
                        removeTapCallback();  
                        break;

                    // d. 若当前的事件 = 滑动View
                    case MotionEvent.ACTION_MOVE:  
                        final int x = (int) event.getX();  
                        final int y = (int) event.getY();  
        
                        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;  
        }

/**
  * 分析1:performClick()
  */  
    public boolean performClick() {  

        if (mOnClickListener != null) {  
            playSoundEffect(SoundEffectConstants.CLICK);  
            mOnClickListener.onClick(this);  
            return true;  
            // 只要我们通过setOnClickListener()为控件View注册1个点击事件
            // 那么就会给mOnClickListener变量赋值(即不为空)
            // 则会往下回调onClick() & performClick()返回true
        }  
        return false;  
    }  

如果View是可点击的就返回true,否则返回false。


控件被点击

可以看出onTouch()的执行优先于onClick(),所以一旦在onTouch()消费了事件onClick()就不会执行。

5. 分发流程
分发流程
6. 总结

事件分发从activity的dispatchTouchEvent方法开始往下遍历子View逐个寻找消费控件,如果有控件或布局消费了此事件,会执行这个控件或布局的onTouchEvent()方法,到此事件结束,不会再往下传递,如果没有控件或布局消费此事件,view层级从里到外逐个调用onTouchEvent()方法,直到activity自己消费此事件为止。

你可能感兴趣的:(Android事件分发机制)