亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Android View的事件分發(fā)機制深入分析講解

 更新時間:2023年01月29日 08:30:39   作者:Hdnw  
事件分發(fā)從手指觸摸屏幕開始,即產(chǎn)生了觸摸信息,被底層系統(tǒng)捕獲后會傳遞給Android的輸入系統(tǒng)服務IMS,通過Binder把消息發(fā)送到activity,activity會通過phoneWindow、DecorView最終發(fā)送給ViewGroup。這里就直接分析ViewGroup的事件分發(fā)

1.分發(fā)對象-MotionEvent

事件類型有:

1.ACTION_DOWN-----手指剛接觸屏幕

2.ACTION_MOVE------手指在屏幕上移動

3.ACTION_UP------手指從屏幕上松開的一瞬間

4.ACTION_CANCEL-----事件被上層攔截時觸發(fā)

MotionEvent主要的方法:

getX()得到事件發(fā)生的x軸坐標(相對于當前視圖)
getY()得到事件發(fā)生的y軸坐標(相對于當前視圖)
getRawX()得到事件發(fā)生的x軸坐標(相對于屏幕左頂點)
getRawY()得到事件發(fā)生的y軸坐標(相對于屏幕左頂點)

2.如何傳遞事件

1.傳遞流程

底層IMS->ViewRootImpl->activity->viewgroup->view

2.事件分發(fā)的源碼解析

1.Activity對點擊事件的分發(fā)過程

Activity#dispatchTouchEvent()

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
//事件交給Activity所附屬的Window進行分發(fā),如果返回true,循環(huán)結(jié)束,返回false,沒人處理
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
//所有View的onTouchEvent都返回false,那么Activity的onTouchEvent就會被調(diào)用
        return onTouchEvent(ev);
}

Window#superDispatchTouchEvent

public abstract boolean superDispatchTouchEvent(MotionEvent event);

PhoneWindow#superDispatchTouchEvent

public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

DecorView#superDispatchTouchEvent()

 public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
}

ViewGroup#dispatchTouchEvent()

public boolean dispatchTouchEvent(MotionEvent ev) {<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->

2.頂級View對點擊事件的分發(fā)過程

把ViewGroup的dispatchTouchEvent()方法中的代碼進行分段說明

第一段:

描述的是View是否攔截點擊事件這個邏輯

 // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {//事件類型為down或者mFirstTouchTarget有值
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);//詢問是否攔截,方法返回true就攔截
                    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或者mFirstTouchTarget有值時,就不攔截當前事件,否則直接攔截了這個事件。那么mFirstTouchTarget什么時候有值?當ViewGroup不攔截事件并且把事件交給子元素處理時,mFirstTouchTarget就有值并且指向子元素。所以當事件類型為down并且攔截事件,那么mFirstTouchTarget為空,這會讓后面的事件move和up無法滿足mFirstTouchTarget有值的條件,直接無法調(diào)用onInterceptTouchEvent方法。

特殊情況:通過requestDisallowInterceptTouchEvent方法來設置標記位FLAG_DISALLOW_INTERCEPT,ViewGroup就無法攔截除了ACTION_DOWN以外的點擊事件,這個標記位無法影響ACTION_DOWN事件,因為當事件為ACTION_DOWN時,就會重置這個標記位,將導致子View設置的這個標記位無效。

總結(jié):

1.當ViewGroup決定攔截事件后,那么后續(xù)的點擊事件將會默認交給它處理并且不再調(diào)用它的onInterceptTouchEvent方法。

2.當ViewGroup不攔截ACTION_DOWN事件,那么標記位FLAG_DISALLOW_INTERCEPT讓ViewGroup不再攔截事件。

第二段:

當ViewGroup不攔截事件時,分發(fā)事件給子View,看哪個子View處理事件

                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x =
                                isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
                        final float y =
                                isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
                        // Find a child that can receive the event.
                        // Scan children from front to back.
                       //對子元素進行排序
                        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);
                            // 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;
                            }
                            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);
                            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;
                                break;
                            }

遍歷ViewGroup的所有的子元素,判斷子元素是否在播動畫和點擊事件的坐標是否落在子元素的區(qū)域內(nèi),如果是,就能接收到點擊事件,并且事件會傳遞給它來處理。

