View和ViewGroup事件分發理解

一:View事件分發理解

1.View事件執行的順序爲View.onDispatcher-->View.setOnTouchEvent-->View.onTouchEvent,如果setOnTouchEvent中onTouch方法返回true表示該事件被消費,則表示

不執行onTouchEvent,onTouch執行的條件是註冊了OnTouch事件,並且View爲可點擊狀態,具體代碼如下

  1.  public boolean dispatchTouchEvent(MotionEvent event) {  
  2.         if (!onFilterTouchEventForSecurity(event)) {  
  3.             return false;  
  4.         }  
  5.   
  6.         if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
  7.                 mOnTouchListener.onTouch(this, event)) {  
  8.             return true;  
  9.         }  
  10.         return onTouchEvent(event);  
  11.     }  

2.onTouch方法會優先於OnClickLisener執行,因爲OnClick是在onTouchEvent中調用當dispatchTouchEvent在進行事件分發的時候,只有前一個action返回true,纔會觸發下一個action

  1. public boolean onTouchEvent(MotionEvent event) {  
  2.     final int viewFlags = mViewFlags;  
  3.     if ((viewFlags & ENABLED_MASK) == DISABLED) {  
  4.         // A disabled view that is clickable still consumes the touch  
  5.         // events, it just doesn't respond to them.  
  6.         return (((viewFlags & CLICKABLE) == CLICKABLE ||  
  7.                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));  
  8.     }  
  9.     if (mTouchDelegate != null) {  
  10.         if (mTouchDelegate.onTouchEvent(event)) {  
  11.             return true;  
  12.         }  
  13.     }  
  14.     if (((viewFlags & CLICKABLE) == CLICKABLE ||  
  15.             (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
  16.         switch (event.getAction()) {  
  17.             case MotionEvent.ACTION_UP:  
  18.                 boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
  19.                 if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  
  20.                     // take focus if we don't have it already and we should in  
  21.                     // touch mode.  
  22.                     boolean focusTaken = false;  
  23.                     if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  
  24.                         focusTaken = requestFocus();  
  25.                     }  
  26.                     if (!mHasPerformedLongPress) {  
  27.                         // This is a tap, so remove the longpress check  
  28.                         removeLongPressCallback();  
  29.                         // Only perform take click actions if we were in the pressed state  
  30.                         if (!focusTaken) {  
  31.                             // Use a Runnable and post this rather than calling  
  32.                             // performClick directly. This lets other visual state  
  33.                             // of the view update before click actions start.  
  34.                             if (mPerformClick == null) {  
  35.                                 mPerformClick = new PerformClick();  
  36.                             }  
  37.                             if (!post(mPerformClick)) {  
  38.                                 performClick();  
  39.                             }  
  40.                         }  
  41.                     }  
  42.                     if (mUnsetPressedState == null) {  
  43.                         mUnsetPressedState = new UnsetPressedState();  
  44.                     }  
  45.                     if (prepressed) {  
  46.                         mPrivateFlags |= PRESSED;  
  47.                         refreshDrawableState();  
  48.                         postDelayed(mUnsetPressedState,  
  49.                                 ViewConfiguration.getPressedStateDuration());  
  50.                     } else if (!post(mUnsetPressedState)) {  
  51.                         // If the post failed, unpress right now  
  52.                         mUnsetPressedState.run();  
  53.                     }  
  54.                     removeTapCallback();  
  55.                 }  
  56.                 break;  
  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.             case MotionEvent.ACTION_CANCEL:  
  66.                 mPrivateFlags &= ~PRESSED;  
  67.                 refreshDrawableState();  
  68.                 removeTapCallback();  
  69.                 break;  
  70.             case MotionEvent.ACTION_MOVE:  
  71.                 final int x = (int) event.getX();  
  72.                 final int y = (int) event.getY();  
  73.                 // Be lenient about moving outside of buttons  
  74.                 int slop = mTouchSlop;  
  75.                 if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
  76.                         (y < 0 - slop) || (y >= getHeight() + slop)) {  
  77.                     // Outside button  
  78.                     removeTapCallback();  
  79.                     if ((mPrivateFlags & PRESSED) != 0) {  
  80.                         // Remove any future long press/tap checks  
  81.                         removeLongPressCallback();  
  82.                         // Need to switch from pressed to not pressed  
  83.                         mPrivateFlags &= ~PRESSED;  
  84.                         refreshDrawableState();  
  85.                     }  
  86.                 }  
  87.                 break;  
  88.         }  
  89.         return true;  
  90.     }  
  91.     return false;  
  92. }  

2.setOnLongClickListener和setOnClickLisenter是否只能執行一個?

如果設置setOnLongClickLisener中返回爲false,兩個都會執行,如果返回true,會屏蔽setOnClickLisener方法

二  ViewGroup事件分發理解

1.ViewGroup繼承View覆蓋了其dispatchTouchEvent方法,當在Activity中點擊一個button,首先會調用該Activity中的dispatchTouchEvent,會拿到setContentVIew中

根節點調用其dispatchTouchEvent方法。

2.在ACTION_DOWN裏,首選判斷事件是否被攔截,如果沒有被攔截,則尋找子View,一旦子View.dispatchTouchEvent 則將這個子View賦值給Target,代表這個子View會消費該事件,如果沒有找到這個子View,會調用Super.dispatchTouchEvent 也就是自身來處理該事件,如果自身也不能處理就會調用Activity中onTouchEvent,最終會調用target.dispatchTouchEvent(ev).

