欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android中onInterceptTouchEvent、dispatchTouchEvent及onTouchEvent的调用顺序及内部原理详解

程序员文章站 2023-04-06 20:46:02
在android中需要经常对用户手势进行判断,在判断手势时需要精细的分清楚每个触摸事件以及每个view对事件的接收情况,在view,viewgroup,activity中都可以接收事件,在对事件进行...

android中需要经常对用户手势进行判断,在判断手势时需要精细的分清楚每个触摸事件以及每个view对事件的接收情况,在view,viewgroup,activity中都可以接收事件,在对事件进行处理时onintercepttouchevent、dispatchtouchevent及ontouchevent这三个函数的调用顺序及关系需要好好理清楚。原理代码有点多,如果不对着具体事例,理解起来很难。下面对着代码进行分析。

mainactivity.java

package com.zqc.dispatchtouchevent;

import android.app.activity;
import android.os.bundle;
import android.util.log;
import android.view.motionevent;
import android.view.view;

import static com.zqc.dispatchtouchevent.constants.tag;

public class mainactivity extends activity implements view.ontouchlistener {
    private myview myview;
    @override
    protected void oncreate(bundle savedinstancestate) {
        super.oncreate(savedinstancestate);
        log.e(tag, "mainactivity oncreate");
        setcontentview(r.layout.activity_main);
        myview = (myview) findviewbyid(r.id.myview);
        myview.setontouchlistener(mainactivity.this);
    }
    @override
    public boolean dispatchtouchevent(motionevent ev) {
        log.e(tag, "mainactivity dispatchtouchevent");
        return super.dispatchtouchevent(ev);
    }
    @override
    public boolean ontouchevent(motionevent event) {
        log.e(tag, "mainactivity ontouchevent");
        switch (event.getaction()) {
            case motionevent.action_down:
                log.e(tag, "mainactivity ontouchevent action_down");
                break;
            case motionevent.action_move:
                log.e(tag, "mainactivity ontouchevent action_move");
                break;
            case motionevent.action_cancel:
                log.e(tag, "mainactivity ontouchevent action_cancel");
                break;
            case motionevent.action_up:
                log.e(tag, "mainactivity ontouchevent action_up");
                break;
            default:
                log.e(tag, "mainactivity ontouchevent " + event.getaction());
                break;
        }
        return super.ontouchevent(event);
    }
    @override
    protected void onresume() {
        log.e(tag, "mainactivity onresume");
        super.onresume();
    }
    @override
    protected void onpause() {
        log.e(tag, "mainactivity onpause");
        super.onpause();
    }
    @override
    public boolean ontouch(view v, motionevent event) {
        log.e(tag, "mainactivity ontouch");
        switch (event.getaction() & motionevent.action_mask) {
            case motionevent.action_down:
                log.e(tag, "mainactivity ontouch action_down");
                break;
            case motionevent.action_move:
                log.e(tag, "mainactivity ontouch action_move");
                break;
            case motionevent.action_cancel:
                log.e(tag, "mainactivity ontouch action_cancel");
                break;
            case motionevent.action_up:
                log.e(tag, "mainactivity ontouch action_up");
                break;
            default:
                log.e(tag, "mainactivity ontouchevent " + event.getaction());
                break;
        }
        return false;
    }
}
myview.java
package com.zqc.dispatchtouchevent;

import android.content.context;
import android.util.attributeset;
import android.util.log;
import android.view.gesturedetector;
import android.view.motionevent;
import android.widget.textview;

import static com.zqc.dispatchtouchevent.constants.my_gesture_tag;
import static com.zqc.dispatchtouchevent.constants.tag;


public class myview extends textview {
    private context mcontext;
    //private gesturedetector mgesture;

    public myview(context context) {
        this(context, null);
    }

    public myview(context context, attributeset attrs) {
        super(context, attrs);
        log.e(tag, "myview");
        mcontext = context;
        //手势初始化
       // mgesture = new gesturedetector(mcontext, mgesturelistener);
    }

    @override
    public boolean ontouchevent(motionevent event) {
        log.e(tag, "myview ontouchevent");
        switch (event.getaction()) {
            case motionevent.action_down:
                log.e(tag, "myview ontouchevent action_down");
                break;
            case motionevent.action_move:
                log.e(tag, "myview ontouchevent action_move");
                break;
            case motionevent.action_cancel:
                log.e(tag, "myview ontouchevent action_cancel");
                break;
            case motionevent.action_up:
                log.e(tag, "myview ontouchevent action_up");
                break;
            default:
                log.e(tag, "myview ontouchevent " + event.getaction());
                break;
        }
//        设置手势监听
       // mgesture.ontouchevent(event);
        return super.ontouchevent(event);
    }

    @override
    public boolean dispatchtouchevent(motionevent event) {
        log.e(tag, "myview dispatchtouchevent");
        return super.dispatchtouchevent(event);
    }
}

myviewgroup.java

package com.zqc.dispatchtouchevent;

