Android点击事件分发流程

首先对Android点击事件分发流程进行一个大概的总结,然后再贴出伪代码进行分析。

一、当一个点击事件发生时,调用顺序如下:

1.事件最先传到Activity的dispatchTouchEvent()进行事件分发;
2.调用Window类实现类PhoneWindow的superDispatchTouchEvent();
3.调用DecorView的superDispatchTouchEvent();
4.最终调用DecorView父类的dispatchTouchEvent(),即ViewGroup的dispatchTouchEvent();
5.ViewGroup的dispatchTouchEvent()根据情况调用View的dispatchTouchEvent;
6.如果DecorView的dispatchTouchEvent()返回true就不执行Activity的onTouchEvent()方法;如果返回false,就执行Activity的onTouchEvent()方法。
所以,先从Acitivity的dispatchTouchEvent()方法看起,就能把整个流程串联起来。
二、ViewGroup的onInterceptTouchEvent方法,该方法一旦返回一次true,以后就再也不会被调用了。该事件列的其他事件(Move、Up)将直接传递给该ViewGroup的onTouchEvent()方法。

三、前提:ViewGroup包含一个View,而且假设ViewGroup没有拦截DOWN事件,View 处理了DOWN事件(即DOWN事件传递到View的onTouchEvent方法,返回了true),但ViewGroup半路拦截了接下来的MOVE事件。

那么:在这后续到来的第一个MOVE事件中,ViewGroup的onInterceptTouchEvent方法返回true拦截该MOVE事件,但该事件并没有传递给ViewGroup的onTouchEvent方法;这个MOVE事件将会被系统变成一个CANCEL事件传递给View的onTouchEvent方法。后续又来了一个或几个MOVE或者UP事件,(不会再传递给ViewGroup的onInterceptTouchEvent方法),该MOVE、UP事件才会直接传递给ViewGroup的onTouchEvent()进行处理,View再也不会收到该事件列产生的后续事件。

所以自定义控件最好考虑一下CANCEL事件,可能与UP处理逻辑一致,不然滑动嵌套复杂时可能有隐藏的bug。

四、参考文章:
1.http://www.jianshu.com/p/38015afcdb58

2.http://blog.csdn.net/guolin_blog/article/details/9097463

 

下面进入伪代码分析阶段,请注意参考上面第(一)条总结的流程:

一、Activity

public class Activity {
    /**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     *
     * @param ev The touch screen event.
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
    /**
     * Called whenever a key, touch, or trackball event is dispatched to the
     * activity.  Implement this method if you wish to know that the user has
     * interacted with the device in some way while your activity is running.
     * This callback and {@link #onUserLeaveHint} are intended to help
     * activities manage status bar notifications intelligently; specifically,
     * for helping activities determine the proper time to cancel a notfication.
     *
     * <p>All calls to your activity's {@link #onUserLeaveHint} callback will
     * be accompanied by calls to {@link #onUserInteraction}.  This
     * ensures that your activity will be told of relevant user activity such
     * as pulling down the notification pane and touching an item there.
     *
     * <p>Note that this callback will be invoked for the touch down action
     * that begins a touch gesture, but may not be invoked for the touch-moved
     * and touch-up actions that follow.
     *
     * @see #onUserLeaveHint()
     */
    //该方法为空方法
    //从注释得知:当此activity在栈顶时,触屏点击按home,back,menu键等都会触发此方法
    //所以onUserInteraction()主要用于屏保
    public void onUserInteraction() {
    }
    /**
     * Called when a touch screen event was not handled by any of the views
     * under it.  This is most useful to process touch events that happen
     * outside of your window bounds, where there is no view to receive it.
     *
     * @param event The touch screen event being processed.
     *
     * @return Return true if you have consumed the event, false if you haven't.
     * The default implementation always returns false.
     */
    public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }
        return false;
    }
}

二、PhoneWindow 

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

三、DecorView 

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

四、ViewGroup

/**
 *5.0代码有变更,但是基本思路差不多,本代码是4.x的
 */
public class ViewGroup extends View {
    //重写了view的dispatchTouchEvent()方法
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.action == MotionEvent.ACTION_DOWN) {
            if (mMotionTarget != null) {
                mMotionTarget = null;
            }
            //disallowIntercept默认是false,受子view控制,代表是否启用拦截onInterceptTouchEvent(),

            //(android开发艺术探索中提出的内部拦截法用到了此标志位)
            //onInterceptTouchEvent(ev)默认实现是false,就是不拦截事件,取反,进入for循环,事件分发给子view
            if (disallowIntercept || !onInterceptTouchEvent(ev)) {
                for (View child : childrenViews) {
                    //如果点击事件位于该子view区域
                    if (child.contains(ev.getX(), ev.getY())) {
                        if (child.dispatchTouchEvent(ev)) {
                            mMotionTarget = child;
                            return true;
                        }
                    }
                }
            }
        }
        //如果没有子view消耗事件,或者点击了没有子view的空白处
        //就会走到这里,从而通过super.dispatchTouchEvent(ev)调用到View类的onTouchEvent(ev)方法

        //所以,如果重写onInterceptTouchEvent(ev),决定某条件下拦截点击事件,那么就要重写View的onTouchEvent(ev)方法进行

        //点击事件的相关操作。ViewGroup类本身没有onTouchEvent(ev)方法,是继承自View的。
        if (mMotionTarget == null) {
            return super.dispatchTouchEvent(ev);
        }
        return true;
    }

    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;
    }
}
注意:
        .Rect frame=new Rect();
        child.getHitRect(frame);
        frame.contains(x,y);
        可以用如上代码判断某个坐标点、某个点击事件是否在某个控件的区域内。

