View事件分發,從明白到懵b~~

事件分發

入口點爲Activity中的dispatchTouchEvent(MotionEvent ev)

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
	//這個window我們知道是PhoneWindow,那就直接去PhoneWindow吧
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
	//默認返回時false
    return onTouchEvent(ev);
}

PhoneWindow

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
 //調用了decorView的這個方法,我們知道,它一定開始要去View中開始搞事情了。
 //最後調用了ViewGroup的dispatchTouchEvent(MotionEvent event)
   return mDecor.superDispatchTouchEvent(event);
}

ViewGroup

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
	 if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;
		//處理down事件
        // Handle an initial down.
        if (actionMasked == MotionEvent.ACTION_DOWN) {
			//因爲是down事件,作爲一次新事件的開頭,清除上次的標誌、緩存
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }
		//檢測事件攔截
        // Check for interception.
        final boolean intercepted;
		//Down事件發生的時候 mFirstTouchTarget == null
		// Up和Move的時候,mFirstTouchTarget如果在down時找到了消費的那個,mFirstTouchTarget!=null
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
			//是否不允許攔截?
			//默認MGroupFlags值是0 ,disallowIntercept爲false ,表示允許攔截
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
			//允許攔截
                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;
        }
##########  down判斷完之後下面的這個if有點長
	//非攔截也非取消信號
        if (!canceled && !intercepted) {
			//下面爲ACTION_DOWN的處理
			##### ActionDown纔會執行的,找目標~~~
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
                    final float x = ev.getX(actionIndex);
                    final float y = ev.getY(actionIndex);
					//更具view.getZ()的值,來創建這個View集合
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);
						// canReceivePointerEvents檢測了當前這個View的可見性是否是Visible
						// isTransformedTouchPointInView檢測了位置是否在該區域
                        if (!child.canReceivePointerEvents()
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
						//不可見及位置不合適都繼續尋找
                            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可見又在區域,View是否要處理這個事件?
						//當然是分發給他
                        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();
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
						//已經找到一個,並且返回了true消費了當前事件
                            break;
                        }

                        // The accessibility focus didn't handle the event, so clear
                        // the flag and do a normal dispatch to all children.
                        ev.setTargetAccessibilityFocus(false);
                    }
                    if (preorderedList != null) preorderedList.clear();
                }
				//上面如果找到了一個View並且在返回了true,那麼這裏不會執行
                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;
              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) {
      		//up時重置mFirstTouchTarget等
              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);
      }
      //返回handled,標緻結束
      return handled;}
    

ACTION DOWN的時候,主要是要找到目標View(即位置在範圍內,並且可見性爲Visible的),而且它的目標是找到一個消費事件的View,加入進mFirstTouchTarget這個鏈表,這樣,當下次事件:Move、Up事件來到的時候,就直接從mFirstTouchTaget取出這個目標View,直接調用dispatchTransformedTouchEvent進行事件分發。也就是一個View,如果在Down事件發生的時候,返回了false,不處理,那麼後續的Move和Up事件都不會接收到。


dispatchTransformedTouchEvent

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
	  handled = child.dispatchTouchEvent(transformedEvent);
	return handled;
}

火速進入View的dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent event) {
	final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
		//如果是ActionDown那麼停下滾動
        stopNestedScroll();
    }
    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
		// 調用了onTouch()
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
		//如果該View有設置onTouch事件,那麼onTouchEvent就不會被回調是這樣的嗎?我們試試就知道
		###確實是這樣的,設置了onTouch那麼onTouchEvent就不會被調用了
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
}

onTouchEvent()

 switch (action) {
       case MotionEvent.ACTION_UP:
		if (prepressed) {
			//擡起時設置動畫效果
               setPressed(true, x, y);}
			//沒有設置長按監聽
			if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        // 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();
                            }
							//使用Handler發消息的方式觸發onClick,而非直接調用,相當於就是留一些時間
                            if (!post(mPerformClick)) {
                                performClickInternal();
                            }
                        }

上面就在Up事件裏面主要調用了onclick的回調(使用Handler進行),同時還有Down事件,主要時判斷有沒有監聽長按事件,如果有,發送一個延時的消息,看了下默認時500ms,根據配置來獲得。

事件攔截問題:

  • 幾個方法:
    • dispatchTouchEvent() View和ViewGroup共有
    • onInterceptTouchEvent() ViewGroup特有
    • requestDisallowInterceptTouchEvent() 用於子View 這個方法名的意思: 不攔截!!不攔截!!記住

