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


    本文转载地址来自:http://blog.csdn.net/xiaanming/article/details/21696315

    其他人转载时,请注明原文的最开始地址(http://blog.csdn.net/xiaanming/article/details/21696315)。谢谢!

    从Android2.2的源码来进行分析。

    ViewGroup的事件分发机制

    手指触摸手机屏幕时,会产生一个触摸事件,其事件分发机制涉及到操作硬件(手机屏幕),即Linux内核,由于这个东西不太熟悉,我们就从与用户交互的Activity来分析吧,首先看dispatchTouchEvent()方法的源码。

    public boolean dispatchTouchEvent(MotionEvent ev) {
    
    	if (ev.getAction() == MotionEvent.ACTION_DOWN) {  //按下
         		onUserInteraction();
    	}
    		
    	if (getWindow().superDispatchTouchEvent(ev)) {
    		return true;      //true表示事件有顶层窗口Window对象来执行
    	}
    	return onTouchEvent(ev);
    }
    

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

    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的superDispatchTouchEvent()方法

    1 public boolean superDispatchTouchEvent(KeyEvent event) {
    2   return mDecor.superDispatcTouchEvent(event);
    3 }

    里面直接调用DecorView类的superDispatchTouchEvent()方法,或许很多人不了解DecorView这个类,DecorView是PhoneWindow的一个final的内部类并且继承FrameLayout的,也是Window界面的最顶层的View对象,这是什么意思呢?别着急,我们接着往下看。
    我们先新建一个项目,取名AndroidTouchEvent,然后直接用模拟器运行项目, MainActivity的布局文件为

     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()方法

    1 public boolean superDispatchTouchEvent(MotionEvent event) {
    2        return super.dispatchTouchEvent(event);
    3 }

    里面调用了父类FrameLayout的dispatchTouchEvent()方法,而FrameLayout中并没有dispatchTouchEvent()方法,所以我们直接看ViewGroup的dispatchTouchEvent()方法

      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()方法

    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()方法的源码

    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如下

     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()

     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()方法

     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是什么

     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接口,我们看看这个方法的源码

    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接口,我们看看这个方法的源码就知道了

       public void setOnClickListener(OnClickListener l) {
            if (!isClickable()) {
                setClickable(true);
            }
            mOnClickListener = l;
        }

    所以说一个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代码如下

     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事件又是怎么分发的呢?我这里就不带大家分析了

     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

  • 相关阅读:
    JS 和 CSS 的位置对其他资源加载顺序的影响
    How browsers workBehind the scenes of modern web browsers
    Gruntjs入门 (2)
    Superhero.js – 构建大型 JavaScript 应用程序的最佳资源
    利用位反操作来简化 indexOf 判断
    Gruntjs入门 (1)
    js和css的顺序关系
    (翻译)理解JavaScript函数调用和"this"(by Yehuda Katz)
    秒,微秒,毫秒
    您试图从目录中执行 CGI、ISAPI 或其他可执行程序,但该目录不允许执行程序
  • 原文地址:https://www.cnblogs.com/zhangping/p/3668046.html
Copyright © 2020-2023  润新知