五、View

public class View {
    public boolean dispatchTouchEvent(MotionEvent event) {
        //一般ENABLED都是true
        //onTouch优先于onTouchEvent执行。如果在onTouch方法中通过返回true将事件消费掉,onTouchEvent将不会再执行。

        //不可用view,即DISENABLED的view是不能响应mOnTouchListener事件的(也不响应onClickListener)
        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
                mOnTouchListener.onTouch(this, event)) {
            return true;
        }

        //如果mOnTouchListener.onTouch()返回false,或者是ENABLED == false,才会走View的onTouchEvent(event)方法,

        //并且View的onClickListener是在onTouchEvent(event)中被执行的。
        return onTouchEvent(event);
    }
    //该onTouchEvent(event)方法是view的默认行为,可以通过重写该方法进行自定义处理逻辑。

    //onClick的调用是在onTouchEvent(event)方法中的
    public boolean onTouchEvent(MotionEvent event) {

 

 

if ((viewFlags & ENABLED_MASK) == DISABLED) { // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. //通过上面英文注释可知,如果该view是 clickable或者longclickble,那么即使该View是DISABLED的,仍然消费事件, //只是消费该事件,并不做具体处理。 //再仔细分析上面的if条件和下面的return逻辑可知: //1.如果view是DISABLED,并且clickable或者longclickble,那么view将消费该事件,只是消费该事件,

            //并不做具体处理,不响应onClickListener(也不响应mOnTouchListener);
            //2.如果view是DISABLED,并且不能clickable或者不能longclickble,那么那么view将不消费该事件,不响应onClickListener(也不响应mOnTouchListener);
            //3.如果view是DISABLED,并且不管能不能clickable或者能不能longclickble,都将不处理点击事件对应的逻辑,
            //  但是分情况决定是否消费该事件,不响应onClickListener()(也不响应mOnTouchListener)。
            return (((viewFlags & CLICKABLE) == CLICKABLE ||  
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));  
     } 

        //如果该view是可clickble或者longclickble,(并且DISABLED == false)才能进入下面的switch判断
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    performClick();
                    break;
                case MotionEvent.ACTION_DOWN:
                     ...
                    break;
                case MotionEvent.ACTION_CANCEL:
                    ....
                    break;
                case MotionEvent.ACTION_MOVE:
                    ...
                    break;
            }
            //clickblez或者longclickble进入switch后,无条件返回true,即消费事件,不管performClick()中返回的是true还是false
            return true;       ---------- 代码第一处
        }
        //如果该view不是clickble或者不是longclickble的,就返回false,就不消费事件
        return false;
    }
    public boolean performClick() {
        if (mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            mOnClickListener.onClick(this);
            return true;
        }
        //如果该view没有设置mOnClickListener,本处返回false,但是该view的onTouchEvent()返回值仍然是true,参考代码第一处
        return false;
    }
    public void setOnClickListener(OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        mOnClickListener = l;
    }
}

 

这样,将上述伪代码从头到尾逐步分析一遍,点击事件的分发流程就基本清楚了。

需要注意的是,该流程是系统view和viewgroup默认的处理方式,总体上dispatchTouchEvent()、onInterceptTouchEvent()、 onTouchEvent()方法的调用流程是不能改变的。但是在具体处理点击事件的逻辑时,可以通过重写ViewGroup的onInterceptTouchEvent()方法,来改变系统默认的事件拦截逻辑,比如决定什么条件下拦截事件,什么条件下不拦截事件;可以通过重写view的onTouchEvent()方法,来改变系统默认的事件相关处理逻辑,比如disable情况下怎么处理,是否响应或者什么条件下响应onTouchListener(或者onClickListner)等等。

ViewGroup的onTouchEvent()方法,在拦截事件自己处理的时候被执行;也可能是因为子view没有处理事件,子view的dispatchTouchEvent()返回false的时候会执行。

理论上dispatchTouchEvent()这个方法也是可以被重写的,因为它是public并且不是final方法,只不过一般不建议重写该方法,因为这样处理起来会很复杂,只根据条件重写ViewGroup的onInterceptTouchEvent()或者onTouchEvent()方法即可。

另外还有两张图片进行总结,图片是从http://www.jianshu.com/p/38015afcdb58网站拷贝出来的,感谢图片原创作者。

好了,android事件分发流程基本讲完了。

--------------------- 本文来自 就是一阵风而已 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/qq1282675628/article/details/78567508?utm_source=copy