你对android的view的Touch事件足够理解吗?从源码角度来看问题

KasBodnar 8年前
   <h2><strong>前言</strong></h2>    <p>在android应用开发过程中,总避免不了各种Touch 事件的处理,网上也有各种各样的示例,更有甚者,还画了各种各样的分析图来显示的表述Touch事件的处理流程,但是呢,相信大多数的读者也就是当时看着明白,但过了几天,就全部忘得一干二净了。所以才有了这篇博客的出现,第一是方便你学习,第二是即便以后找不到这篇博客,你直接看源码便会了然一切。</p>    <p>ps: 请大家注意源码中的注释,一般注释是最重要的</p>    <h2><strong>ViewGroup的分发</strong></h2>    <pre>  <code class="language-java">@Override      public boolean dispatchTouchEvent(MotionEvent ev) {  //为了debuging的一致性验证 属性 ,神马东东,不管了          if (mInputEventConsistencyVerifier != null) {                mInputEventConsistencyVerifier.onTouchEvent(ev, 1);            }            boolean handled = false;  //经过filter 顾虑器,然后将可被处理的touchEvent 往下行进          if (onFilterTouchEventForSecurity(ev)) {               final int action = ev.getAction();              final int actionMasked = action & MotionEvent.ACTION_MASK;                // Handle an initial down.  // 看这里,所有的 Touch事件都是从一个Down开始              if (actionMasked == MotionEvent.ACTION_DOWN) {                  // Throw away all previous state when starting a new touch gesture.                  // The framework may have dropped the up or cancel event for the previous gesture                  // due to an app switch, ANR, or some other state change.                  cancelAndClearTouchTargets(ev);                  resetTouchState();              }                // Check for interception.              final boolean intercepted;              if (actionMasked == MotionEvent.ACTION_DOWN                      || mFirstTouchTarget != null) {                  final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;                  if (!disallowIntercept) {   //这里是不是很熟悉 ,注意 Down的时候去判断,  //如果onInterceptTouchEvent 返回 true 和返回 false的情况                      intercepted = onInterceptTouchEvent(ev);                       ev.setAction(action); // restore action in case it was changed                  } else {                      intercepted = false;                  }              } else {                  // There are no touch targets and this action is not an initial down                  // so this view group continues to intercept touches.                  intercepted = true;              }                // Check for cancelation.              final boolean canceled = resetCancelNextUpFlag(this)                      || actionMasked == MotionEvent.ACTION_CANCEL;                // Update list of touch targets for pointer down, if needed.              final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;              TouchTarget newTouchTarget = null;              boolean alreadyDispatchedToNewTouchTarget = false;  //ok,看这里 ,判断语句里有child ,很明显这里要进行touch 事件的分发了吧,哈哈哈。  //所以联系上面的onInterceptTouchEvent,是不是很熟悉,哈哈哈,如果返回true,则不传递给子view。。。              if (!canceled && !intercepted) {                  if (actionMasked == MotionEvent.ACTION_DOWN                          || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)                          || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {                      final int actionIndex = ev.getActionIndex(); // always 0 for down                      final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)                              : TouchTarget.ALL_POINTER_IDS;                        // Clean up earlier touch targets for this pointer id in case they                      // have become out of sync.                      removePointersFromTouchTargets(idBitsToAssign);                        final int childrenCount = mChildrenCount;                      if (newTouchTarget == null && childrenCount != 0) {                          final float x = ev.getX(actionIndex);                          final float y = ev.getY(actionIndex);                          // Find a child that can receive the event.                          // Scan children from front to back.  //这里这里 父view的touch 事件传递给子view的顺序是不是一目了然                         final ArrayList<View> preorderedList = buildOrderedChildList();                          final boolean customOrder = preorderedList == null                                  && isChildrenDrawingOrderEnabled();                          final View[] children = mChildren;                          for (int i = childrenCount - 1; i >= 0; i--) {                              final int childIndex = customOrder                                      ? getChildDrawingOrder(childrenCount, i) : i;                              final View child = (preorderedList == null)                                      ? children[childIndex] : preorderedList.get(childIndex);                              if (!canViewReceivePointerEvents(child)                                      || !isTransformedTouchPointInView(x, y, child, null)) {                                  continue;                              }                                newTouchTarget = getTouchTarget(child);                              if (newTouchTarget != null) {                                  // Child is already receiving touch within its bounds.                                  // Give it the new pointer in addition to the ones it is handling.                                  newTouchTarget.pointerIdBits |= idBitsToAssign;                                  break;                              }                                resetCancelNextUpFlag(child);  //这里这里 当子view dispatchTouchEvent 返回true,进入以下逻辑,  //该view会消费掉这个Touch事件,并生成一个新的Touch事件的包装(  //TouchTarget 包含接收事件的view以及touch事件的类型等信息)                              if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {                                  // Child wants to receive touch within its bounds.                                  mLastTouchDownTime = ev.getDownTime();                                  if (preorderedList != null) {                                      // childIndex points into presorted list, find original index                                      for (int j = 0; j < childrenCount; j++) {                                          if (children[childIndex] == mChildren[j]) {                                              mLastTouchDownIndex = j;                                              break;                                          }                                      }                                  } else {                                      mLastTouchDownIndex = childIndex;                                  }                                  mLastTouchDownX = ev.getX();                                  mLastTouchDownY = ev.getY();  //这里便是生成新的TouchTarget的逻辑                                  newTouchTarget = addTouchTarget(child, idBitsToAssign);                                  alreadyDispatchedToNewTouchTarget = true;                                  break;                              }                          }                          if (preorderedList != null) preorderedList.clear();                      }  //这里呢 ,如果没有新的TouchTarget ,辣么就是上面 注释返回 false的情况,  //可以清楚的知道注释里会把Touch事件传递给最新最近添加到view  //(举个栗子,一个RelativeLayout 依次添加view A,view B ,  //View B 在 view A 之上,辣么当view B 的dispatchTouchEvent 返回 false时,  //则touch事件一定会传递给 view A)                      if (newTouchTarget == null && mFirstTouchTarget != null) {                          // Did not find a child to receive the event.                          // Assign the pointer to the least recently added target.                          newTouchTarget = mFirstTouchTarget;                          while (newTouchTarget.next != null) {                              newTouchTarget = newTouchTarget.next;                          }                          newTouchTarget.pointerIdBits |= idBitsToAssign;                      }                  }              }                // Dispatch to touch targets.              if (mFirstTouchTarget == null) {                  // No touch targets so treat this as an ordinary view.                  handled = dispatchTransformedTouchEvent(ev, canceled, null,                          TouchTarget.ALL_POINTER_IDS);              } else {                  // Dispatch to touch targets, excluding the new touch target if we already                  // dispatched to it.  Cancel touch targets if necessary.                  TouchTarget predecessor = null;                  TouchTarget target = mFirstTouchTarget;  //这里面则是一个递归给子view 的子view 的过程,哈哈哈                  while (target != null) {                      final TouchTarget next = target.next;                      if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {                          handled = true;                      } else {                          final boolean cancelChild = resetCancelNextUpFlag(target.child)                                  || intercepted;                          if (dispatchTransformedTouchEvent(ev, cancelChild,                                  target.child, target.pointerIdBits)) {                              handled = true;                          }                          if (cancelChild) {                              if (predecessor == null) {                                  mFirstTouchTarget = next;                              } else {                                  predecessor.next = next;                              }                              target.recycle();                              target = next;                              continue;                          }                      }                      predecessor = target;                      target = next;                  }              }                // Update list of touch targets for pointer up or cancel, if needed.              if (canceled                      || actionMasked == MotionEvent.ACTION_UP                      || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {                  resetTouchState();              } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {                  final int actionIndex = ev.getActionIndex();                  final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);                  removePointersFromTouchTargets(idBitsToRemove);              }          }            if (!handled && mInputEventConsistencyVerifier != null) {              mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);          }          return handled;      }</code></pre>    <p>辣么我们来看看如何 buildOrderedChildList,哈哈,只看注释就可以啦 ,z越大,插得就越靠前</p>    <pre>  <code class="language-java">/**       * Populates (and returns) mPreSortedChildren with a pre-ordered list of the View's children,       * sorted first by Z, then by child drawing order (if applicable).       *       * Uses a stable, insertion sort which is commonly O(n) for ViewGroups with very few elevated       * children.       */      ArrayList<View> buildOrderedChildList() {          final int count = mChildrenCount;          if (count <= 1 || !hasChildWithZ()) return null;            if (mPreSortedChildren == null) {              mPreSortedChildren = new ArrayList<View>(count);          } else {              mPreSortedChildren.ensureCapacity(count);          }            final boolean useCustomOrder = isChildrenDrawingOrderEnabled();          for (int i = 0; i < mChildrenCount; i++) {              // add next child (in child order) to end of list              int childIndex = useCustomOrder ? getChildDrawingOrder(mChildrenCount, i) : i;              View nextChild = mChildren[childIndex];              float currentZ = nextChild.getZ();                // insert ahead of any Views with greater Z              int insertIndex = i;              while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {                  insertIndex--;              }              mPreSortedChildren.add(insertIndex, nextChild);          }          return mPreSortedChildren;      }</code></pre>    <p>然后我们看看这个</p>    <pre>  <code class="language-java">/**       * Transforms a motion event into the coordinate space of a particular child view,       * filters out irrelevant pointer ids, and overrides its action if necessary.       * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.       */      private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,              View child, int desiredPointerIdBits) {          final boolean handled;            // Canceling motions is a special case.  We don't need to perform any transformations          // or filtering.  The important part is the action, not the contents.          final int oldAction = event.getAction();          if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {              event.setAction(MotionEvent.ACTION_CANCEL);              if (child == null) {                  handled = super.dispatchTouchEvent(event);              } else {                  handled = child.dispatchTouchEvent(event);              }              event.setAction(oldAction);              return handled;          }            // Calculate the number of pointers to deliver.          final int oldPointerIdBits = event.getPointerIdBits();          final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;            // If for some reason we ended up in an inconsistent state where it looks like we          // might produce a motion event with no pointers in it, then drop the event.          if (newPointerIdBits == 0) {              return false;          }            // If the number of pointers is the same and we don't need to perform any fancy          // irreversible transformations, then we can reuse the motion event for this          // dispatch as long as we are careful to revert any changes we make.          // Otherwise we need to make a copy.          final MotionEvent transformedEvent;          if (newPointerIdBits == oldPointerIdBits) {              if (child == null || child.hasIdentityMatrix()) {                  if (child == null) {                      handled = super.dispatchTouchEvent(event);                  } else {                      final float offsetX = mScrollX - child.mLeft;                      final float offsetY = mScrollY - child.mTop;                      event.offsetLocation(offsetX, offsetY);                        handled = child.dispatchTouchEvent(event);                        event.offsetLocation(-offsetX, -offsetY);                  }                  return handled;              }              transformedEvent = MotionEvent.obtain(event);          } else {              transformedEvent = event.split(newPointerIdBits);          }            // Perform any necessary transformations and dispatch.          if (child == null) {              handled = super.dispatchTouchEvent(transformedEvent);          } else {              final float offsetX = mScrollX - child.mLeft;              final float offsetY = mScrollY - child.mTop;              transformedEvent.offsetLocation(offsetX, offsetY);  //哈哈,这里是Touch 事件的传递时的坐标转换              if (! child.hasIdentityMatrix()) {                  transformedEvent.transform(child.getInverseMatrix());              }  //这里这里 ,当view 的dispatchTouchEvent为 true ,为false?              handled = child.dispatchTouchEvent(transformedEvent);          }            // Done.          transformedEvent.recycle();          return handled;      }</code></pre>    <h2><strong>View 的分发Touch事件</strong></h2>    <p>代码如下,可以看到相较于ViewGroup的 dispatchTouchEvent而言,要简单的多,毕竟没有牵扯到子view的Touch事件的分发。</p>    <pre>  <code class="language-java">/**       * Pass the touch screen motion event down to the target view, or this       * view if it is the target.       *       * @param event The motion event to be dispatched.       * @return True if the event was handled by the view, false otherwise.       */      public boolean dispatchTouchEvent(MotionEvent event) {          boolean result = false;            if (mInputEventConsistencyVerifier != null) {              mInputEventConsistencyVerifier.onTouchEvent(event, 0);          }            final int actionMasked = event.getActionMasked();          if (actionMasked == MotionEvent.ACTION_DOWN) {              // Defensive cleanup for new gesture              stopNestedScroll();          }            if (onFilterTouchEventForSecurity(event)) {              //noinspection SimplifiableIfStatement              ListenerInfo li = mListenerInfo;              if (li != null && li.mOnTouchListener != null                      && (mViewFlags & ENABLED_MASK) == ENABLED                      && li.mOnTouchListener.onTouch(this, event)) {                  result = true;              }                if (!result && onTouchEvent(event)) {                  result = true;              }          }            if (!result && mInputEventConsistencyVerifier != null) {              mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);          }            // Clean up after nested scrolls if this is the end of a gesture;          // also cancel it if we tried an ACTION_DOWN but we didn't want the rest          // of the gesture.          if (actionMasked == MotionEvent.ACTION_UP ||                  actionMasked == MotionEvent.ACTION_CANCEL ||                  (actionMasked == MotionEvent.ACTION_DOWN && !result)) {              stopNestedScroll();          }            return result;      }</code></pre>    <p>直接说吧 ,有一个 mOnTouchListener.onTouch(this, event) 和 onTouchEvent(event) 没有 onInterceptTouchEvent 。各位看官,大致的传递顺序应该不用我讲了吧,一目了然。</p>    <h2><strong>总结</strong></h2>    <p>我们在处理应用开发过程中的一些复杂的交互过程时,TouchEvent是避不开的,网上也有详细介绍各种return false ,return true .然而,其实对于这些来讲,所有的所有,一切的触发点是linux层根据你的触摸发送出事件信号,传递到最底层的view,并由该view去dispatch的。所以只要清晰dispatch的机制,这些处理将不是一个难点。</p>    <h2><strong>参考</strong></h2>    <ul>     <li>android 应用层源码</li>    </ul>    <p> </p>    <p>来自:http://www.jianshu.com/p/c1d184032ae1</p>    <p> </p>