欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android事件分發(fā)機制示例分析

 更新時間:2022年08月27日 10:17:45   作者:niuyongzhi  
在說事件分發(fā)之前,我們先想一個問題,在APP中我們點擊一個View的時候,事件是如何傳遞到這個View的呢?其實這就是我理解的事件分發(fā)機制。即當手指點擊屏幕時,事件傳遞到具體View的過程
  • Android事件類型
public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
                break;
            case MotionEvent.ACTION_CANCEL:
                break;
        }
        return true;
    }

如上圖所示android主要有四種事件類型,Down、Move、Up、Cancel。

在一次事件流中:

Down觸發(fā)一次,手指在屏幕按下時。

Move事件會觸發(fā)0次或多次,手指在屏幕上滑動時。

Up事件會觸發(fā)0次或一次,手指在屏幕抬起時。

Cancel事件會觸發(fā)0次或一次,滑動超出空間邊界時。

  • android App層事件分發(fā)機制調(diào)用鏈是怎么樣的?

App層事件分發(fā)的入口在Activity中,dispatchTouchEvent是在Framework層的WindowManagerService掉用的,WMS中的事件是來源于Native層,Linux層,這塊的事件傳遞有機會在寫文章進行分析,這篇文章主要分析Android事件分發(fā)機制在App成是如何展現(xiàn)的。

 public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

getWindow()返回的是PhoneWindow對象。

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

mDecor是DecorView,是PhoneWindow的一個內(nèi)部類,繼承FrameLayout。

FrameLayout繼承自ViewGroup,接下來看事件傳遞在ViewGroup中是如何處理的。事件傳遞的主要邏輯就在ViewGroup中。

接下來看ViewGroup,dispatchToucheEvent(MotionEvent ev)是如何對事件進行分發(fā)的。

public class ViewGroup {
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean handled = false;
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;
        // Check for interception.
        //按下后,第一次調(diào)用時actionMasked=down,mFirstTouchTarget=null
        //一直按著 actionMasked=move, 如果mFirstTouchTarget!=null說明有子view處理了touch事件
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                //調(diào)用子類復寫的方法 如果為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.
            //如果沒有子view處理touch事件,并且不是第一次按下的down事件,group會對touch事件進行攔截
            intercepted = true;
        }
        TouchTarget newTouchTarget = null;
        //第一次按下時,判斷view是否處理事件的標志
        boolean alreadyDispatchedToNewTouchTarget = false;
        //如果不攔截,則進入事件分發(fā)
        if (!canceled && !intercepted) {
            //注意,只有ACTION_DOWN時才會遍歷子View
            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 ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final View[] children = mChildren;
                    //開始遍歷子view
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);
                        //isTransformedTouchPointInView 通過比對坐標,來判斷事件是否落在了child上,
                        // true是,false不是
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            // 如果事件沒有落在child上,則跳過此次循環(huán),執(zhí)行下一次循環(huán)
                            continue;
                        }
                        //通過遍歷TouchTarget鏈表查找TouchTarget,如果找到了,則break跳出循環(huán)
                        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;
                        }
                        //第一次進來newTouchTarget==null,進入下面的邏輯
                        //如果dispatchTransformedTouchEvent 返回true,表示子view對事件進行了處理,則跳出此循環(huán),
                        // 不會再向子view進行分發(fā)
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            // Child wants to receive touch within its bounds.
                            //將child緩存到TouchTarget的鏈表中
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            //注意這個標志位,true,下面的邏輯會用到
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }
 
                    }
                    if (preorderedList != null) preorderedList.clear();
                }
            }
        }
        // Dispatch to touch targets.
        //ACTION_DOWN和ACTION_MOVE 的時都會執(zhí)行下面邏輯
        //mFirstTouchTarget==null 說明沒有子View處理touch事件,dispatchTransformedTouchEvent的第三個參數(shù)為null。
        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 鏈表進行遍歷,TouchTarget里緩存了處理事件的view,
            //一次完整事件,move會調(diào)用多次,所以在ACTION_DOWN時找到的對應的view,緩存起來,
            // 而不是每次都遍歷ViewGroup的子View,這樣太損壞性能
            TouchTarget target = mFirstTouchTarget;
            //開始對鏈表進行遍歷,為啥是一個鏈表呢,這是為了進行多點觸摸,會有多個view響應觸摸事件。
            while (target != null) {
                final TouchTarget next = target.next;
                //alreadyDispatchedToNewTouchTarget ==true ,并且緩存的target和newTouchTarget是同一個
                //直接將handled置為true,防止ACTION_DOWN時dispatchTransformedTouchEvent方法重復調(diào)用
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                }
                target = next;
            }
        }
        //將handled 返回dispatchTouchEvent的調(diào)用者。
        return handled;
    }
    /**
     * Gets the touch target for specified child view.
     * Returns null if not found.
     * 通過遍歷TouchTarget 鏈表,找到touch target
     */
    private TouchTarget getTouchTarget(@NonNull View child) {
        for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
            if (target.child == child) {
                return target;
            }
        }
        return null;
    }
    /**
     * Adds a touch target for specified child to the beginning of the list.
     * Assumes the target child is not already present.
     * 將TouchTarget和view進行關(guān)聯(lián),并且把這個TouchTarget放在鏈表的頭部
     */
    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                                                  View child, int desiredPointerIdBits) {
        final boolean handled;
        // 只看最關(guān)鍵的兩行代碼
        if (child == null) {
            //如果child為null,沒有子view處理touch事件,則調(diào)用父類View的dispatchTouchEvent。
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            //如果child不為空,則調(diào)用child view 的dispatchTouchEvent
            //這行代碼很關(guān)鍵,如果child是ViewGroup則繼續(xù)在ViewGoup的進行事件分發(fā),這會是一個遞歸調(diào)用
            //如果child 是一個View,則調(diào)用View的dispatchTouchEvent,進行事件的分發(fā)。
            handled = child.dispatchTouchEvent(transformedEvent);
        }
        //以上兩行代碼最終會調(diào)用到View的dispatchTouchEvent方法。
        return handled;
    }
}

