Andriod 从源码的角度详解View,ViewGroup的Touch事件的分发机制

ViewGroup的事件分发机制html

咱们用手指去触摸Android手机屏幕,就会产生一个触摸事件,可是这个触摸事件在底层是怎么分发的呢?这个我还真不知道,这里涉及到操做硬件(手机屏幕)方面的知识,也就是Linux内核方面的知识,我也没有了解过这方面的东西,因此咱们可能就往上层来分析分析,咱们知道Android中负责与用户交互,与用户操做紧密相关的四大组件之一是Activity, 因此咱们有理由相信Activity中存在分发事件的方法,这个方法就是dispatchTouchEvent(),咱们先看其源码吧
java

[java] view plaincopyandroid

  1. public boolean dispatchTouchEvent(MotionEvent ev) {  windows

  2.   

  3.         //若是是按下状态就调用onUserInteraction()方法,onUserInteraction()方法  ide

  4.         //是个空的方法, 咱们直接跳过这里看下面的实现  工具

  5.         if (ev.getAction() == MotionEvent.ACTION_DOWN) {  布局

  6.             onUserInteraction();  post

  7.         }  动画

  8.           

  9.         if (getWindow().superDispatchTouchEvent(ev)) {  this

  10.             return true;  

  11.         }  

  12.           

  13.         //getWindow().superDispatchTouchEvent(ev)返回false,这个事件就交给Activity  

  14.         //来处理, Activity的onTouchEvent()方法直接返回了false  

  15.         return onTouchEvent(ev);  

  16.     }  

这个方法中咱们仍是比较关心getWindow()的superDispatchTouchEvent()方法,getWindow()返回当前Activity的顶层窗口Window对象,咱们直接看Window API的superDispatchTouchEvent()方法

[java] view plaincopy

  1. /** 

  2.      * Used by custom windows, such as Dialog, to pass the touch screen event 

  3.      * further down the view hierarchy. Application developers should 

  4.      * not need to implement or call this. 

  5.      * 

  6.      */  

  7.     public abstract boolean superDispatchTouchEvent(MotionEvent event);  

这个是个抽象方法,因此咱们直接找到其子类来看看superDispatchTouchEvent()方法的具体逻辑实现,Window的惟一子类是PhoneWindow,咱们就看看PhoneWindow的superDispatchKeyEvent()方法

[java] view plaincopy

  1. public boolean superDispatchKeyEvent(KeyEvent event) {  

  2.         return mDecor.superDispatchKeyEvent(event);  

  3.     }  

里面直接调用DecorView类的superDispatchKeyEvent方法,或许不少人不了解DecorView这个类,DecorView是PhoneWindow的一个final的内部类而且继承FrameLayout的,也是Window界面的最顶层的View对象,这是什么意思呢?别着急,咱们接着往下看

咱们先新建一个项目,取名AndroidTouchEvent,而后直接用模拟器运行项目, MainActivity的布局文件为

[html] view plaincopy

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  

  2.     xmlns:tools="http://schemas.android.com/tools"  

  3.     android:layout_width="match_parent"  

  4.     android:layout_height="match_parent"  

  5.     tools:context=".MainActivity" >  

  6.   

  7.     <TextView  

  8.         android:layout_width="wrap_content"  

  9.         android:layout_height="wrap_content"  

  10.         android:layout_centerHorizontal="true"  

  11.         android:layout_centerVertical="true"  

  12.         android:text="@string/hello_world" />  

  13.   

  14. </RelativeLayout>  

利用hierarchyviewer工具来查看下MainActivity的View的层次结构,以下图


咱们看到最顶层就是PhoneWindow$DecorView,接着DecorView下面有一个LinearLayout, LinearLayout下面有两个FrameLayout

上面那个FrameLayout是用来显示标题栏的,这个Demo中是一个TextView,固然咱们还能够定制咱们的标题栏,利用getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE,R.layout.XXX); xxx就是咱们自定义标题栏的布局XML文件
下面的FrameLayout是用来装载ContentView的,也就是咱们在Activity中利用setContentView()方法设置的View,如今咱们知道了,原来咱们利用setContentView()设置Activity的View的外面还嵌套了这么多的东西

