关于android touch事件源代码分析

本文深入探讨了Android中Touch事件的处理流程,从mView.dispatchTouchEvent()出发,详细解析了事件如何流转到ViewRoot,再到ViewGroup及Activity的dispatchTouchEvent()方法。文中还特别关注了ACTION_DOWN事件的处理细节及其对后续事件的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

   本篇博客描述我阅读android源代码的了解,在此做一个笔记.

   关于android的touch事件,也就是触摸屏事件,最早的调用在mView.dispatchTouchEvent(),该函数是在ViewRoot调用的,关于为何事件处理转到ViewRoot来,有如下过程,android 所有的键盘,触摸事件的处理都需要最终查问Window类,因为Window类记录了窗体的信息,我们能够根据这个查找到需要响应该事件的View,android对此异步处理的,通过Handler将此消息发给相应的View,而这个任务由ViewRoot来完成,ViewRoot本身就是一个Handler对象,并且每一个窗体都有一个ViewRoot对象,以便异步处理消息,因为对消息的处理有多个不同的线程协同完成。

   上面说道mView.dispathTouchEvent(),那么mView是一个什么样的对象呢?根据柯元旦老师的<android内核剖析>,mView分两种情况:对于应用窗口而言,mView是一个PhoneWindow中的DecorView类型,对于非应用窗口而言,mView是一般的ViewGroup类型。关于DecorView类型,他是所有应用窗口的view hierarchy的root,也就是view 树的根,它继承FrameLayout。在非应用窗口,mView.dispatchTouchEvent()就是等同于ViewGroup.dispatchTouchEvent()。对于DecorView类型的调用我们着重探讨.

   DecorView.dispathTouchEvent(),首先判断是否存在Callback对象,Callback 对象其实就是Activity累。如果没有Callback对象,(else)直接调用DecorView基类ViewGroup的dispatchOnTouchEvent()。一般都是存在CallBack对象的,我们可以看Activity的dispatchOnTouchEvent().

 

//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 c//all this implementation for touch screen events that should be handled normally.
//Parameters:
//ev The touch screen event.
//Returns:
//boolean Return true if this event was consumed.
2081
2082    public boolean dispatchTouchEvent(MotionEvent ev) {
2083        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
2084            onUserInteraction();
2085        }
2086        if (getWindow().superDispatchTouchEvent(ev)) {
2087            return true;
2088        }
2089        return onTouchEvent(ev);
2090    }

这是Activity 的实现方法,但是我们可以重载这个方法,所以我们可以将touch事件全权交由Activity处理,不传递给任何的View,看代码2082开始,如果是
    ACTION_DOWN事件,有一个Callback onUserInteraction(),告诉我们页面开始和用户交互了(该方法是没有返回值的),所以第2086行,Activity将事件交给Window(getWindow()返回对象)处理,可以去看Window类的方法superDispatchTouchEvent,可以看到最终是调用到ViewGroup的dispatchOnTouchEvent().所以从这里看出,有没有CallBack都是会调用ViewGroup的dispatchOnTouchEvent(),但是Activity给我一个统一管理触摸事件的机会,我们可以重载Activit 的dispatchTouchEvent方法,自己处理触摸事件。
    okay,以上说道ViewGroup的dispatchTouchEvent方法,看android源代码。
 @Override