3.如果攔截了事件,那麼會調用target的ACTION_CANCLE方法,並將target=null,返回true,事件不會向下傳遞

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    //這個最終傳遞到setContentView對應的View    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    //如果ContentView沒有對時間進行處理,統一由ActivityonTouchEvent()來處理
    return onTouchEvent(ev);
}

  1. public boolean dispatchTouchEvent(MotionEvent ev) {  
  2.     final int action = ev.getAction();  
  3.     final float xf = ev.getX();  
  4.     final float yf = ev.getY();  
  5.     final float scrolledXFloat = xf + mScrollX;  
  6.     final float scrolledYFloat = yf + mScrollY;  
  7.     final Rect frame = mTempRect;  
  8.     boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
  9.     if (action == MotionEvent.ACTION_DOWN) {  
  10.         if (mMotionTarget != null) {  
  11.             mMotionTarget = null;  
  12.         }  
  13.         if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
  14.             ev.setAction(MotionEvent.ACTION_DOWN);  
  15.             final int scrolledXInt = (int) scrolledXFloat;  
  16.             final int scrolledYInt = (int) scrolledYFloat;  
  17.             final View[] children = mChildren;  
  18.             final int count = mChildrenCount;  
  19.             for (int i = count - 1; i >= 0; i--) {  
  20.                 final View child = children[i];  
  21.                 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
  22.                         || child.getAnimation() != null) {  
  23.                     child.getHitRect(frame);  
  24.                     if (frame.contains(scrolledXInt, scrolledYInt)) {  
  25.                         final float xc = scrolledXFloat - child.mLeft;  
  26.                         final float yc = scrolledYFloat - child.mTop;  
  27.                         ev.setLocation(xc, yc);  
  28.                         child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  29.                         if (child.dispatchTouchEvent(ev))  {  
  30.                             mMotionTarget = child;  
  31.                             return true;  
  32.                         }  
  33.                     }  
  34.                 }  
  35.             }  
  36.         }  
  37.     }  
  38.     boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  
  39.             (action == MotionEvent.ACTION_CANCEL);  
  40.     if (isUpOrCancel) {  
  41.         mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
  42.     }  
  43.     final View target = mMotionTarget;  
  44.     if (target == null) {  
  45.         ev.setLocation(xf, yf);  
  46.         if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
  47.             ev.setAction(MotionEvent.ACTION_CANCEL);  
  48.             mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  49.         }  
  50.         return super.dispatchTouchEvent(ev);  
  51.     }  
  52.     if (!disallowIntercept && onInterceptTouchEvent(ev)) {  
  53.         final float xc = scrolledXFloat - (float) target.mLeft;  
  54.         final float yc = scrolledYFloat - (float) target.mTop;  
  55.         mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  56.         ev.setAction(MotionEvent.ACTION_CANCEL);  
  57.         ev.setLocation(xc, yc);  
  58.         if (!target.dispatchTouchEvent(ev)) {  
  59.         }  
  60.         mMotionTarget = null;  
  61.         return true;  
  62.     }  
  63.     if (isUpOrCancel) {  
  64.         mMotionTarget = null;  
  65.     }  
  66.     final float xc = scrolledXFloat - (float) target.mLeft;  
  67.     final float yc = scrolledYFloat - (float) target.mTop;  
  68.     ev.setLocation(xc, yc);  
  69.     if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
  70.         ev.setAction(MotionEvent.ACTION_CANCEL);  
  71.         target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  72.         mMotionTarget = null;  
  73.     }  
  74.     return target.dispatchTouchEvent(ev);  
常見問題分析解決:
  1. touch監聽器沒被調用到? 
    View.dispatchTouchEvent(),ViewGroup.dispatchTouchEvent() 
    a)如果是View非使能,直接用setEnabled(true) 
    b)如果是事件被這個View的viewparent攔截了。可以修改這個viewparent的onInterceptTouchTouchEvent(),或者在這個View中調用getParent().requestDisallowInterceptTouchEvent()

  2. 雙層滑動模塊嵌套後發生滑動不了的現象? 
    ViewGroup.dispatchTouchEvent() 
    如果是事件被這個View的viewparent攔截了。可以修改這個viewparent的onInterceptTouchTouchEvent(),或者在這個View中調用getParent().requestDisallowInterceptTouchEvent()

  3. 設置了onClickListener後,點擊View沒有反應? 
    View.onTouchEvent() 
    a)如果是View非使能,直接用setEnabled(true) 
    b)可能覆蓋了onTouchEvent(),需要在覆蓋的方法調用super.onTouchEvent()或者手動調用performClick()

  4. 點擊兩下View才調用onClickListener的bug? 
    View.onTouchEvent() 
    這個其實是安卓的設計,當某個View調用了setFocusableInTouchMode(true)後,第一次點擊會引起這個View的focus,第二次點擊纔會調用onClickListener,只需要設置setFocusableInTouchMode(false)即可。

5.ListView中Item帶Button,Button click事件和OnItemClick事件的衝突
       1.這種可以在itemview中進行事件攔截,攔截後不會調用button dispatcherTouchEvent方法,可以在adapter中通過requestDisallowInterceptTouchEvent(true)進行攔截

   2.覆蓋Button的onTouchEvent方法,返回false

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