咱们来理清下思路,Activity的最顶层窗体是PhoneWindow,而PhoneWindow的最顶层View是DecorView,接下来咱们就看DecorView类的superDispatchTouchEvent()方法

[java] view plaincopy

  1. public boolean superDispatchTouchEvent(MotionEvent event) {  

  2.             return super.dispatchTouchEvent(event);  

  3.         }  

在里面调用了父类FrameLayout的dispatchTouchEvent()方法,而FrameLayout中并无dispatchTouchEvent()方法,因此咱们直接看ViewGroup的dispatchTouchEvent()方法

[java] view plaincopy

  1. /** 

  2.     * {@inheritDoc} 

  3.     */  

  4.    @Override  

  5.    public boolean dispatchTouchEvent(MotionEvent ev) {  

  6.        final int action = ev.getAction();  

  7.        final float xf = ev.getX();  

  8.        final float yf = ev.getY();  

  9.        final float scrolledXFloat = xf + mScrollX;  

  10.        final float scrolledYFloat = yf + mScrollY;  

  11.        final Rect frame = mTempRect;  

  12.   

  13.        //这个值默认是false, 而后咱们能够经过requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法  

  14.        //来改变disallowIntercept的值  

  15.        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  

  16.   

  17.        //这里是ACTION_DOWN的处理逻辑  

  18.        if (action == MotionEvent.ACTION_DOWN) {  

  19.         //清除mMotionTarget, 每次ACTION_DOWN都很设置mMotionTarget为null  

  20.            if (mMotionTarget != null) {  

  21.                mMotionTarget = null;  

  22.            }  

  23.   

  24.            //disallowIntercept默认是false, 就看ViewGroup的onInterceptTouchEvent()方法  

  25.            if (disallowIntercept || !onInterceptTouchEvent(ev)) {  

  26.                ev.setAction(MotionEvent.ACTION_DOWN);  

  27.                final int scrolledXInt = (int) scrolledXFloat;  

  28.                final int scrolledYInt = (int) scrolledYFloat;  

  29.                final View[] children = mChildren;  

  30.                final int count = mChildrenCount;  

  31.                //遍历其子View  

  32.                for (int i = count - 1; i >= 0; i--) {  

  33.                    final View child = children[i];  

  34.                      

  35.                    //若是该子View是VISIBLE或者该子View正在执行动画, 表示该View才  

  36.                    //能够接受到Touch事件  

  37.                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  

  38.                            || child.getAnimation() != null) {  

  39.                     //获取子View的位置范围  

  40.                        child.getHitRect(frame);  

  41.                          

  42.                        //如Touch到屏幕上的点在该子View上面  

  43.                        if (frame.contains(scrolledXInt, scrolledYInt)) {  

  44.                            // offset the event to the view's coordinate system  

  45.                            final float xc = scrolledXFloat - child.mLeft;  

  46.                            final float yc = scrolledYFloat - child.mTop;  

  47.                            ev.setLocation(xc, yc);  

  48.                            child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  

  49.                              

  50.                            //调用该子View的dispatchTouchEvent()方法  

  51.                            if (child.dispatchTouchEvent(ev))  {  

  52.                                // 若是child.dispatchTouchEvent(ev)返回true表示  

  53.                             //该事件被消费了,设置mMotionTarget为该子View  

  54.                                mMotionTarget = child;  

  55.                                //直接返回true  

  56.                                return true;  

  57.                            }  

  58.                            // The event didn't get handled, try the next view.  

  59.                            // Don't reset the event's location, it's not  

  60.                            // necessary here.  

  61.                        }  

  62.                    }  

  63.                }  

  64.            }  

  65.        }  

  66.   

  67.        //判断是否为ACTION_UP或者ACTION_CANCEL  

  68.        boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  

  69.                (action == MotionEvent.ACTION_CANCEL);  

  70.   

  71.        if (isUpOrCancel) {  

  72.            //若是是ACTION_UP或者ACTION_CANCEL, 将disallowIntercept设置为默认的false  

  73.         //假如咱们调用了requestDisallowInterceptTouchEvent()方法来设置disallowIntercept为true  

  74.         //当咱们抬起手指或者取消Touch事件的时候要将disallowIntercept重置为false  

  75.         //因此说上面的disallowIntercept默认在咱们每次ACTION_DOWN的时候都是false  

  76.            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  

  77.        }  

  78.   

  79.        // The event wasn't an ACTION_DOWN, dispatch it to our target if  

  80.        // we have one.  

  81.        final View target = mMotionTarget;  

  82.        //mMotionTarget为null意味着没有找到消费Touch事件的View, 因此咱们须要调用ViewGroup父类的  

  83.        //dispatchTouchEvent()方法,也就是View的dispatchTouchEvent()方法  

  84.        if (target == null) {  

  85.            // We don't have a target, this means we're handling the  

  86.            // event as a regular view.  

  87.            ev.setLocation(xf, yf);  

  88.            if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  

  89.                ev.setAction(MotionEvent.ACTION_CANCEL);  

  90.                mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  

  91.            }  

  92.            return super.dispatchTouchEvent(ev);  

  93.        }  

  94.   

  95.        //这个if里面的代码ACTION_DOWN不会执行,只有ACTION_MOVE  

  96.        //ACTION_UP才会走到这里, 假如在ACTION_MOVE或者ACTION_UP拦截的  

  97.        //Touch事件, 将ACTION_CANCEL派发给target,而后直接返回true  

  98.        //表示消费了此Touch事件  

  99.        if (!disallowIntercept && onInterceptTouchEvent(ev)) {  

  100.            final float xc = scrolledXFloat - (float) target.mLeft;  

  101.            final float yc = scrolledYFloat - (float) target.mTop;  

  102.            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  

  103.            ev.setAction(MotionEvent.ACTION_CANCEL);  

  104.            ev.setLocation(xc, yc);  

  105.              

  106.            if (!target.dispatchTouchEvent(ev)) {  

  107.            }  

  108.            // clear the target  

  109.            mMotionTarget = null;  

  110.            // Don't dispatch this event to our own view, because we already  

  111.            // saw it when intercepting; we just want to give the following  

  112.            // event to the normal onTouchEvent().  

  113.            return true;  

  114.        }  

  115.   

  116.        if (isUpOrCancel) {  

  117.            mMotionTarget = null;  

  118.        }  

  119.   

  120.        // finally offset the event to the target's coordinate system and  

  121.        // dispatch the event.  

  122.        final float xc = scrolledXFloat - (float) target.mLeft;  

  123.        final float yc = scrolledYFloat - (float) target.mTop;  

  124.        ev.setLocation(xc, yc);  

  125.   

  126.        if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  

  127.            ev.setAction(MotionEvent.ACTION_CANCEL);  

  128.            target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  

  129.            mMotionTarget = null;  

  130.        }  

  131.   

  132.        //若是没有拦截ACTION_MOVE, ACTION_DOWN的话,直接将Touch事件派发给target  

  133.        return target.dispatchTouchEvent(ev);  

  134.    }  