781     public boolean dispatchTouchEvent(MotionEvent ev) {
782         final int action = ev.getAction();
783         final float xf = ev.getX();
784         final float yf = ev.getY();
785         final float scrolledXFloat = xf + mScrollX;
786         final float scrolledYFloat = yf + mScrollY;
787         final Rect frame = mTempRect;
788 
789         boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
790 
791         if (action == MotionEvent.ACTION_DOWN) {
792             if (mMotionTarget != null) {
793                 // this is weird, we got a pen down, but we thought it was
794                 // already down!
795                 // XXX: We should probably send an ACTION_UP to the current
796                 // target.
797                 mMotionTarget = null;
798             }
799             // If we're disallowing intercept or if we're allowing and we didn't
800             // intercept
801             if (disallowIntercept || !onInterceptTouchEvent(ev)) {
802                 // reset this event's action (just to protect ourselves)
803                 ev.setAction(MotionEvent.ACTION_DOWN);
804                 // We know we want to dispatch the event down, find a child
805                 // who can handle it, start with the front-most child.
806                 final int scrolledXInt = (int) scrolledXFloat;
807                 final int scrolledYInt = (int) scrolledYFloat;
808                 final View[] children = mChildren;
809                 final int count = mChildrenCount;
810                 for (int i = count - 1; i >= 0; i--) {
811                     final View child = children[i];
812                     if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
813                             || child.getAnimation() != null) {
814                         child.getHitRect(frame);
815                         if (frame.contains(scrolledXInt, scrolledYInt)) {
816                             // offset the event to the view's coordinate system
817                             final float xc = scrolledXFloat - child.mLeft;
818                             final float yc = scrolledYFloat - child.mTop;
819                             ev.setLocation(xc, yc);
820                             if (child.dispatchTouchEvent(ev))  {
821                                 // Event handled, we have a target now.
822                                 mMotionTarget = child;
823                                 return true;
824                             }
825                             // The event didn't get handled, try the next view.
826                             // Don't reset the event's location, it's not
827                             // necessary here.
828                         }
829                     }
830                 }
831             }
832         }
833 
834         boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
835                 (action == MotionEvent.ACTION_CANCEL);
836 
837         if (isUpOrCancel) {
838             // Note, we've already copied the previous state to our local
839             // variable, so this takes effect on the next event
840             mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
841         }
842 
843         // The event wasn't an ACTION_DOWN, dispatch it to our target if
844         // we have one.
845         final View target = mMotionTarget;
846         if (target == null) {
847             // We don't have a target, this means we're handling the
848             // event as a regular view.
849             ev.setLocation(xf, yf);
850             return super.dispatchTouchEvent(ev);
851         }
852 
853         // if have a target, see if we're allowed to and want to intercept its
854         // events
855         if (!disallowIntercept && onInterceptTouchEvent(ev)) {
856             final float xc = scrolledXFloat - (float) target.mLeft;
857             final float yc = scrolledYFloat - (float) target.mTop;
858             ev.setAction(MotionEvent.ACTION_CANCEL);
859             ev.setLocation(xc, yc);
860             if (!target.dispatchTouchEvent(ev)) {
861                 // target didn't handle ACTION_CANCEL. not much we can do
862                 // but they should have.
863             }
864             // clear the target
865             mMotionTarget = null;
866             // Don't dispatch this event to our own view, because we already
867             // saw it when intercepting; we just want to give the following
868             // event to the normal onTouchEvent().
869             return true;
870         }
871 
872         if (isUpOrCancel) {
873             mMotionTarget = null;
874         }
875 
876         // finally offset the event to the target's coordinate system and
877         // dispatch the event.
878         final float xc = scrolledXFloat - (float) target.mLeft;
879         final float yc = scrolledYFloat - (float) target.mTop;
880         ev.setLocation(xc, yc);
881 
882         return target.dispatchTouchEvent(ev);
883     }
该方法的内部操作分几部:
   1.将布局坐标转换为视图坐标,意思是将视图当前的坐标转成视图显示内容的坐标。这个不具体分析了,有兴趣的可以查看相关内容.
   2.当前事件是否是MotionEvent.ACTION_DOWN,代码在791处开始,如果是需要查处到底是那个子view接收该事件。详细见810行开始,如果找到了相应的子视图,则调用子视图的dispatchTouchEvent,那这里需要注意了,如果该子视图是ViewGroup类型,那么会递归调用相应的view的dispatchOnTouch(),直到该view不是ViewGroup对象,因为regular view(非ViewGroup)对象的dispatchOnTouch会调用onTouchEvent做最终的返回值,关于这些不同,下面还会有讲述.见820行如果child.dispatchTouch()返回true,有一个重要的变量赋值了:
  mMotionTarget = child;return true;
 并且返回了true,ACTION_DOWN事件被处理完了。但是在ACTION_DOWN中并没有返回true呢?那么mMotionTarget==null,看846行,那么该ViewGroup就以regular view的方式处理该事件。关键是850处  return super.dispatchTouchEvent(ev);super?那就是View类,那我们看View的dispatchOnTouchEvent,看看跟ViewGroup有什么不同.代码:
3703
3704    public boolean dispatchTouchEvent(MotionEvent event) {
3705        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
3706                mOnTouchListener.onTouch(this, event)) {
3707            return true;
3708        }
3709        return onTouchEvent(event);
3710    }
注意这里java的多态性,如果我们在ViewGroup中设置了(setOnTouchListener()),并且该ViewGroup enabled,我们先调用listener 的onTouch,如果onTouch返回true,该方法直接返回true,但是不是我们需要调用onTouchEvent()(注意Java的多态性,我们可以重载onTouchEvent).所以我们setOnTouchListener 是先于onTouchEvent调用的(一般情形,因为有其他条件).但是我们需要思考什么情况下我们会调用到这一步?是mMotionTarget==null的空,那什么情况下会等于空?第一是我们在ACTION_DOWN中child并没有处理ACTION_DOWN,那么还有另外一种状况看855,在mMotionTarget不等于null的情形下,意思是ACTION_DOWN事件得到处理,而后我们处理其他的事件,比如ACTION_MOVE,ACTION_UP,当前ViewGroup可以截断该事件(但是在ACTION_DOWN中并没有截断该事件,不然mMotionTarget==null),如果发生截断的情形下,在865行,mMotionTarget = null;然后直接返回true,并没有调用super.dispathOnTouchEvent(),意思是截断了事件在onInterceptTouchEvent中。最普遍的情况下是ACTION_DOWN事件得到处理,并且不被VIewGroup截断,那么 return target.dispatchTouchEvent(ev);交由child对剩余的事件处理.



综上,可以归纳android对于ViewGroup对dispatchTouchEvent的处理:架设有A,B,C,D四个View,A是B的parent,B是C的parent,C是D的parent,D是纯View,不是ViewGroup对象。
第一ACTION_DOWN事件:
    1.A截取了事件,那么A中的mMotionTarget==null,return A的super.dispatchTouchEvent(ev);若A中有OnTouchListener并且onTouch返回true,A的dispatchTouchEvent直接返回true,并不掉用A的onTouchEvent,若无监听器,或者onTouch返回false,则需要调用onTouchEvent().
    2.A未截取该事件,但是A的child.dispatchTouchEvent返回false,情况同1.
    3.A未截取该事件,A的child.dispatchTouchEvent返回true,则ACTION_DOWN事件处理完毕.
第二ACTION_MOVE事件:
      1.若ACTION_DOWN事件处理方式是1,2,处理同ACTION_DOWN1的处理.
      2.若ACTION_DOWN事件处理方式是3.mMotionTarget==B,
        2.1.1若A截取该事件mMotionTarget==null;return true。
        2.1.2若A不截取,return B.dispatchTarget.
第三ACTION_UP事件:
       1.ACTION_MOVE同1,则同ACTION_DOWN1.
       2.ACTION_MOVE同2.1.1,则同ACTION_DOWN1
       3.ACTION_MOVE同2.1.2同ACTION_MOVE2
因此当ACTION_DOWN被目标视图正确的处理(dispatchTouchEvent返回true),后续事件才有可能会传递到目标视图,因为在中途可以被view tree截取掉,这样子也是不会传递到目标视图的.若目标视图(在处理ACTION_DOWN)没有返回true或者在ACTION_DOWN的情形下截断,则当前的mMotionTarget==null,则事件交由view的dispatcheTouchEvent处理。如果截断的是其他事件则当前ViewGroup直接返回true,事件一旦被截断再也不能传递到子视图(只可能传递ACTION_CANCEL,详情看代码).









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值