Android中的view全解析(四)

来源:互联网 发布:办公室软件2013下载 编辑:程序博客网 时间:2024/06/05 19:14

最后,我们来看一下View的事件分发机制。

当我们对一个View进行点击时(Button也好,ImageView也好),首先会调用View的dispatchTouchEvent方法,方法的代码如下:
    public boolean dispatchTouchEvent(MotionEvent event) {  
        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
                mOnTouchListener.onTouch(this, event)) {  
            return true;  
        }  
        return onTouchEvent(event);  
    }  
先看if中的语句,
1. mOnTouchListener != null View被设置了OnTouchListener
2. (mViewFlags & ENABLED_MASK) == ENABLED View的状态是enabled,enabled的状态表明View可以接收事件
3. mOnTouchListener.onTouch(this, event) onTouch方法返回值为true
当以上3个条件都成立时,返回值为true。否则,就执行onTouchEvent方法,返回该方法的返回值。

下面是onTouchEvent方法的具体代码:

   
public boolean onTouchEvent(MotionEvent event) {          final int viewFlags = mViewFlags;          if ((viewFlags & ENABLED_MASK) == DISABLED) {              // 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));          }          if (mTouchDelegate != null) {              if (mTouchDelegate.onTouchEvent(event)) {                  return true;              }          }          if (((viewFlags & CLICKABLE) == CLICKABLE ||                  (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {              switch (event.getAction()) {                  case MotionEvent.ACTION_UP:                      boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;                      if ((mPrivateFlags & 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 (!mHasPerformedLongPress) {                              // 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) {                              mPrivateFlags |= PRESSED;                              refreshDrawableState();                              postDelayed(mUnsetPressedState,                                      ViewConfiguration.getPressedStateDuration());                          } else if (!post(mUnsetPressedState)) {                              // If the post failed, unpress right now                              mUnsetPressedState.run();                          }                          removeTapCallback();                      }                      break;                  case MotionEvent.ACTION_DOWN:                      if (mPendingCheckForTap == null) {                          mPendingCheckForTap = new CheckForTap();                      }                      mPrivateFlags |= PREPRESSED;                      mHasPerformedLongPress = false;                      postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());                      break;                  case MotionEvent.ACTION_CANCEL:                      mPrivateFlags &= ~PRESSED;                      refreshDrawableState();                      removeTapCallback();                      break;                  case MotionEvent.ACTION_MOVE:                      final int x = (int) event.getX();                      final int y = (int) event.getY();                      // Be lenient about moving outside of buttons                      int slop = mTouchSlop;                      if ((x < 0 - slop) || (x >= getWidth() + slop) ||                              (y < 0 - slop) || (y >= getHeight() + slop)) {                          // Outside button                          removeTapCallback();                          if ((mPrivateFlags & PRESSED) != 0) {                              // Remove any future long press/tap checks                              removeLongPressCallback();                              // Need to switch from pressed to not pressed                              mPrivateFlags &= ~PRESSED;                              refreshDrawableState();                          }                      }                      break;              }              return true;          }          return false;      }  


在该方法中,首先判断View是否是可点击的,或者可长点击的,如果是,进入switch判断,并在MotionEvent_UP事件中调用了performClick方法,在performClick方法中判断,如果View设置了OnClickListener,就会回调执行了onClick方法。
但要注意一点,如果View是可点击的或者是可长点击的,一旦进入了if的语句段,最终的返回值为true。这是什么意思呢?如果我们在onTouch中的返回值为false,那么只要这个View是可点击的,最终的返回值也为true。
这里就要区分一下,onTouch返回值中的含义了。如果onTouch返回为true,表明View的onTouch方法将此事件消费掉了,不再向下传递,并且将继续监听View的其他事件。由于onTouch方法将事件消费掉了,那么onClick方法和onLongClick方法都无法接受到该事件了。如果onTouch方法返回为false,表明onTouch方法没有消费该事件,事件将会向下继续传递给onClick和onLongClick方法,事件将会被onClick或者onLongClick方法消费,于是返回true,表明View的onClick和onClick方法还将继续接收事件,而由于onTouch方法接收事件在onClick和onLongClick之前,所以它也会再继续接受事件。
总结一下,这个最终的返回值为true或者false并不是由onTouch方法决定的,而是由事件是否被View所消费决定的。如果View是可点击的,那么它总能消费事件,返回值也始终为true。每个ENABLED状态的View都可接收事件,默认均可被触摸,所以要在onTouch方法中返回值来判断是否要消费该事件。此时要注意区分接收事件和消费事件的区别。

上面分析了View的事件分发机制,下面来看一看ViewGroup中的事件分发。
首先,ViewGroup中也有dispatchTouchEvent方法,我们来看一看它的具体实现。

   
public boolean dispatchTouchEvent(MotionEvent ev) {          final int action = ev.getAction();          final float xf = ev.getX();          final float yf = ev.getY();          final float scrolledXFloat = xf + mScrollX;          final float scrolledYFloat = yf + mScrollY;          final Rect frame = mTempRect;          boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;          if (action == MotionEvent.ACTION_DOWN) {              if (mMotionTarget != null) {                  mMotionTarget = null;              }              if (disallowIntercept || !onInterceptTouchEvent(ev)) {                  ev.setAction(MotionEvent.ACTION_DOWN);                  final int scrolledXInt = (int) scrolledXFloat;                  final int scrolledYInt = (int) scrolledYFloat;                  final View[] children = mChildren;                  final int count = mChildrenCount;                  for (int i = count - 1; i >= 0; i--) {                      final View child = children[i];                      if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE                              || child.getAnimation() != null) {                          child.getHitRect(frame);                          if (frame.contains(scrolledXInt, scrolledYInt)) {                              final float xc = scrolledXFloat - child.mLeft;                              final float yc = scrolledYFloat - child.mTop;                              ev.setLocation(xc, yc);                              child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;                              if (child.dispatchTouchEvent(ev))  {                                  mMotionTarget = child;                                  return true;                              }                          }                      }                  }              }          }          boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||                  (action == MotionEvent.ACTION_CANCEL);          if (isUpOrCancel) {              mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;          }          final View target = mMotionTarget;          if (target == null) {              ev.setLocation(xf, yf);              if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {                  ev.setAction(MotionEvent.ACTION_CANCEL);                  mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;              }              return super.dispatchTouchEvent(ev);          }          if (!disallowIntercept && onInterceptTouchEvent(ev)) {              final float xc = scrolledXFloat - (float) target.mLeft;              final float yc = scrolledYFloat - (float) target.mTop;              mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;              ev.setAction(MotionEvent.ACTION_CANCEL);              ev.setLocation(xc, yc);              if (!target.dispatchTouchEvent(ev)) {              }              mMotionTarget = null;              return true;          }          if (isUpOrCancel) {              mMotionTarget = null;          }          final float xc = scrolledXFloat - (float) target.mLeft;          final float yc = scrolledYFloat - (float) target.mTop;          ev.setLocation(xc, yc);          if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {              ev.setAction(MotionEvent.ACTION_CANCEL);              target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;              mMotionTarget = null;          }          return target.dispatchTouchEvent(ev);      }  


注意到,代码中有这么一个判断句:
if (disallowIntercept || !onInterceptTouchEvent(ev))
if中有两个条件,只要满足其一,就会进入下面的代码段。
1. disallowIntercept ViewGroup是否禁用掉事件拦截功能,true表示禁用,false表示不禁用。
2. !onInterceptTouchEvent(ev) ViewGroup是否拦截该事件。该条件对onInterceptTouchEvent方法的返回值进行了取反操作,如果onInterceptTouchEvent方法的返回值为false,即不拦截,那么该条件成立。
总结一下,如果ViewGroup禁用掉了事件拦截功能,或者ViewGroup没有对该事件进行拦截,那么将进入下面的代码段。下面的代码段是在做什么呢?

for (int i = count - 1; i >= 0; i--) {                  final View child = children[i];                  if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE                          || child.getAnimation() != null) {                      child.getHitRect(frame);                      if (frame.contains(scrolledXInt, scrolledYInt)) {                          final float xc = scrolledXFloat - child.mLeft;                          final float yc = scrolledYFloat - child.mTop;                          ev.setLocation(xc, yc);                          child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;                          if (child.dispatchTouchEvent(ev))  {                              mMotionTarget = child;                              return true;                          }                      }                  }  
看这个for循环应该就很明白了,它获取了ViewGroup中可以接收该事件的childView,将事件向下传递了。childView接收到了该事件后,该怎么办呢?下面是for循环中的if语句。

if (child.dispatchTouchEvent(ev))  {                              mMotionTarget = child;                              return true;                          }  

有if中的条件可见,如果childView将事件消费掉了,那么ViewGroup的dispatchTouchEvent方法直接返回true,ViewGroup不再处理该事件。否则,将继续执行下面的代码段,由ViewGroup来处理该事件。

总结:
1. Android中的事件传递是由ViewGroup开始在dispatchTouchEvent方法中向下传递,先传递到ViewGroup,再由ViewGroup决定是否传递到View。
2. 如果ViewGroup的onInterceptTouchEvent将事件拦截了,或者childView没有将事件消费掉,ViewGroup将处理该事件。
3. 如果childView处理了该事件,那么ViewGroup便不会处理该事件。




0 0