我們來看一下dispatchTransformedTouchEvent方法

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
            ...
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            ...
}

dispatchTransformedTouchEvent實際上調(diào)用的是子元素的dispatchTouchEvent方法。

如果子元素的dispatchTouchEvent方法返回true,那么mFirstTouchTarget就會被賦值同時跳出for循環(huán)

newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;

這幾行代碼完成了mFirstTouchTarget的賦值并終止對子元素的遍歷,如果子元素的dispatchTouchEvent方法返回false,那么ViewGroup就會把事件分發(fā)給下一個子元素。

其實mFirstTouchTarget真正的賦值是在addTouchTarget方法里面,mFirstTouchTarget是一種單鏈表結(jié)構(gòu)。

    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }

第三段:

執(zhí)行事件

//當前View的事件處理代碼
if (mFirstTouchTarget == null) {
          // No touch targets so treat this as an ordinary view.
           handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
} else {
//子View的事件處理代碼
...

dispatchTransformedTouchEvent方法的第三個參數(shù)為null,則會調(diào)用super.dispatchTouchEvent方法,也就是View的dispatchTouchEvent方法,所以點擊事件給View處理。

View對點擊事件的處理過程

View(不包含ViewGroup)是一個單獨的元素,沒有子元素,只能自己處理事件。

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)) {
                result = true;
            }
     }
    ...
    return result;
}

首先判斷是否設置了OnTouchListener,如果OnTouchListener的onTouch方法返回true,就不會調(diào)用onTouchEvent方法,否則就會調(diào)用onTouchEvent方法。

public boolean onTouchEvent(MotionEvent event) {
           ...
           if ((viewFlags & ENABLED_MASK) == DISABLED
                && (mPrivateFlags4 & PFLAG4_ALLOW_CLICK_WHEN_DISABLED) == 0) {
                if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                     setPressed(false);
                }
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                // A disabled view that is clickable still consumes the touch
                // events, it just doesn't respond to them.
                 return clickable;
          }
               ...
}

不可用狀態(tài)下的View照樣會消耗點擊事件

switch (action) {
    case MotionEvent.ACTION_UP:
        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
         if ((viewFlags & TOOLTIP) == TOOLTIP) {
                  handleTooltipUp();
         }
         ...
         if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
         ...
              if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                           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)) {
                                    performClickInternal();
                                }
                            }
                }     
         } 
         ...
         mIgnoreNextUpEvent = false;
         break;

當ACTION_UP事件發(fā)生時,會觸發(fā)performClick方法,如果View設置了OnClickListener,那么performClick方法內(nèi)部會調(diào)用它的onClick方法。

 private boolean performClickInternal() {
        // Must notify autofill manager before performing the click actions to avoid scenarios where
        // the app has a click listener that changes the state of views the autofill service might
        // be interested on.
        notifyAutofillManagerOnClick();
        return performClick();
    }
public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        //關(guān)鍵代碼,判斷是否設置了onClickListener
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }
        ...
        return result;//最終返回執(zhí)行結(jié)果
}

點擊事件的分發(fā)機制的源碼實現(xiàn)已經(jīng)分析完了。

3.主要方法

1.dispatchTouchEvent:用來進行事件的分發(fā),如果事件可以傳遞給當前View,那么此方法一定會被調(diào)用,返回結(jié)果受當前View的onTouchEvent和下級View的dispatchTouchEvent方法的影響,表示是否消耗當前事件。

2.onInterceptTouchEvent:用來判斷是否攔截某個事件,如果當前View攔截了某個事件,那么在同一事件序列當中,此方法不會被再次調(diào)用,返回結(jié)果表示是否攔截當前事件。

3.onTouchEvent:用來處理點擊事件,返回結(jié)果表示是否消耗當前事件,如果不消耗,則在同一個事件序列中,當前View無法再次接收到事件。

4.requestDisallowInterceptTouchEvent:一般用于子View中,要求父View不攔截事件。

5.dispatchTransformedTouchEvent:如果child不為空,就發(fā)到child的dispatchTouchEvent中,否則發(fā)給自己。