接下來看View dispatchTouchEvent如何處理的

public class View {
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean result = false;
        ListenerInfo li = mListenerInfo;
        //如果view設(shè)置了mOnTouchListener,先調(diào)用mOnTouchListener.onTouch,
        //如果返回true,則表示消費了此事件,不在向下傳遞,為啥這樣說?那是因為,通過他的返回值,進行了邏輯判斷
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        //如果result為false,則調(diào)用View.onTouchEvent方法
        if (!result && onTouchEvent(event)) {
            result = true;
        }
        return result;
    }
    public boolean onTouchEvent(MotionEvent event) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                if (mPerformClick == null) {
                    mPerformClick = new PerformClick();
                }
                if (!post(mPerformClick)) {
                    //調(diào)用performClick mOnClickListener.onClick()方法
                    performClickInternal();
                }
                break;
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_CANCEL:
                break;
            case MotionEvent.ACTION_MOVE:
                break;
        }
        return false;
    }
    public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }
        return result;
    }
}

如果仔細看了ViewGroup和View事件分發(fā)邏輯代碼。我們可以總結(jié)為以下幾點。

1.ViewGroup接收到DispatchTouchEvent調(diào)用后,先調(diào)用了onInterceptTouchEvent,通過返回值進行判斷是否對事件進行攔截。我們可以在代碼中復寫這個方法來進行事件的攔截。

2.down事件后,ViewGoup會遍歷子View,找到處理Touch事件的View,并且緩存到了TouchTarget鏈表中,一次事件流中,只有down的時候才會進行子View的遍歷。

只有做的好處提升了性能,緩存的鏈表中也是為了多點觸摸,會有多個view響應觸摸事件。

move事件時,則會從這個鏈表中進行遍歷,通過調(diào)用dispatchTransformedTouchEvent方法進行事件的傳遞。

  • 在dispatchTransformedTouchEvent方法中,根據(jù)child的值是否為null進入不同的分發(fā)流程。

1)如果為null說明沒有子view沒有響應事件,則調(diào)用父類View的dispatchTouchEvent--->onTouchEvent方法。

2)如果child值不為null。則調(diào)用child的dispatchTouchEvent方法,

此時如果child是一個ViewGoup則還是執(zhí)行ViewGoup中的分發(fā)邏輯,進行遞歸調(diào)用。

如果child是一個View,則調(diào)用View的dispatchTouchEvent--->onTouchEvent方法。

  • 在View的dispatchTouchEvent方法中:

1)listener的onTouch優(yōu)先級要高于View的onTouchEvent

2)會先調(diào)用mOnTouchListener.onTouch方法,并根據(jù)返回值進行判斷是否消費事件。

3)如果上面的返回值為false,則會調(diào)用View的onTouchEvent方法。

5.在View的onTouchEvent方法中UP事件時,才會調(diào)用View設(shè)置的onClickListener的監(jiān)聽事件。

這就是為什么了,同時給一個View設(shè)置了onTouch監(jiān)聽和lonClickListener監(jiān)聽時,當onTouch返回true無法調(diào)用onClick事件的原因。

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

相關(guān)文章

最新評論