import android.content.context;
import android.util.attributeset;
import android.util.log;
import android.view.motionevent;
import android.widget.relativelayout;

import static com.zqc.dispatchtouchevent.constants.tag;

public class myviewgroup extends relativelayout {
    public myviewgroup(context context) {
        this(context, null);
    }

    public myviewgroup(context context, attributeset attrs) {
        super(context, attrs);
        log.e(tag, "myviewgroup");
    }

    @override
    public boolean onintercepttouchevent(motionevent ev) {
        log.e(tag, "myviewgroup onintercepttouchevent");
        return super.onintercepttouchevent(ev);
    }

    @override
    public boolean dispatchtouchevent(motionevent ev) {
        log.e(tag, "myviewgroup dispatchtouchevent");
        return super.dispatchtouchevent(ev);
    }

    @override
    public boolean ontouchevent(motionevent event) {
        log.e(tag, "myviewgroup ontouchevent");
        switch (event.getaction()) {
            case motionevent.action_down:
                log.e(tag, "myviewgroup ontouchevent action_down");
                break;
            case motionevent.action_move:
                log.e(tag, "myviewgroup ontouchevent action_move");
                break;
            case motionevent.action_cancel:
                log.e(tag, "myviewgroup ontouchevent action_cancel");
                break;
            case motionevent.action_up:
                log.e(tag, "myviewgroup ontouchevent action_up");
                break;
            default:
                log.e(tag, "myviewgroup ontouchevent " + event.getaction());
                break;
        }
        return super.ontouchevent(event);
    }
}
contants.java
package com.zqc.dispatchtouchevent;

public class constants {

    public final static string tag = "my_log";
    public final static string my_gesture_tag = "gesture_tag";
}

在代码中将每个函数分别列出并加上log输出,这样对着log日志进行分析,则一目了然。

1.让所有的onintercepttouchevent、dispatchtouchevent及ontouchevent均返回super.ontouchevent即均返回false时,轻轻点击myview然后快速抬起,查看相应的log:

Android中onInterceptTouchEvent、dispatchTouchEvent及onTouchEvent的调用顺序及内部原理详解

通过log能清楚的查看代码执行的流程,具体流程如下:

down事件:mainactivity.dispatchtouchevent->myviewgroup.dispatchtouchevet->myviewgroup.onintercepttouchevent->myview.dispatchtouchevent->setontouchlistener.ontouch->myview.ontouchevent->myviewgroup.ontouchevent->mainactivity.ontouchevent

up事件:mainactivity.dispatchtouchevent->mainactivity.ontouchevent

从上面流程可以看出,点击事件最先传给窗口activity的dispatchtouchevent函数进行事件分发,然后对于view类,是先传给对应的父view的dispatchtouchevent进行事件分发,然后在传给里面点击的view。当down事件没有被各个view消费时,最终会调用acitivity的ontouchevent,并在在down后续的up事件不在传给myviewgroup和myview,直接传给mainacitivity。所以当事件没有被窗口中的view消费时,最终都是给了该窗口activity类中的ontouchevent事件处理。从log中也可以看出setontouchlistener中的ontouch事件是在对应view的ontouchevent事件之前被执行。

2.当mainacivity中dispathtouchevent返回true时,轻轻点击myview,查看对应log:

Android中onInterceptTouchEvent、dispatchTouchEvent及onTouchEvent的调用顺序及内部原理详解

通过log可以看到当窗口activity的dispatchtouchevent返回true时,down事件没有往view中传,也就没有调用任何的ontouchevent事件,up事件也是走到activity的dispatchtouchevent时也就结束了。

3.重新置activity中dispatchtouchevent返回false,然后置viewgroup中onintercepttouchevent返回true时,轻轻点击myview查看对应log:

Android中onInterceptTouchEvent、dispatchTouchEvent及onTouchEvent的调用顺序及内部原理详解

这时down事件和up事件的执行流程如下:

down事件:mainactivity.dipatchtouchevent->myviewgroup.dispatchtouchevent->myviewgroup.onintercepttouchevent->myviewgroup.ontouchevent->mainactivity.ontouchevent.

up事件:mainactiviy.dispatchtouchevent->mainactivity.ontouchevent.

从log中可以看出,当onintercepttouchevent返回true时,事件即被myviewgroup拦截了,这时事件就直接传给myviewgroup.ontouchevent,不在往子view传,由于myviewgroup.ontouchevent返回的是false,即myviewgroup并没有消费事件,这时事件会传给窗口activity,up事件会传给最后一个接受down事件的窗口或view。

4.当myview中ontouchevent返回true时,即myview会消费传给他的事件。轻点myview查看对应的log:

Android中onInterceptTouchEvent、dispatchTouchEvent及onTouchEvent的调用顺序及内部原理详解

继续分析down事件的流程:

down事件:mainactivity.dispatchtouchevent->myviewgroup.dispatchtouchevet->myviewgroup.onintercepttouchevent->myview.dispatchtouchevent->setontouchlistener.ontouch->myview.ontouchevent