4.事件傳遞中l(wèi)istener

onTouch,performClick和onClick調(diào)用的順序以及onTouch返回值的影響?

當一個View需要處理事件時,View的dispatchTouchEvent方法中,如果設置了OnTouchListener,那么OnTouchListener的onTouch方法會被調(diào)用,當onTouch方法返回true時,onTouchEvent就不會被調(diào)用,當onTouch方法返回false時,onTouchEvent方法就被調(diào)用,在onTouchEvent方法里面進入performClick方法,在performClick方法里面判斷是否設置onClickListener,并且如果設置了onClickListener,那么onClick方法就被調(diào)用,performClick方法就返回true,如果沒有設置了onClickListener,performClick方法就返回false。

總的來說方法調(diào)用的順序為

5.滑動沖突如何用事件分發(fā)處理

滑動沖突定義:當有內(nèi)外兩層View都可以響應事件時,事件由誰來決定。

滑動沖突類型:1.當內(nèi)外兩層View滑動方向不一致

2.當內(nèi)外兩層滑動方向一致的時候

3.兩種情況疊加

解決思路:

內(nèi)部攔截:dispatchTouchEvent+dispatchTransformedTouchEvent

重寫子元素的dispatchTouchEvent方法

down事件分發(fā)給子元素,move事件是看條件的,如果不滿足條件,就把事件交給子元素處理,如果滿足條件,就會取消子元素的處理事件,然后把事件交給父元

public boolean dispatchTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                //down事件,父容器不要攔截我
                parent.requestDisallowInterceptTouchEvent(true);
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                int deltaX = x - mLastX;
                int deltaY = y - mLastY;
                if (父容器需要此類點擊事件) {
                   //父容器攔截我
                    parent.requestDisallowInterceptTouchEvent(false);
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                break;
            }
            default:
                break;
        }
        mLastX = x;
        mLastY = y;
        return super.dispatchTouchEvent(event);
    }

(當move事件時,進入第一塊代碼,調(diào)用intercepted = onInterceptTouchEvent(ev),我們在onInterceptTouchEvent方法中設置不是down事件就返回true,所以intercepted為true,然后第二塊代碼不會執(zhí)行,進入第三塊代碼,因為intercepted為true,所以cancelChild就為true,取消子元素事件執(zhí)行,調(diào)用dispatchTransformedTouchEvent方法,cancel為true->

event.setAction(MotionEvent.ACTION_CANCEL)->handled = child.dispatchTouchEvent(event)

把mFirstTouchTarget設置為空,所以到下一個move事件來的時候,mFirstTouchTarget是為空的,在第一段代碼中intercepted為true,第二段代碼不執(zhí)行,第三塊代碼走dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS),即由當前View的事件處理代碼(父元素))

重寫父元素的onInterceptTouchEvent方法

當為down事件時,要return false,因為在ViewGroup的dispatchTouchEvent方法中,當為down事件時,會調(diào)用resetTouchState()方法,在resetTouchState()方法里面會重置狀態(tài),把mGroupFlags也重置,這樣會導致在前面的parent.requestDisallowInterceptTouchEvent(true)沒有用,所以我們在onInterceptTouchEvent方法里面要設置為down事件時返回false,因為在down事件時onInterceptTouchEvent一定會執(zhí)行。

   public boolean onInterceptTouchEvent(MotionEvent event) {
        int action = event.getAction();
        if (action == MotionEvent.ACTION_DOWN) {
            super.onInterceptTouchEvent(event);
            return false;
        } else {
            return true;
        }
    }

外部攔截:onInterceptTouchEvent

點擊事件先經(jīng)過父容器的攔截處理,如果父容器需要這件事就攔截,不需要就不攔截。

public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean intercepted = false;
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                intercepted = false;
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                if (滿足父容器的攔截要求) {
                    intercepted = true;
                } else {
                    intercepted = false;
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                intercepted = false;
                break;
            }
            default:
                break;
        }
        mLastXIntercept = x;
        mLastYIntercept = y;
        return intercepted;
    }

到此這篇關(guān)于Android View的事件分發(fā)機制深入分析講解的文章就介紹到這了,更多相關(guān)Android View事件分發(fā)機制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論