down事件很重要,是否能找到可以分發的子View,完全是在Down事件中進行查找,如果當前ViewGroup的包含的View都返回false,那麼mFistTouchTarget爲空,遊戲結束。後續傳來的Move和Up都不會收到。該次事件分發給ViewGroup的onTouchEvent處理。如果有子View的Down返回的是true,那麼mFirstTouchTarget指向該View,遊戲繼續進行,後續Move和Up進入:是否有請求不攔截,

沒有例子的學習都是耍流氓,ok

	//繼承LinearLayout
    <com.yp.deepstudy.MyLinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorPrimary"
        android:gravity="center"
        android:orientation="vertical">
	//繼承自View
        <com.yp.deepstudy.MyView
            android:layout_width="120dp"
            android:layout_height="120dp"
            android:background="@color/colorAccent" />
    </com.yp.deepstudy.MyLinearLayout>
  • 按下擡起、按下移動擡起,因爲down事件都返回的是false,沒人處理,所以後續Move和Up都接收不到

      // 0代表 down事件
      MyLinearLayout: dispatchTouchEvent0
      MyLinearLayout: onInterceptTouchEvent0
      MyView: dispatchTouchEvent0
      MyView: onTouchEvent0
      MyLinearLayout: onTouchEvent0
    
  • 現在將MyView,的dispatchTouchEvent改成返回true:下面的log表明有控件進行消費(Down中返回true,如果在move中返回true,也是然並卵的),那麼後續事件就將會收到。

      MyLinearLayout: dispatchTouchEvent0
      MyLinearLayout: onInterceptTouchEvent0
      MyView: dispatchTouchEvent0
      MyLinearLayout: dispatchTouchEvent2
      MyLinearLayout: onInterceptTouchEvent2
      MyView: dispatchTouchEvent2
      MyLinearLayout: dispatchTouchEvent1
      MyLinearLayout: onInterceptTouchEvent1
      MyView: dispatchTouchEvent1
    

下一步開始攔截

  • onInterceptTouchEvent中Action_Move的時候返回true

      MyLinearLayout: dispatchTouchEvent_ACTION_DOWN
      MyLinearLayout: onInterceptTouchEvent_ACTION_DOWN
      MyView: dispatchTouchEvent_ACTION_DOWN
      MyView: onTouchEvent_ACTION_DOWN
      MyLinearLayout: dispatchTouchEvent_ACTION_MOVE
      MyLinearLayout: onInterceptTouchEvent_ACTION_MOVE
      MyLinearLayout: 開始攔截 
      //注意下面MyView中收到的事件是:Cancel
      MyView: dispatchTouchEvent_ACTION_CANCEL
      MyView: onTouchEvent_ACTION_CANCEL
      MyLinearLayout: dispatchTouchEvent_ACTION_MOVE
      MyLinearLayout: onTouchEvent_ACTION_MOVE
      MyLinearLayout: dispatchTouchEvent_ACTION_MOVE
      MyLinearLayout: onTouchEvent_ACTION_MOVE
      MyLinearLayout: dispatchTouchEvent_ACTION_UP
      MyLinearLayout: onTouchEvent_ACTION_UP
      //對上面的log進行代碼分析
      final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                      || intercepted; //intercepted爲true
                              if (dispatchTransformedTouchEvent(ev, cancelChild,
                                      target.child, target.pointerIdBits)) {
                                  handled = true;}
      //注意上面第二個參數,因爲攔截了,傳來是true
      dispatchTransformedTouchEvent
      final int oldAction = event.getAction();
      //cancel爲true
      if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
      	//在這設置了爲Cancel,所以父類攔截,子View收到的事件的Cancel,
      	//當連續的下一次事件進入,mFirstTouchTarget爲空,又不是down事件,因此,onInterceptTouchEvent根本不會執行,直接執行父類的dispatchTouchEvent,分發給onTouchEvent
          event.setAction(MotionEvent.ACTION_CANCEL);
          if (child == null) {
              handled = super.dispatchTouchEvent(event);
          } else {
      		//孩子收到cancel事件
              handled = child.dispatchTouchEvent(event);
          }
          event.setAction(oldAction);
          return handled;
      }
    
  • 注意,一旦父類onInteceptTouchEvent就是完全進行了,不能在這個事件流中分發給子View了。

俗話說的好, 任何不配圖的分析都是耍流氓因此:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章