up事件:mainactivity.dispatchtouchevent->myviewgroup.dispatchtouchevet->myviewgroup.onintercepttouchevent->myview.dispatchtouchevent->setontouchlistener.ontouch->myview.ontouchevent

从上面的执行流程可以看出当事件被myview消费后,事件不会在往上传,后续的up事件也直接通过dispatchtouchevent分发给对应的view,这里还是提一下,在mainacitivy中设置的setontouchlistener中的ontouch事件是在myview自身的ontouchevent事件之前被执行,因而设置的setontouchevent的ontouch函数还是会被执行。

先只分析这几种场景,move事件和up事件一样只要down事件被某个view消耗了,那么move事件也就直接传到这个view。可以下载代码运行后,在myview上面滑动下看下log,具体log我也贴一份。

情况1:

Android中onInterceptTouchEvent、dispatchTouchEvent及onTouchEvent的调用顺序及内部原理详解

情况2:

Android中onInterceptTouchEvent、dispatchTouchEvent及onTouchEvent的调用顺序及内部原理详解

下面对着android来具体分析view的触摸事件到底是怎么执行的。首先根据log可以最先接收到消息的是activity的dispatchtouchevent,在该处设置断点,然后查看对应的调用方法栈(你会发现在调到mainactivity的dispatchtouchevent时,前面已经调用了很多方法),如下:

Android中onInterceptTouchEvent、dispatchTouchEvent及onTouchEvent的调用顺序及内部原理详解

由于android启动后会先启动zygote进程,该进程会在手机开机后一直运行,android中的几个系统服务都是由zygote进程fork出来的,一个应用在启动时所分配到的进程也是由zygote进程fork出来的,通常说一个应用的起点是application里面的oncreate函数,其实真正的起点是activitythread里面的main函数,看到这个main函数是不是有种熟悉的感觉啊。在main函数中初始化了应用程序的主线程,同时初始化了主线程的消息队列,并调用了looper.loop()函数使主线程不断的对消息队列进行循环检测,有消息则进行处理。点击事件产生一个消息,该消息传到inputeventreceiver后,由inputeventreceiver的继承类windowinputeventreceiver去处理,windowinputeventreceiver类是viewrootimpl类的内部类,查看对应代码如下:

viewrootimpl.java

    final class windowinputeventreceiver extends inputeventreceiver {
        public windowinputeventreceiver(inputchannel inputchannel, looper looper) {
            super(inputchannel, looper);
        }

        @override
        public void oninputevent(inputevent event) {
            enqueueinputevent(event, this, 0, true);
        }

        @override
        public void onbatchedinputeventpending() {
            if (munbufferedinputdispatch) {
                super.onbatchedinputeventpending();
            } else {
                scheduleconsumebatchedinput();
            }
        }

        @override
        public void dispose() {
            unscheduleconsumebatchedinput();
            super.dispose();
        }
    }
查看代码可以当点击消息过来时,直接调用viewrootimpl类中的enqueueinputevent(event,this,0,true)方法:

viewrootimpl.java

    void enqueueinputevent(inputevent event,
            inputeventreceiver receiver, int flags, boolean processimmediately) {
        adjustinputeventforcompatibility(event);
        queuedinputevent q = obtainqueuedinputevent(event, receiver, flags);

        // always enqueue the input event in order, regardless of its time stamp.
        // we do this because the application or the ime may inject key events
        // in response to touch events and we want to ensure that the injected keys
        // are processed in the order they were received and we cannot trust that
        // the time stamp of injected events are monotonic.
        queuedinputevent last = mpendinginputeventtail;
        if (last == null) {
            mpendinginputeventhead = q;
            mpendinginputeventtail = q;
        } else {
            last.mnext = q;
            mpendinginputeventtail = q;
        }
        mpendinginputeventcount += 1;
        trace.tracecounter(trace.trace_tag_input, mpendinginputeventqueuelengthcountername,
                mpendinginputeventcount);

        if (processimmediately) {
            doprocessinputevents();
        } else {
            scheduleprocessinputevents();
        }
    }

由于processimmediately为true,因而是立即处理,即直接调用doprocessinputevents();

viewrootimpl.java

    void doprocessinputevents() {
        // deliver all pending input events in the queue.
        while (mpendinginputeventhead != null) {
            queuedinputevent q = mpendinginputeventhead;
            mpendinginputeventhead = q.mnext;
            if (mpendinginputeventhead == null) {
                mpendinginputeventtail = null;
            }
            q.mnext = null;

            mpendinginputeventcount -= 1;
            trace.tracecounter(trace.trace_tag_input, mpendinginputeventqueuelengthcountername,
                    mpendinginputeventcount);

            long eventtime = q.mevent.geteventtimenano();
            long oldesteventtime = eventtime;
            if (q.mevent instanceof motionevent) {
                motionevent me = (motionevent)q.mevent;
                if (me.gethistorysize() > 0) {
                    oldesteventtime = me.gethistoricaleventtimenano(0);
                }
            }
            mchoreographer.mframeinfo.updateinputeventtime(eventtime, oldesteventtime);

            deliverinputevent(q);
        }

        // we are done processing all input events that we can process right now
        // so we can clear the pending flag immediately.
        if (mprocessinputeventsscheduled) {
            mprocessinputeventsscheduled = false;
            mhandler.removemessages(msg_process_input_events);
        }
    }