这个方法相对来讲仍是蛮长,不过全部的逻辑都写在一块儿,看起来比较方便,接下来咱们就具体来分析一下


咱们点击屏幕上面的TextView来看看Touch是如何分发的,先看看ACTION_DOWN

在DecorView这一层会直接调用ViewGroup的dispatchTouchEvent(), 先看18行,每次ACTION_DOWN都会将mMotionTarget设置为null, mMotionTarget是什么?咱们先无论,继续看代码,走到25行,  disallowIntercept默认为false,咱们再看ViewGroup的onInterceptTouchEvent()方法

[java] view plaincopy

  1. public boolean onInterceptTouchEvent(MotionEvent ev) {  

  2.       return false;  

  3.   }  

直接返回false, 继续往下看,循环遍历DecorView里面的Child,从上面的MainActivity的层次结构图咱们能够看出,DecorView里面只有一个Child那就是LinearLayout, 第43行判断Touch的位置在不在LinnearLayout上面,这是毫无疑问的,因此直接跳到51行, 调用LinearLayout的dispatchTouchEvent()方法,LinearLayout也没有dispatchTouchEvent()这个方法,因此也是调用ViewGroup的dispatchTouchEvent()方法,因此这个方法卡在51行没有继续下去,而是去先执行LinearLayout的dispatchTouchEvent()

