本篇博客描述我阅读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有什么不同.代码:
注意这里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对剩余的事件处理.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 }
综上,可以归纳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,详情看代码).