二. View的事件分發機制

不忘初心 砥礪前行, Tomorrow Is Another Day !html

相關文章

本文概要:java

  1. 座標體系.
  2. View的觸摸事件.

一. 座標體系

橫軸:方向從左至右,數值依次增大. 縱軸:方向從上至下,數值依次增大.bash


View與MotionEvent的座標體系-圖片來自《進階之光》

1.1 對於View的座標

  • getLeft/getTop/getBottom/getRight:都是獲取四邊相對於父容器的座標,對應View的left、top、bottom、right四個參數.

left、top、bottom、right與x,y,transLationX,transLationY之間的關係ide

  • x = left + transLationX
  • y = top + transLationY

其中x,y表明View左上角的座標,transLationX,transLationY表明View左上角的座標偏移量.須要明確的是這四個參數都是相對於父容器座標是一種相對座標.post

當在作平移的過程當中,left與top的原始信息不會發生改變,改變的是x,y,transLationX,transLationY的值.學習

1.2 對於MotionEvent的座標

  • getX/getY: 獲取相對於當前View左上角的座標.
  • getRawx/getRawY: 獲取相對於手機屏幕左上角的座標.

二. View的觸摸事件

對於觸摸事件須要明白幾點.動畫

  1. 一個事件流,是指以一個DOWN爲開始,最終以UP爲結束.
    • 另一種以CANCEL爲結束特殊狀況,就是當ViewGroup攔截某個事件時,它會對子view發出CANCEL事件.
  2. 關鍵是對DOWN事件的處理,至關因而一個試探性事件,若是某個View不處理,那麼其他該事件流的事件序列都不會再交給它.
  3. 能夠經過 requestDisallowInterceptTouchEvent:讓ViewGroup不要攔截事件,前提是ViewGroup不攔截DOWN事件.

2.1 觸摸事件總體流程

主要涉及到下面三個方法.ui

  • dispatchTouchEvent: 分發事件
    • 返回true表示事件已經消耗.
  • onInterceptTouchEvent:攔截事件,ViewGroup僅有的方法.
    • 返回true表示攔截.
  • onTouchEvent:處理事件
    • 返回true表示已處理

總體流程具體參考下面僞代碼,我的以爲將觸摸事件流程體現的淋淋盡致.this

/*
 * 經典僞代碼
 */
public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean comsume;
        //ViewGroup是否攔截事件
        if (onInterceptTouchEvent(ev)) {//true攔截,本身處理
            if(!OnTouchListener.onTouch()){//根據OnTouchListener的OnTouch決定是否調用.
                comsume = onTouchEvent(ev);
            }
        } else {//不攔截,交給子View處理
            comsume = child.dispatchTouchEvent(ev);
        }
    }
複製代碼

對於上面僞代碼咱們來對總體流程作個總結spa

  • 當點擊事件達到頂級View(也就是ViewGroup時),會調用ViewGroup的dispatchTouchEvent方法,在此方法中若是onInterceptTouchEvent.
    • True,則表示攔截事件,全部事件都交由它處理,具體處理流程見下一點.
    • False,則表示交由其子View去處理,即調用子view的dispatchTouchEvent.
  • 如此反覆直到事件終止處理

具體處理流程

  • 在處理事件時,若是設置OnTouchListener,那麼會調用onTouch方法.
    • True,則不會回調onTouchEvent.
    • false,則會回調onTouchEvent.
      • 在onTouchEvent方法中,若是設置了OnClickListener,那麼OnClick方法會調用.

若是到這裏,上面介紹的觸摸事件內容都能很清晰的理解,那麼暫時不用看下面原理分析內容,能夠本身試着去分析View的事件分發機制的源碼,會有不同的收穫與感覺.


2.2 原理分析

Activity的分發過程

  • Activity → Window(PhoneWindow) → View(DecorView)

事件由Activity調用dispatchTouchEvent首先傳入到Window,而Window的惟一實現類是PhoneWindow,最終傳遞到頂級View(DecorView),一直往下傳遞.若是這些事件都沒有處理,最終又會交給Activity的onTouchEvent進行處理.

對應源碼

//Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        //調用window的superDispatchTouchEvent
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
    
//PhoneWindow.java 
    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        //調用DecorView的superDispatchTouchEvent
        return mDecor.superDispatchTouchEvent(event);
    }

//DecorView.java
public boolean superDispatchTouchEvent(MotionEvent event) {
        //調用ViewGroup的dispatchTouchEvent
        return super.dispatchTouchEvent(event);
    }
複製代碼

ViewGroup的分發過程

ViewGroup的dispatchTouchEvent分發過程比較複雜分層2個部分去看.

1. 對攔截事件的處理

對應源碼

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //...省略部分代碼
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            /*
             *  1.當是ACTION_DOWN事件時,則重置狀態
             */
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            /*
               2.檢查是否攔截
                mFirstTouchTarget: 記錄消耗事件的子view
             */
            
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {//DOWN事件或者有子View消耗了事件
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {//檢查標記位
                //回調onInterceptTouchEvent
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }

         
複製代碼
2. 尋找能接受事件的子view.

對應源碼

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重疊問題處理
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        /*
                          1. 遍歷全部子View
                         */
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                            // If there is a view that has accessibility focus we want it
                            // to get the event first and if not handled we will perform a
                            // normal dispatch. We may do a double iteration but this is
                            // safer given the timeframe.
                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }

                            /*
                              2. 尋找是否能接受點擊事件
                              canViewReceivePointerEvents:是否在播放動畫
                              isTransformedTouchPointInView:點擊事件的座標是否落在子元素區域內
                             */
                            if (!canViewReceivePointerEvents(child)
                                    || !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);
                            /*
                              3.事件傳遞給子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();
                                //3.1 true,若是處理了事件,則賦值mFirstTouchTarget,跳出循環.
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

                            
                           
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }
                    
                    
            if (mFirstTouchTarget == null) { //3.2 無子view或者false沒有處理事件

                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } 
            //省略部分代碼


//dispatchTransformedTouchEvent方法
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

       
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                //本身處理事件
                handled = super.dispatchTouchEvent(event);
            } else {
                //調用子View的dispatchTouchEvent
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
}
複製代碼

具體流程能夠看註釋,這裏作個小結.

  1. 當尋找到能夠處理點擊事件的子View時,那麼就會將事件傳遞給子View.
  2. 若是沒有找到子View或者子View沒有處理事件,那麼將由自身來處理.

View的處理過程

對應源碼

public boolean dispatchTouchEvent(MotionEvent event) {
        //...省略部分代碼

        boolean result = false;
       
        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //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)) {//onTouch返回false時
                result = true;
            }
        }

        return result;
    }



複製代碼

最後對上面流程作一個小結.

  1. 若是設置了OnTouchListener的OnTouch方法.
    • 返回true,那麼onTouchEvent不會調用.
    • 返回false,則回調onTouchEvent方法.
  2. 在onTouchEvent方法中,當UP事件時,會回調OnClickListener的Onclcik方法.

因而可知優先級爲: OnTouch > onTouchEvent > Onclcik。

因爲本人技術有限,若有錯誤的地方,麻煩你們給我提出來,本人不勝感激,你們一塊兒學習進步.

參考連接:

相關文章
相關標籤/搜索