LinearLayout调用dispatchTouchEvent()的逻辑跟DecorView是同样的,因此也是遍历LinearLayout的两个FrameLayout,判断Touch的是哪一个FrameLayout,很明显是下面那个,调用下面那个FrameLayout的dispatchTouchEvent(),  因此LinearLayout的dispatchTouchEvent()卡在51也没继续下去

继续调用FrameLayout的dispatchTouchEvent()方法,和上面同样的逻辑,下面的FrameLayout也只有一个Child,就是RelativeLayout,FrameLayout的dispatchTouchEvent()继续卡在51行,先执行RelativeLayout的dispatchTouchEvent()方法

执行RelativeLayout的dispatchTouchEvent()方法逻辑仍是同样的,循环遍历 RelativeLayout里面的孩子,里面只有一个TextView, 因此这里就调用TextView的dispatchTouchEvent(), TextView并无dispatchTouchEvent()这个方法,因而找TextView的父类View,在看View的dispatchTouchEvent()的方法以前,咱们先理清下上面这些ViewGroup执行dispatchTouchEvent()的思路,我画了一张图帮你们理清下(这里没有画出onInterceptTouchEvent()方法)

上面的ViewGroup的Touch事件分发就告一段落先,由于这里要调用TextView(也就是View)的dispatchTouchEvent()方法,因此咱们先分析View的dispatchTouchEvent()方法在将上面的继续下去


View的Touch事件分发机制

咱们仍是先看View的dispatchTouchEvent()方法的源码

[java] view plaincopy

  1. public boolean dispatchTouchEvent(MotionEvent event) {  

  2.         if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  

  3.                 mOnTouchListener.onTouch(this, event)) {  

  4.             return true;  

  5.         }  

  6.         return onTouchEvent(event);  

  7.     }  

在这个方法里面,先进行了一个判断

第一个条件mOnTouchListener就是咱们调用View的setTouchListener()方法设置的

第二个条件是判断View是否为enabled的, View通常都是enabled,除非你手动设置为disabled

第三个条件就是OnTouchListener接口的onTouch()方法的返回值了,若是调用了setTouchListener()设置OnTouchListener,而且onTouch()方法返回true,View的dispatchTouchEvent()方法就直接返回true,不然就执行View的onTouchEvent() 并返回View的onTouchEvent()的值
如今你了解了View的onTouchEvent()方法和onTouch()的关系了吧,为何Android提供了处理Touch事件onTouchEvent()方法还要增长一个OnTouchListener接口呢?我以为OnTouchListener接口是对处理Touch事件的屏蔽和扩展做用吧,屏蔽做用我就不举例介绍了,看上面的源码就知道了,我就说下扩展吧,好比咱们要打印View的Touch的点的坐标,咱们能够自定义一个View以下

[java] view plaincopy

  1. public class CustomView extends View {  

  2.       

  3.     public CustomView(Context context, AttributeSet attrs) {  

  4.         super(context, attrs);  

  5.     }  

  6.   

  7.     public CustomView(Context context, AttributeSet attrs, int defStyle) {  

  8.         super(context, attrs, defStyle);  

  9.     }  

  10.   

  11.     @Override  

  12.     public boolean onTouchEvent(MotionEvent event) {  

  13.           

  14.         Log.i("tag""X的坐标 = " + event.getX() + " Y的坐标 = " + event.getY());  

  15.           

  16.         return super.onTouchEvent(event);  

  17.     }  

  18.   

  19. }  

也能够直接对View设置OnTouchListener接口,在return的时候调用下v.onTouchEvent()

[java] view plaincopy

  1. view.setOnTouchListener(new OnTouchListener() {  

  2.               

  3.             @Override  

  4.             public boolean onTouch(View v, MotionEvent event) {  

  5.                   

  6.                 Log.i("tag""X的坐标 = " + event.getX() + " Y的坐标 = " + event.getY());  

  7.                   

  8.                 return v.onTouchEvent(event);  

  9.             }  

  10.         });  