z之后调用了deliverinputevent(q)

viewrootimpl.java

    private void deliverinputevent(queuedinputevent q) {
        trace.asynctracebegin(trace.trace_tag_view, "deliverinputevent",
                q.mevent.getsequencenumber());
        if (minputeventconsistencyverifier != null) {
            minputeventconsistencyverifier.oninputevent(q.mevent, 0);
        }

        inputstage stage;
        if (q.shouldsendtosynthesizer()) {
            stage = msyntheticinputstage;
        } else {
            stage = q.shouldskipime() ? mfirstpostimeinputstage : mfirstinputstage;
        }

        if (stage != null) {
            stage.deliver(q);
        } else {
            finishinputevent(q);
        }
    }

在这里初始化了一个inputstage类的实例,然后调用了该类的deliver(q),具体方法如下:

/**
     * base class for implementing a stage in the chain of responsibility
     * for processing input events.
     * 

* events are delivered to the stage by the {@link #deliver} method. the stage * then has the choice of finishing the event or forwarding it to the next stage. *

*/ abstract class inputstage { private final inputstage mnext; protected static final int forward = 0; protected static final int finish_handled = 1; protected static final int finish_not_handled = 2; /** * creates an input stage. * @param next the next stage to which events should be forwarded. */ public inputstage(inputstage next) { mnext = next; } /** * delivers an event to be processed. */ public final void deliver(queuedinputevent q) { if ((q.mflags & queuedinputevent.flag_finished) != 0) { forward(q); } else if (shoulddropinputevent(q)) { finish(q, false); } else { apply(q, onprocess(q)); } } /** * marks the the input event as finished then forwards it to the next stage. */ protected void finish(queuedinputevent q, boolean handled) { q.mflags |= queuedinputevent.flag_finished; if (handled) { q.mflags |= queuedinputevent.flag_finished_handled; } forward(q); } /** * forwards the event to the next stage. */ protected void forward(queuedinputevent q) { ondelivertonext(q); } /** * applies a result code from {@link #onprocess} to the specified event. */ protected void apply(queuedinputevent q, int result) { if (result == forward) { forward(q); } else if (result == finish_handled) { finish(q, true); } else if (result == finish_not_handled) { finish(q, false); } else { throw new illegalargumentexception("invalid result: " + result); } } /** * called when an event is ready to be processed. * @return a result code indicating how the event was handled. */ protected int onprocess(queuedinputevent q) { return forward; } /** * called when an event is being delivered to the next stage. */ protected void ondelivertonext(queuedinputevent q) { if (debug_input_stages) { log.v(tag, "done with " + getclass().getsimplename() + ". " + q); } if (mnext != null) { mnext.deliver(q); } else { finishinputevent(q); } } protected boolean shoulddropinputevent(queuedinputevent q) { if (mview == null || !madded) { slog.w(tag, "dropping event due to root view being removed: " + q.mevent); return true; } else if ((!mattachinfo.mhaswindowfocus && !q.mevent.isfromsource(inputdevice.source_class_pointer)) || mstopped || (mpausedfortransition && !isback(q.mevent))) { // this is a focus event and the window doesn't currently have input focus or // has stopped. this could be an event that came back from the previous stage // but the window has lost focus or stopped in the meantime. if (isterminalinputevent(q.mevent)) { // don't drop terminal input events, however mark them as canceled. q.mevent.cancel(); slog.w(tag, "cancelling event due to no window focus: " + q.mevent); return false; } // drop non-terminal input events. slog.w(tag, "dropping event due to no window focus: " + q.mevent); return true; } return false; } void dump(string prefix, printwriter writer) { if (mnext != null) { mnext.dump(prefix, writer); } } private boolean isback(inputevent event) { if (event instanceof keyevent) { return ((keyevent) event).getkeycode() == keyevent.keycode_back; } else { return false; } } } 对应方法栈可以看出,进过一些列调用最终会调用到viewpostimeinputstage类的processpointerevent方法.

viewrootimpl.java

        private int processpointerevent(queuedinputevent q) {
            final motionevent event = (motionevent)q.mevent;

            mattachinfo.munbuffereddispatchrequested = false;
            boolean handled = mview.dispatchpointerevent(event);
            if (mattachinfo.munbuffereddispatchrequested && !munbufferedinputdispatch) {
                munbufferedinputdispatch = true;
                if (mconsumebatchedinputscheduled) {
                    scheduleconsumebatchedinputimmediately();
                }
            }
            return handled ? finish_handled : forward;
        }

在该方法中调用了mview的dispatchpointerevent,这个mview的初始化可以查看activity的创建代码,在activity创建的时候会给activity设置一个根布局也就是decorview,这里的mview就是decorview,这个decorview是phonewindow的私有内部类,它继承于framelayout并实现了rootviewsurfacetaker接口,但是该方法是view类的一个final方法,子类无法覆写,直接查看view中的相应代码即可。代码如下:

view.java

    /**
     * dispatch a pointer event.
     * 

* dispatches touch related pointer events to {@link #ontouchevent(motionevent)} and all * other events to {@link #ongenericmotionevent(motionevent)}. this separation of concerns * reinforces the invariant that {@link #ontouchevent(motionevent)} is really about touches * and should not be expected to handle other pointing device features. *

* * @param event the motion event to be dispatched. * @return true if the event was handled by the view, false otherwise. * @hide */ public final boolean dispatchpointerevent(motionevent event) { if (event.istouchevent()) { return dispatchtouchevent(event); } else { return dispatchgenericmotionevent(event); } }

继续查看decorview类中的dispatchtouchevent方法,代码如下:

phonewindow.java

        @override
        public boolean dispatchtouchevent(motionevent ev) {
            final callback cb = getcallback();
            return cb != null && !isdestroyed() && mfeatureid < 0 ? cb.dispatchtouchevent(ev)
                    : super.dispatchtouchevent(ev);
        }

这个getcallback也就是当前的activity,当当前activity没有destroy的时候即调用该activity的dispatchtouchevent,这里代码就回到了应用层了,框架层完成了很多操作,这些操作只有查看源码才知道,这里终于回到了我们编写代码的地方了。当然这之后还是会通过框架层将对应的touch事件传给对应的viewgroup和view。下面先看下activity中dispatchtouchevent的代码:

activity.java

    /**
     * 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)) {//这个getwindow就是phonewindow,也就是通过phonewindow继续对touch事件进行分发。
            return true;
        }//当上面返回true,也就是view把事件消费了,那么就不再调用activity的ontouchevent函数了。
        return ontouchevent(ev);
    }

果然这里又回到了框架层,这里getwindow就是phonewindow,继续查看phonewindow的代码:

phonewindow.java

    @override
    public boolean superdispatchtouchevent(motionevent event) {
        return mdecor.superdispatchtouchevent(event);
    }

这里把事件就传给了decorview进行分发。

phonewindow.java->decorview

        public boolean superdispatchtouchevent(motionevent event) {
            return super.dispatchtouchevent(event);
        }

前面说过decorview继承于framelayout,这里super.dispatchtouchevent就是调用了framelayout里面的dispatchtouchevent,而framelayout类中并未重写dispatchtouchevent,因而直接调用的是viewgroup中的dispatchtouchevent。继续查看代码:

viewgroup.java

    /**
     * {@inheritdoc}
     */
    @override
    public boolean dispatchtouchevent(motionevent ev) {
        if (minputeventconsistencyverifier != null) {
            minputeventconsistencyverifier.ontouchevent(ev, 1);
        }

        // if the event targets the accessibility focused view and this is it, start
        // normal event dispatch. maybe a descendant is what will handle the click.
        if (ev.istargetaccessibilityfocus() && isaccessibilityfocusedvieworhost()) {
            ev.settargetaccessibilityfocus(false);
        }

        boolean handled = false;
        if (onfiltertoucheventforsecurity(ev)) {
            final int action = ev.getaction();
            final int actionmasked = action & motionevent.action_mask;

            // handle an initial down.
            if (actionmasked == motionevent.action_down) {
                // throw away all previous state when starting a new touch gesture.
                // the framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, anr, or some other state change.
                cancelandcleartouchtargets(ev);
                resettouchstate();
            }

            // check for interception.
            final boolean intercepted;
            if (actionmasked == motionevent.action_down
                    || mfirsttouchtarget != null) {
                final boolean disallowintercept = (mgroupflags & flag_disallow_intercept) != 0;
                if (!disallowintercept) {
                    intercepted = onintercepttouchevent(ev);
                    ev.setaction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // there are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }

            // if intercepted, start normal event dispatch. also if there is already
            // a view that is handling the gesture, do normal event dispatch.
            if (intercepted || mfirsttouchtarget != null) {
                ev.settargetaccessibilityfocus(false);
            }

            // check for cancelation.
            final boolean canceled = resetcancelnextupflag(this)
                    || actionmasked == motionevent.action_cancel;

            // update list of touch targets for pointer down, if needed.
            final boolean split = (mgroupflags & flag_split_motion_events) != 0;
            touchtarget newtouchtarget = null;
            boolean alreadydispatchedtonewtouchtarget = false;
            if (!canceled && !intercepted) {

                // if the event is targeting accessiiblity focus we give it to the
                // view that has accessibility focus and if it does not handle it
                // we clear the flag and dispatch the event to all children as usual.
                // we are looking up the accessibility focused host to avoid keeping
                // state since these events are very rare.
                view childwithaccessibilityfocus = ev.istargetaccessibilityfocus()
                        ? findchildwithaccessibilityfocus() : null;

                if (actionmasked == motionevent.action_down
                        || (split && actionmasked == motionevent.action_pointer_down)
                        || actionmasked == motionevent.action_hover_move) {
                    final int actionindex = ev.getactionindex(); // always 0 for down
                    final int idbitstoassign = split ? 1 << ev.getpointerid(actionindex)
                            : touchtarget.all_pointer_ids;

                    // clean up earlier touch targets for this pointer id in case they
                    // have become out of sync.
                    removepointersfromtouchtargets(idbitstoassign);

                    final int childrencount = mchildrencount;
                    if (newtouchtarget == null && childrencount != 0) {
                        final float x = ev.getx(actionindex);
                        final float y = ev.gety(actionindex);
                        // find a child that can receive the event.
                        // scan children from front to back.
                        final arraylist preorderedlist = buildorderedchildlist();
                        final boolean customorder = preorderedlist == null
                                && ischildrendrawingorderenabled();
                        final view[] children = mchildren;
                        for (int i = childrencount - 1; i >= 0; i--) {
                            final int childindex = customorder
                                    ? getchilddrawingorder(childrencount, i) : i;
                            final view child = (preorderedlist == null)
                                    ? children[childindex] : preorderedlist.get(childindex);

                            // if there is a view that has accessibility focus we want it
                            // to get the event first and if not handled we will perform a
                            // normal dispatch. we may do a double iteration but this is
                            // safer given the timeframe.
                            if (childwithaccessibilityfocus != null) {
                                if (childwithaccessibilityfocus != child) {
                                    continue;
                                }
                                childwithaccessibilityfocus = null;
                                i = childrencount - 1;
                            }

                            if (!canviewreceivepointerevents(child)
                                    || !istransformedtouchpointinview(x, y, child, null)) {
                                ev.settargetaccessibilityfocus(false);
                                continue;
                            }

                            newtouchtarget = gettouchtarget(child);
                            if (newtouchtarget != null) {
                                // child is already receiving touch within its bounds.
                                // give it the new pointer in addition to the ones it is handling.
                                newtouchtarget.pointeridbits |= idbitstoassign;
                                break;
                            }

                            resetcancelnextupflag(child);
                            if (dispatchtransformedtouchevent(ev, false, child, idbitstoassign)) {
                                // child wants to receive touch within its bounds.
                                mlasttouchdowntime = ev.getdowntime();
                                if (preorderedlist != null) {
                                    // childindex points into presorted list, find original index
                                    for (int j = 0; j < childrencount; j++) {
                                        if (children[childindex] == mchildren[j]) {
                                            mlasttouchdownindex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mlasttouchdownindex = childindex;
                                }
                                mlasttouchdownx = ev.getx();
                                mlasttouchdowny = ev.gety();
                                newtouchtarget = addtouchtarget(child, idbitstoassign);
                                alreadydispatchedtonewtouchtarget = true;
                                break;
                            }

                            // the accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.settargetaccessibilityfocus(false);
                        }
                        if (preorderedlist != null) preorderedlist.clear();
                    }

                    if (newtouchtarget == null && mfirsttouchtarget != null) {
                        // did not find a child to receive the event.
                        // assign the pointer to the least recently added target.
                        newtouchtarget = mfirsttouchtarget;
                        while (newtouchtarget.next != null) {
                            newtouchtarget = newtouchtarget.next;
                        }
                        newtouchtarget.pointeridbits |= idbitstoassign;
                    }
                }
            }

            // dispatch to touch targets.
            if (mfirsttouchtarget == null) {
                // no touch targets so treat this as an ordinary view.
                handled = dispatchtransformedtouchevent(ev, canceled, null,
                        touchtarget.all_pointer_ids);
            } else {
                // dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  cancel touch targets if necessary.
                touchtarget predecessor = null;
                touchtarget target = mfirsttouchtarget;
                while (target != null) {
                    final touchtarget next = target.next;
                    if (alreadydispatchedtonewtouchtarget && target == newtouchtarget) {
                        handled = true;
                    } else {
                        final boolean cancelchild = resetcancelnextupflag(target.child)
                                || intercepted;
                        if (dispatchtransformedtouchevent(ev, cancelchild,
                                target.child, target.pointeridbits)) {
                            handled = true;
                        }
                        if (cancelchild) {
                            if (predecessor == null) {
                                mfirsttouchtarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }

            // update list of touch targets for pointer up or cancel, if needed.
            if (canceled
                    || actionmasked == motionevent.action_up
                    || actionmasked == motionevent.action_hover_move) {
                resettouchstate();
            } else if (split && actionmasked == motionevent.action_pointer_up) {
                final int actionindex = ev.getactionindex();
                final int idbitstoremove = 1 << ev.getpointerid(actionindex);
                removepointersfromtouchtargets(idbitstoremove);
            }
        }

        if (!handled && minputeventconsistencyverifier != null) {
            minputeventconsistencyverifier.onunhandledevent(ev, 1);
        }
        return handled;
    }

代码有点多,通过调试可知将会调用dispatchtransformedtouchevent,查看代码如下:

viewgroup.java

    /**
     * transforms a motion event into the coordinate space of a particular child view,
     * filters out irrelevant pointer ids, and overrides its action if necessary.
     * if child is null, assumes the motionevent will be sent to this viewgroup instead.
     */
    private boolean dispatchtransformedtouchevent(motionevent event, boolean cancel,
            view child, int desiredpointeridbits) {
        final boolean handled;

        // canceling motions is a special case.  we don't need to perform any transformations
        // or filtering.  the important part is the action, not the contents.
        final int oldaction = event.getaction();
        if (cancel || oldaction == motionevent.action_cancel) {
            event.setaction(motionevent.action_cancel);
            if (child == null) {
                handled = super.dispatchtouchevent(event);
            } else {
                handled = child.dispatchtouchevent(event);
            }
            event.setaction(oldaction);
            return handled;
        }

        // calculate the number of pointers to deliver.
        final int oldpointeridbits = event.getpointeridbits();
        final int newpointeridbits = oldpointeridbits & desiredpointeridbits;

        // if for some reason we ended up in an inconsistent state where it looks like we
        // might produce a motion event with no pointers in it, then drop the event.
        if (newpointeridbits == 0) {
            return false;
        }

        // if the number of pointers is the same and we don't need to perform any fancy
        // irreversible transformations, then we can reuse the motion event for this
        // dispatch as long as we are careful to revert any changes we make.
        // otherwise we need to make a copy.
        final motionevent transformedevent;
        if (newpointeridbits == oldpointeridbits) {
            if (child == null || child.hasidentitymatrix()) {
                if (child == null) {
                    handled = super.dispatchtouchevent(event);
                } else {
                    final float offsetx = mscrollx - child.mleft;
                    final float offsety = mscrolly - child.mtop;
                    event.offsetlocation(offsetx, offsety);

                    handled = child.dispatchtouchevent(event);

                    event.offsetlocation(-offsetx, -offsety);
                }
                return handled;
            }
            transformedevent = motionevent.obtain(event);
        } else {
            transformedevent = event.split(newpointeridbits);
        }

        // perform any necessary transformations and dispatch.
        if (child == null) {
            handled = super.dispatchtouchevent(transformedevent);
        } else {
            final float offsetx = mscrollx - child.mleft;
            final float offsety = mscrolly - child.mtop;
            transformedevent.offsetlocation(offsetx, offsety);
            if (! child.hasidentitymatrix()) {
                transformedevent.transform(child.getinversematrix());
            }

            handled = child.dispatchtouchevent(transformedevent);
        }

        // done.
        transformedevent.recycle();
        return handled;
    }

在该函数中调用了child.dispatchtouchevent(),这里便走到了子view的dispatchtouchevent中。子view也就是myview,也就走到了textview的dispathtouchevent中,由于textview并未重写dispathtouchevent,因而直接进入view的dispatchtouchevent中,代码如下:

view.java

    /**
     * pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     *
     * @param event the motion event to be dispatched.
     * @return true if the event was handled by the view, false otherwise.
     */
    public boolean dispatchtouchevent(motionevent event) {
        // if the event should be handled by accessibility focus first.
        if (event.istargetaccessibilityfocus()) {
            // we don't have focus or no virtual descendant has it, do not handle the event.
            if (!isaccessibilityfocusedvieworhost()) {
                return false;
            }
            // we have focus and got the event, then use normal event dispatch.
            event.settargetaccessibilityfocus(false);
        }

        boolean result = false;

        if (minputeventconsistencyverifier != null) {
            minputeventconsistencyverifier.ontouchevent(event, 0);
        }

        final int actionmasked = event.getactionmasked();
        if (actionmasked == motionevent.action_down) {
            // defensive cleanup for new gesture
            stopnestedscroll();
        }

        if (onfiltertoucheventforsecurity(event)) {
            //noinspection simplifiableifstatement
            listenerinfo li = mlistenerinfo;
            if (li != null && li.montouchlistener != null
                    && (mviewflags & enabled_mask) == enabled
                    && li.montouchlistener.ontouch(this, event)) {//在这里就调用了setontouchlistener中的ontouch函数,如果有一个消费了,那么result=true
                result = true;
            }

            if (!result && ontouchevent(event)) {//当上面的result为true时,子view的ontouchevent便不会执行了。
                result = true;
            }
        }

        if (!result && minputeventconsistencyverifier != null) {
            minputeventconsistencyverifier.onunhandledevent(event, 0);
        }

        // clean up after nested scrolls if this is the end of a gesture;
        // also cancel it if we tried an action_down but we didn't want the rest
        // of the gesture.
        if (actionmasked == motionevent.action_up ||
                actionmasked == motionevent.action_cancel ||
                (actionmasked == motionevent.action_down && !result)) {
            stopnestedscroll();
        }

        return result;
    }

在该函数中看到了在mainactivity中设置的setontouchlistener对应的listener接口,当setlistener中的ontouch返回true时,myview本身的ontouchevent便不被调用。接下来看下view的ontouchevent代码:

view.java

public boolean ontouchevent(motionevent event) {
        final float x = event.getx();
        final float y = event.gety();
        final int viewflags = mviewflags;
        final int action = event.getaction();

        if ((viewflags & enabled_mask) == disabled) {
            if (action == motionevent.action_up && (mprivateflags & pflag_pressed) != 0) {
                setpressed(false);
            }
            // 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)
                    || (viewflags & context_clickable) == context_clickable);
        }

        if (mtouchdelegate != null) {//一个view还可以设置touchdelegate,也可以在touchdelegate的ontouchevent里面处理点击事件
            if (mtouchdelegate.ontouchevent(event)) {
                return true;
            }
        }

        if (((viewflags & clickable) == clickable ||
                (viewflags & long_clickable) == long_clickable) ||
                (viewflags & context_clickable) == context_clickable) {
            switch (action) {
                case motionevent.action_up:
                    boolean prepressed = (mprivateflags & pflag_prepressed) != 0;
                    if ((mprivateflags & pflag_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 (prepressed) {
                            // the button is being released before we actually
                            // showed it as pressed.  make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            setpressed(true, x, y);
                       }

                        if (!mhasperformedlongpress && !mignorenextupevent) {
                            // 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();
                                }
                            }
                        }

                        if (munsetpressedstate == null) {
                            munsetpressedstate = new unsetpressedstate();
                        }

                        if (prepressed) {
                            postdelayed(munsetpressedstate,
                                    viewconfiguration.getpressedstateduration());
                        } else if (!post(munsetpressedstate)) {
                            // if the post failed, unpress right now
                            munsetpressedstate.run();
                        }

                        removetapcallback();
                    }
                    mignorenextupevent = false;
                    break;

                case motionevent.action_down:
                    mhasperformedlongpress = false;

                    if (performbuttonactionontouchdown(event)) {
                        break;
                    }

                    // walk up the hierarchy to determine if we're inside a scrolling container.
                    boolean isinscrollingcontainer = isinscrollingcontainer();

                    // for views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    if (isinscrollingcontainer) {
                        mprivateflags |= pflag_prepressed;
                        if (mpendingcheckfortap == null) {
                            mpendingcheckfortap = new checkfortap();
                        }
                        mpendingcheckfortap.x = event.getx();
                        mpendingcheckfortap.y = event.gety();
                        //这个注意下,这里会调用viewrootimpl内部函数也就是后面的move为啥知道前面down了
postdelayed(mpendingcheckfortap, viewconfiguration.gettaptimeout()); } else { // not inside a scrolling container, so show the feedback right away setpressed(true, x, y);
//这个去检查是否有长按事件
checkforlongclick(0); } break; case motionevent.action_cancel: setpressed(false); removetapcallback(); removelongpresscallback(); mincontextbuttonpress = false; mhasperformedlongpress = false; mignorenextupevent = false; break; case motionevent.action_move: drawablehotspotchanged(x, y); // be lenient about moving outside of buttons if (!pointinview(x, y, mtouchslop)) { // outside button removetapcallback(); if ((mprivateflags & pflag_pressed) != 0) { // remove any future long press/tap checks removelongpresscallback(); setpressed(false); } } break; } return true; } return false; }

这里仅分析下down事件的处理,这里会先处理按钮自身的一些事件,具体事件见如下代码:

    /**
     * performs button-related actions during a touch down event.
     *
     * @param event the event.
     * @return true if the down was consumed.
     *
     * @hide
     */
    protected boolean performbuttonactionontouchdown(motionevent event) {
        if (event.gettooltype(0) == motionevent.tool_type_mouse &&
            (event.getbuttonstate() & motionevent.button_secondary) != 0) {
            showcontextmenu(event.getx(), event.gety(), event.getmetastate());
            mprivateflags |= pflag_cancel_next_up_event;
            return true;
        }
        return false;
    }

然后判断当前view的父view是否在滚动,如果不在滚动就调用postdelayed:

view.java

    public boolean postdelayed(runnable action, long delaymillis) {
        final attachinfo attachinfo = mattachinfo;
        if (attachinfo != null) {
            return attachinfo.mhandler.postdelayed(action, delaymillis);
        }
        // assume that post will succeed later
        viewrootimpl.getrunqueue().postdelayed(action, delaymillis);
        return true;
    }

将action延迟一段时间,用于后续判断(是否长按事件,后续move事件,up事件)。