Android中onInterceptTouchEvent、dispatchTouchEvent及onTouchEvent的调用顺序及内部原理详解
在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:
通过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:
通过log可以看到当窗口activity的dispatchtouchevent返回true时,down事件没有往view中传,也就没有调用任何的ontouchevent事件,up事件也是走到activity的dispatchtouchevent时也就结束了。
3.重新置activity中dispatchtouchevent返回false,然后置viewgroup中onintercepttouchevent返回true时,轻轻点击myview查看对应log:
这时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:
继续分析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:
情况2:
下面对着android来具体分析view的触摸事件到底是怎么执行的。首先根据log可以最先接收到消息的是activity的dispatchtouchevent,在该处设置断点,然后查看对应的调用方法栈(你会发现在调到mainactivity的dispatchtouchevent时,前面已经调用了很多方法),如下:
由于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事件)。