这样子也实现了咱们所须要的功能,因此我认为OnTouchListener是对onTouchEvent()方法的一个屏蔽和扩展做用,假如你有不同的理解,你也能够告诉我下,这里就不纠结这个了。

咱们再看View的onTouchEvent()方法

[java] view plaincopy

  1. public boolean onTouchEvent(MotionEvent event) {  

  2.       final int viewFlags = mViewFlags;  

  3.   

  4.       if ((viewFlags & ENABLED_MASK) == DISABLED) {  

  5.           return (((viewFlags & CLICKABLE) == CLICKABLE ||  

  6.                   (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));  

  7.       }  

  8.   

  9.       //若是设置了Touch代理,就交给代理来处理,mTouchDelegate默认是null  

  10.       if (mTouchDelegate != null) {  

  11.           if (mTouchDelegate.onTouchEvent(event)) {  

  12.               return true;  

  13.           }  

  14.       }  

  15.   

  16.       //若是View是clickable或者longClickable的onTouchEvent就返回true, 不然返回false  

  17.       if (((viewFlags & CLICKABLE) == CLICKABLE ||  

  18.               (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  

  19.           switch (event.getAction()) {  

  20.               case MotionEvent.ACTION_UP:  

  21.                   boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  

  22.                   if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  

  23.                       boolean focusTaken = false;  

  24.                       if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  

  25.                           focusTaken = requestFocus();  

  26.                       }  

  27.   

  28.                       if (!mHasPerformedLongPress) {  

  29.                           removeLongPressCallback();  

  30.   

  31.                           if (!focusTaken) {  

  32.                               if (mPerformClick == null) {  

  33.                                   mPerformClick = new PerformClick();  

  34.                               }  

  35.                               if (!post(mPerformClick)) {  

  36.                                   performClick();  

  37.                               }  

  38.                           }  

  39.                       }  

  40.   

  41.                       if (mUnsetPressedState == null) {  

  42.                           mUnsetPressedState = new UnsetPressedState();  

  43.                       }  

  44.   

  45.                       if (prepressed) {  

  46.                           mPrivateFlags |= PRESSED;  

  47.                           refreshDrawableState();  

  48.                           postDelayed(mUnsetPressedState,  

  49.                                   ViewConfiguration.getPressedStateDuration());  

  50.                       } else if (!post(mUnsetPressedState)) {  

  51.                           mUnsetPressedState.run();  

  52.                       }  

  53.                       removeTapCallback();  

  54.                   }  

  55.                   break;  

  56.   

  57.               case MotionEvent.ACTION_DOWN:  

  58.                   if (mPendingCheckForTap == null) {  

  59.                       mPendingCheckForTap = new CheckForTap();  

  60.                   }  

  61.                   mPrivateFlags |= PREPRESSED;  

  62.                   mHasPerformedLongPress = false;  

  63.                   postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  

  64.                   break;  

  65.   

  66.               case MotionEvent.ACTION_CANCEL:  

  67.                   mPrivateFlags &= ~PRESSED;  

  68.                   refreshDrawableState();  

  69.                   removeTapCallback();  

  70.                   break;  

  71.   

  72.               case MotionEvent.ACTION_MOVE:  

  73.                   final int x = (int) event.getX();  

  74.                   final int y = (int) event.getY();  

  75.   

  76.                   //当手指在View上面滑动超过View的边界,  

  77.                   int slop = mTouchSlop;  

  78.                   if ((x < 0 - slop) || (x >= getWidth() + slop) ||  

  79.                           (y < 0 - slop) || (y >= getHeight() + slop)) {  

  80.                       // Outside button  

  81.                       removeTapCallback();  

  82.                       if ((mPrivateFlags & PRESSED) != 0) {  

  83.                           removeLongPressCallback();  

  84.   

  85.                           mPrivateFlags &= ~PRESSED;  

  86.                           refreshDrawableState();  

  87.                       }  

  88.                   }  

  89.                   break;  

  90.           }  

  91.           return true;  

  92.       }  

  93.   

  94.       return false;  

  95.   }  

这个方法也是比较长的,咱们先看第4行,若是一个View是disabled, 而且该View是Clickable或者longClickable, onTouchEvent()就不执行下面的代码逻辑直接返回true, 表示该View就一直消费Touch事件,若是一个enabled的View,而且是clickable或者longClickable的,onTouchEvent()会执行下面的代码逻辑并返回true,综上,一个clickable或者longclickable的View是一直消费Touch事件的,而通常的View既不是clickable也不是longclickable的(即不会消费Touch事件,只会执行ACTION_DOWN而不会执行ACTION_MOVE和ACTION_UP) Button是clickable的,能够消费Touch事件,可是咱们能够经过setClickable()和setLongClickable()来设置View是否为clickable和longClickable。固然还能够经过重写View的onTouchEvent()方法来控制Touch事件的消费与否

咱们在看57行的ACTION_DOWN, 新建一个CheckForTap,咱们看看CheckForTap是什么

[java] view plaincopy

  1. private final class CheckForTap implements Runnable {  

  2.        public void run() {  

  3.            mPrivateFlags &= ~PREPRESSED;  

  4.            mPrivateFlags |= PRESSED;  

  5.            refreshDrawableState();  

  6.            if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {  

  7.                postCheckForLongClick(ViewConfiguration.getTapTimeout());  

  8.            }  

  9.        }  

  10.    }  

原来是个Runnable对象,而后使用Handler的post方法延时ViewConfiguration.getTapTimeout()执行CheckForTap的run()方法,在run方法中先判断view是否longClickable的,通常的View都是false, postCheckForLongClick(ViewConfiguration.getTapTimeout())这段代码就是执行长按的逻辑的代码,只有当咱们设置为longClickble才会去执行postCheckForLongClick(ViewConfiguration.getTapTimeout()),这里我就不介绍了

因为考虑到文章篇幅的问题,我就不继续分析View的长按事件和点击事件了,在这里我直接得出结论吧

长按事件是在ACTION_DOWN中执行,点击事件是在ACTION_UP中执行,要想执行长按事件,这个View必须是longclickable的, 也许你会纳闷,通常的View不是longClickable为何也会执行长按事件呢?咱们要执行长按事件必需要调用setOnLongClickListener()设置OnLongClickListener接口,咱们看看这个方法的源码

[java] view plaincopy

  1. public void setOnLongClickListener(OnLongClickListener l) {  

  2.      if (!isLongClickable()) {  

  3.          setLongClickable(true);  

  4.      }  

  5.      mOnLongClickListener = l;  

  6.  }  

看到没有,若是这个View不是longClickable的,咱们就调用setLongClickable(true)方法设置为longClickable的,因此才会去执行长按方法onLongClick();

要想执行点击事件,这个View就必需要消费ACTION_DOWN和ACTION_MOVE事件,而且没有设置OnLongClickListener的状况下,若是设置了OnLongClickListener的状况下,须要onLongClick()返回false才能执行到onClick()方法,也许你又会纳闷,通常的View默认是不消费touch事件的,这不是和你上面说的相违背嘛,咱们要向执行点击事件必需要调用setOnClickListener()来设置OnClickListener接口,咱们看看这个方法的源码就知道了

[java] view plaincopy

  1. public void setOnClickListener(OnClickListener l) {  

  2.      if (!isClickable()) {  

  3.          setClickable(true);  

  4.      }  

  5.      mOnClickListener = l;  

  6.  }  

因此说一个enable的而且是clickable的View是一直消费touch事件的,因此才会执行到onClick()方法



对于View的Touch事件的分发机制算是告一段落了,从上面咱们能够得出TextView的dispatchTouchEvent()的返回false的,即不消费Touch事件。咱们就要往上看RelativeLayout的dispatchTouchEvent()方法的51行,因为TextView.dispatchTouchEvent()为false, 致使mMotionTarget没有被赋值,仍是null, 继续往下走执行RelativeLayout的dispatchTouchEvent()方法, 来到第84行, 判断target是否为null,这个target就是mMotionTarget,知足条件,执行92行的 super.dispatchTouchEvent(ev)代码并返回, 这里调用的是RelativeLayout父类View的dispatchTouchEvent()方法,因为RelativeLayout没有设置onTouchListener, 因此这里直接调用RelativeLayout(其实就是View, 由于RelativeLayout没有重写onTouchEvent())的onTouchEvent()方法 因为RelativeLayout既不是clickable的也是longClickable的,因此其onTouchEvent()方法false, RelativeLayout的dispatchTouchEvent()也是返回false,这里就执行完了RelativeLayout的dispatchTouchEvent()方法

继续执行FrameLayout的dispatchTouchEvent()的第51行,因为RelativeLayout.dispatchTouchEvent()返回的是false, 跟上面的逻辑是同样的, 也是执行到92行的super.dispatchTouchEvent(ev)代码并返回,而后执行FrameLayout的onTouchEvent()方法,而FrameLayout的onTouchEvent()也是返回false,因此FrameLayout的dispatchTouchEvent()方法返回false,执行完毕FrameLayout的dispatchTouchEvent()方法

在上面的我就不分析了,你们自行分析一下,跟上面的逻辑是同样的,我直接画了个图来帮你们理解下(这里没有画出onInterceptTouchEvent()方法)

因此咱们点击屏幕上面的TextView的事件分发流程是上图那个样子的,表示Activity的View都不消费ACTION_DOWN事件,因此就不能在触发ACTION_MOVE, ACTION_UP等事件了,具体是为何?我还不太清楚,毕竟从Activity到TextView这一层是分析不出来的,估计是在底层实现的。


但若是将TextView换成Button,流程是否是仍是这个样子呢?答案不是,咱们来分析分析一下,若是是Button , Button是一个clickable的View,onTouchEvent()返回true, 表示他一直消费Touch事件,因此Button的dispatchTouchEvent()方法返回true, 回到RelativeLayout的dispatchTouchEvent()方法的51行,知足条件,进入到if方法体,设置mMotionTarget为Button,而后直接返回true, RelativeLayout的dispatchTouchEvent()方法执行完毕, 不会调用到RelativeLayout的onTouchEvent()方法

而后到FrameLayout的dispatchTouchEvent()方法的51行,因为RelativeLayout.dispatchTouchEvent()返回true, 知足条件,进入if方法体,设置mMotionTarget为RelativeLayout,注意下,这里的mMotionTarget跟RelativeLayout的dispatchTouchEvent()方法的mMotionTarget不是同一个哦,由于他们是不一样的方法中的,而后返回true

同理FrameLayout的dispatchTouchEvent()也是返回true, DecorView的dispatchTouchEvent()方法也返回true, 仍是画一个流程图(这里没有画出onInterceptTouchEvent()方法)给你们理清下

从上面的流程图得出一个结论,Touch事件是从顶层的View一直往下分发到手指按下的最里面的View,若是这个View的onTouchEvent()返回false,即不消费Touch事件,这个Touch事件就会向上找父布局调用其父布局的onTouchEvent()处理,若是这个View返回true,表示消费了Touch事件,就不调用父布局的onTouchEvent()


接下来咱们用一个自定义的ViewGroup来替换RelativeLayout,自定义ViewGroup代码以下

[java] view plaincopy

  1. package com.example.androidtouchevent;  

  2.   

  3. import android.content.Context;  

  4. import android.util.AttributeSet;  

  5. import android.view.MotionEvent;  

  6. import android.widget.RelativeLayout;  

  7.   

  8. public class CustomLayout extends RelativeLayout {  

  9.       

  10.     public CustomLayout(Context context, AttributeSet attrs) {  

  11.         super(context, attrs, 0);  

  12.     }  

  13.   

  14.     public CustomLayout(Context context, AttributeSet attrs, int defStyle) {  

  15.         super(context, attrs, defStyle);  

  16.     }  

  17.   

  18.     @Override  

  19.     public boolean onTouchEvent(MotionEvent event) {  

  20.         return super.onTouchEvent(event);  

  21.     }  

  22.   

  23.     @Override  

  24.     public boolean onInterceptTouchEvent(MotionEvent ev) {  

  25.         return true;  

  26.     }  

  27.       

  28.   

  29. }  

咱们就重写了onInterceptTouchEvent(),返回true, RelativeLayout默认是返回false, 而后再CustomLayout布局中加一个Button ,以下图

咱们此次不从DecorView的dispatchTouchEvent()分析了,直接从CustomLayout的dispatchTouchEvent()分析

咱们先看ACTION_DOWN 来到25行,因为咱们重写了onInterceptTouchEvent()返回true, 因此不走这个if里面,直接往下看代码,来到84行, target为null,因此进入if方法里面,直接调用super.dispatchTouchEvent()方法, 也就是View的dispatchTouchEvent()方法,而在View的dispatchTouchEvent()方法中是直接调用View的onTouchEvent()方法,可是CustomLayout重写了onTouchEvent(),因此这里仍是调用CustomLayout的onTouchEvent(), 这个方法返回false, 不消费Touch事件,因此不会在触发ACTION_MOVE,ACTION_UP等事件了,这里我再画一个流程图吧(含有onInterceptTouchEvent()方法的)


好了,就分析到这里吧,差很少分析完了,还有一种状况没有分析到,例如我将CustomLayout的代码改为下面的情形,Touch事件又是怎么分发的呢?我这里就不带你们分析了

[java] view plaincopy

  1. package com.example.androidtouchevent;  

  2.   

  3. import android.content.Context;  

  4. import android.util.AttributeSet;  

  5. import android.view.MotionEvent;  

  6. import android.widget.RelativeLayout;  

  7.   

  8. public class CustomLayout extends RelativeLayout {  

  9.       

  10.     public CustomLayout(Context context, AttributeSet attrs) {  

  11.         super(context, attrs, 0);  

  12.     }  

  13.   

  14.     public CustomLayout(Context context, AttributeSet attrs, int defStyle) {  

  15.         super(context, attrs, defStyle);  

  16.     }  

  17.   

  18.     @Override  

  19.     public boolean onTouchEvent(MotionEvent event) {  

  20.         return super.onTouchEvent(event);  

  21.     }  

  22.   

  23.     @Override  

  24.     public boolean onInterceptTouchEvent(MotionEvent ev) {  

  25.         if(ev.getAction() == MotionEvent.ACTION_MOVE){  

  26.             return true;  

  27.         }  

  28.         return super.onInterceptTouchEvent(ev);  

  29.     }  

  30.       

  31.   

  32. }  

这篇文章的篇幅有点长,若是你想了解Touch事件的分发机制,你必定要认真看完,下面来总结一下吧

1.Activity的最顶层Window是PhoneWindow,PhoneWindow的最顶层View是DecorView

2.一个clickable或者longClickable的View会永远消费Touch事件,无论他是enabled仍是disabled的

3.View的长按事件是在ACTION_DOWN中执行,要想执行长按事件该View必须是longClickable的,而且不能产生ACTION_MOVE

4.View的点击事件是在ACTION_UP中执行,想要执行点击事件的前提是消费了ACTION_DOWN和ACTION_MOVE,而且没有设置OnLongClickListener的状况下,如设置了OnLongClickListener的状况,则必须使onLongClick()返回false

5.若是View设置了onTouchListener了,而且onTouch()方法返回true,则不执行View的onTouchEvent()方法,也表示View消费了Touch事件,返回false则继续执行onTouchEvent()

6.Touch事件是从最顶层的View一直分发到手指touch的最里层的View,若是最里层View消费了ACTION_DOWN事件(设置onTouchListener,而且onTouch()返回true 或者onTouchEvent()方法返回true)才会触发ACTION_MOVE,ACTION_UP的发生,若是某个ViewGroup拦截了Touch事件,则Touch事件交给ViewGroup处理

7.Touch事件的分发过程当中,若是消费了ACTION_DOWN,而在分发ACTION_MOVE的时候,某个ViewGroup拦截了Touch事件,就像上面那个自定义CustomLayout,则会将ACTION_CANCEL分发给该ViewGroup下面的Touch到的View,而后将Touch事件交给ViewGroup处理,并返回true

相关文章
相关标签/搜索