Android--焦點(diǎn)問(wèn)題以及討論事件傳遞機(jī)制問(wèn)題(結(jié)合部分相關(guān)源碼)

還是之前的項(xiàng)目中的一些東西掸绞,繼續(xù)抽出來(lái)給大家淹遵。

文章結(jié)構(gòu):(1)展現(xiàn)焦點(diǎn)問(wèn)題(以及一些體驗(yàn)交互的狀態(tài))继找;(2)分析焦點(diǎn)問(wèn)題遂跟,詳解兩個(gè)屬性;(3)結(jié)合部分相關(guān)源碼討論事件傳遞機(jī)制;


一幻锁、展現(xiàn)焦點(diǎn)問(wèn)題:

(1)如果對(duì)我下面給的demo不加一些屬性處理凯亮,效果如下:

這里寫圖片描述

也就是看不到上面的輪播圖,這樣的話越败,就是recyclerview搶占了activity的焦點(diǎn)咯触幼。

那么demo中硼瓣,我們?cè)趺唇鉀Q的呢究飞??堂鲤?

<!-- 我就加了一句屬性android:focusableInTouchMode="true"  亿傅,就把焦點(diǎn)交給輪播圖-->
             <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical"
                app:layout_collapseMode="pin"
                android:focusableInTouchMode="true">

                <com.bigkoo.convenientbanner.ConvenientBanner
                    android:id="@+id/banner"
                    android:layout_width="match_parent"
                    android:layout_height="200dp"
                    app:canLoop="true" />
            </LinearLayout>

為什么這樣做呢?瘟栖?葵擎?

我何用做是為了給輪播圖控件施加焦點(diǎn)嘛,我就在輪播圖的父控件設(shè)定了android:focusableInTouchMode=true半哟,也就攔截了輪播圖默認(rèn)行為讓父控件得到高亮得到焦點(diǎn)酬滤,當(dāng)然也搶奪了recyclerview想要的焦點(diǎn)。


(2)另外寓涨,大家在編寫自己的登錄頁(yè)面時(shí)也經(jīng)常遇到焦點(diǎn)問(wèn)題吧盯串??是什么導(dǎo)致的呢戒良?是EditTextL迥蟆!它自動(dòng)搶奪焦點(diǎn)的E雌椤几缭!

EditText這種即使在TouchMode下,依然需要獲取焦點(diǎn)的控件

怎么解決呢?

解決:在EditText的父級(jí)控件中找一個(gè)沃呢,設(shè)置成

   android:focusable="true"  
   android:focusableInTouchMode="true"

這樣年栓,就把EditText默認(rèn)的行為截?cái)嗔耍”∷〔蛔屗詣?dòng)奪取焦點(diǎn)韵洋。

(3)講述另外一些體驗(yàn)交互狀態(tài)

Select

這里寫圖片描述

Focusable in Touch Mode

也就是我們上面的edittext,點(diǎn)擊去獲取焦點(diǎn)黄锤。

Focus

就是焦點(diǎn)模式咯搪缨。


二、分析焦點(diǎn)問(wèn)題鸵熟,詳解兩個(gè)屬性:

focusableInTouchMode跟focusable有什么區(qū)別副编?

1.要理解這個(gè)屬性,首先你得知道,Android不是只面向手機(jī)的,它還有可能被安裝在電視等非觸摸輸入設(shè)備上.即使是在手機(jī)上,目前很多手機(jī)也都支持鍵盤輸入了。

2.focusable這種屬性,更多的是為了解決非觸摸輸入的,因?yàn)槟阌眠b控器或鍵盤點(diǎn)擊控件,就必然要涉及到焦點(diǎn)的問(wèn)題,只有可以獲得焦點(diǎn)的控件才能響應(yīng)鍵盤或者遙控器或者軌跡球的確定事件.

3.focusableInTouchMode.這個(gè)屬性的意思一如字面所述,就是在進(jìn)入觸摸輸入模式后,該控件是否還有獲得焦點(diǎn)的能力.

什么意思呢流强?痹届?再通俗點(diǎn)呻待,

對(duì)于一個(gè)擁有觸摸屏功能的設(shè)備而言, 一旦用戶用手點(diǎn)擊屏幕, 設(shè)備會(huì)立刻進(jìn)入touch mode。這時(shí)候被點(diǎn)擊的控件只有設(shè)置android:focusableInTouchMode為true的時(shí)候才會(huì)獲得focus队腐,比如EditText控件蚕捉。其他可以觸摸的控件比如Button。

然后其android:focusableInTouchMode默認(rèn)為false, 當(dāng)被點(diǎn)擊的時(shí)候不會(huì)獲取焦點(diǎn)柴淘,它們只是簡(jiǎn)單地執(zhí)行onClick事件而已迫淹。

所以以上兩個(gè)屬性就針對(duì)這幾種的touch情況啦。交互體驗(yàn)是十分地不同的N稀A舶尽!

所以我們大致看下這份源碼的接口聲明第股,我們就可以清晰認(rèn)知focus跟touch是極大的不同应民。

public static class ListenerInfo {  
        /** 
         * Listener used to dispatch focus change events. 
         * This field should be made private, so it is hidden from the SDK. 
         * {@hide} 
         */  
        protected OnFocusChangeListener mOnFocusChangeListener;  
  
        /** 
         * Listeners for layout change events. 
         */  
        private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;  
  
        /** 
         * Listeners for attach events. 
         */  
        private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;  
  
        /** 
         * Listener used to dispatch click events. 
         * This field should be made private, so it is hidden from the SDK. 
         * {@hide} 
         */  
        public OnClickListener mOnClickListener;  
  
        /** 
         * Listener used to dispatch long click events. 
         * This field should be made private, so it is hidden from the SDK. 
         * {@hide} 
         */  
        protected OnLongClickListener mOnLongClickListener;  
  
        /** 
         * Listener used to build the context menu. 
         * This field should be made private, so it is hidden from the SDK. 
         * {@hide} 
         */  
        protected OnCreateContextMenuListener mOnCreateContextMenuListener;  
  
        private OnKeyListener mOnKeyListener;  
  
        private OnTouchListener mOnTouchListener;  
  
        private OnHoverListener mOnHoverListener;  
  
        private OnGenericMotionListener mOnGenericMotionListener;  
  
        private OnDragListener mOnDragListener;  
  
        private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;  
  
        OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;  
    }  

三、討論事件傳遞機(jī)制

焦點(diǎn)問(wèn)題也是涉及到事件機(jī)制的夕吻,所以我們就順便進(jìn)一步地去討論這個(gè)android事件機(jī)制咯诲锹。

關(guān)于事件的傳遞,我們主要是關(guān)注幾個(gè)問(wèn)題:(1)事件怎么傳遞涉馅?它的傳遞流程是怎樣归园?(2)事件是怎么消費(fèi)的?控漠?(3)自定義view也事件沖突時(shí)蔓倍,我們?cè)趺刺幚恚浚?)源碼是怎么定義這個(gè)事件機(jī)制的盐捷?偶翅?

(1)事件怎么傳遞?它的傳遞流程是怎樣碉渡?

首先由Activity分發(fā)聚谁,分發(fā)給根View,也就是DecorView(DecorView為整個(gè)Window界面的最頂層View)滞诺。

然后由根View分發(fā)到子的ViewGroup形导,再由各個(gè)ViewGroup分發(fā)給子View

這里寫圖片描述

好了看下源碼:

//攔截事件  
@Override  
public boolean onInterceptTouchEvent(MotionEvent ev) {  
    // TODO Auto-generated method stub  
    return super.onInterceptTouchEvent(ev);  
}  
  
//處理事件  
@Override  
public boolean onTouchEvent(MotionEvent ev) {  
    // TODO Auto-generated method stub  
    return super.onTouchEvent(ev);  
}  
  
//分發(fā)事件  
@Override  
public boolean dispatchTouchEvent(MotionEvent ev) {  
    // TODO Auto-generated method stub  
    return super.dispatchTouchEvent(ev);  
} 

我們來(lái)仔細(xì)討論ViewGroup事件的傳遞機(jī)制:

這里寫圖片描述

雖然那個(gè)圖真的很棒,但是講得不夠清晰呢习霹,下面我將它具體講述朵耕。

(1)當(dāng)我們點(diǎn)擊viewC時(shí),就會(huì)觸發(fā)事件淋叶,然后事件傳遞給viewgroupA阎曹,viewgroupA它首先會(huì)執(zhí)行dispatchTouchEvent來(lái)調(diào)用onInterceptTouchEvent判斷本group是否可以處理,return ture則交由onTouchEvent處理事件,return false則使用dispatchTouchEvent往下傳遞事件处嫌。

(2)往下傳遞過(guò)來(lái)的事件由viewgroupB的onInterceptTouchEvent攔截栅贴,問(wèn)自己能否處理該事件,能則處理熏迹,不能則往下繼續(xù)傳遞檐薯。同理viewC的這一步流程。

(3)當(dāng)傳遞到最終的viewC的時(shí)候注暗,如果不能夠處理該觸發(fā)事件坛缕,是會(huì)重新回傳給父控件的!S汛妗5簧拧陶衅!


(2)事件是怎么消費(fèi)的屡立??

就是dispatchTouchEvent判斷自己能處理后就調(diào)用自己的onTouchEvent進(jìn)行處理搀军。

(3)自定義view時(shí)膨俐,我們?cè)趺刺幚恚?/h2>

點(diǎn)這里看例子。感謝那位博主的精妙例子罩句。

(4)源碼是怎么定義這個(gè)事件機(jī)制的焚刺??

事件即MotionEvent:

(1)MotionEvent.ACTION_DOWN 按下View门烂,是所有事件的開(kāi)始

(2)MotionEvent.ACTION_MOVE 滑動(dòng)事件

(3)MotionEvent.ACTION_UP 與down對(duì)應(yīng)乳愉,表示抬起

//負(fù)責(zé)分發(fā)事件的這位,很重要的代碼
//看之前先記住這句話:mFirstTouchTarget為null就是說(shuō)Touch事件未被消費(fèi).  至于是什么內(nèi)容請(qǐng)仔細(xì)閱讀下文
 @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        // If the event targets the accessibility focused view and this is it, start
        // normal event dispatch. Maybe a descendant is what will handle the click.
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;//標(biāo)記這個(gè)為事件暫存值

        /*
            一屯远、因?yàn)锳CTION_DOWN是一系列事件的開(kāi)端,當(dāng)是ACTION_DOWN時(shí)進(jìn)行一些初始化操作. 
        */
            // Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                //下面的方法中有一個(gè)非常重要的操作:clearTouchTargets();將mFirstTouchTarget設(shè)置為null!!!!隨后在resetTouchState()中重置Touch狀態(tài)標(biāo)識(shí)
                cancelAndClearTouchTargets(ev);//清除以往的Touch狀態(tài)(state)開(kāi)始新的手勢(shì)(gesture)  
                resetTouchState();
            }
        /*
            二蔓姚、檢查是否要攔截
        */
            // Check for interception.
            final boolean intercepted;//使用變量intercepted來(lái)標(biāo)記ViewGroup是否攔截Touch事件的傳遞. 
            // 事件為ACTION_DOWN或者mFirstTouchTarget不為null(即已經(jīng)找到能夠接收touch事件的目標(biāo)組件)時(shí)if成立  
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                //判斷disallowIntercept(禁止攔截)標(biāo)志位    
                //因?yàn)樵谄渌胤娇赡苷{(diào)用了requestDisallowInterceptTouchEvent(boolean disallowIntercept)    
                //從而禁止執(zhí)行是否需要攔截的判斷(有點(diǎn)拗口~其實(shí)看requestDisallowInterceptTouchEvent()方法名就可明白) 
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                 //當(dāng)沒(méi)有禁止攔截判斷時(shí)(即disallowIntercept為false)調(diào)用onInterceptTouchEvent(ev)方法    
                if (!disallowIntercept) {
                //既然disallowIntercept為false那么就調(diào)用onInterceptTouchEvent()方法將結(jié)果賦值給intercepted    
                    //常說(shuō)事件傳遞中的流程是:dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent    
                    //其實(shí)在這就是一個(gè)體現(xiàn),在dispatchTouchEvent()中調(diào)用了onInterceptTouchEvent()    
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                /當(dāng)禁止攔截判斷時(shí)(即disallowIntercept為true)設(shè)置intercepted = false  
                    intercepted = false;
                }
            } else {
                  //當(dāng)事件不是ACTION_DOWN并且mFirstTouchTarget為null(即沒(méi)有Touch的目標(biāo)組件)時(shí)    
                //設(shè)置 intercepted = true表示ViewGroup執(zhí)行Touch事件攔截的操作。 
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }

            // If intercepted, start normal event dispatch. Also if there is already
            // a view that is handling the gesture, do normal event dispatch.
            if (intercepted || mFirstTouchTarget != null) {
                ev.setTargetAccessibilityFocus(false);
            }
            /*
                三慨丐、檢查cancel
            */
            // Check for cancelation.
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            /**  
             * 第四步:事件分發(fā)
             */    
            // Update list of touch targets for pointer down, if needed.
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null;//獲取新的touch事件
            boolean alreadyDispatchedToNewTouchTarget = false;//判別是否分發(fā)給新的事件目標(biāo)
            //不是ACTION_CANCEL并且ViewGroup的攔截標(biāo)志位intercepted為false(不攔截)  
            if (!canceled && !intercepted) {

                // If the event is targeting accessiiblity focus we give it to the
                // view that has accessibility focus and if it does not handle it
                // we clear the flag and dispatch the event to all children as usual.
                // We are looking up the accessibility focused host to avoid keeping
                // state since these events are very rare.
                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;
                 //處理ACTION_DOWN事件
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    final int actionIndex = ev.getActionIndex(); // always 0 for down
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

                    // Clean up earlier touch targets for this pointer id in case they
                    // have become out of sync.
                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;
                    // 依據(jù)Touch坐標(biāo)尋找子View來(lái)接收Touch事件
                    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.
                        final ArrayList<View> preorderedList = buildOrderedChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        // 遍歷子View判斷哪個(gè)子View接受Touch事件 
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = customOrder
                                    ? getChildDrawingOrder(childrenCount, i) : i;
                            final View child = (preorderedList == null)
                                    ? children[childIndex] : preorderedList.get(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 (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                            // 找到接收Touch事件的子View!!!!!!!即為newTouchTarget.    
                                // 既然已經(jīng)找到了,所以執(zhí)行break跳出for循環(huán)   
                                // 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);
                            //調(diào)用方法dispatchTransformedTouchEvent()將Touch事件傳遞給子View做,遞歸處理(也就是遍歷該子View的View樹(shù)) ,將Touch事件傳遞給特定的子View的onTouchEvent返回true(即Touch事件被消費(fèi))那么就滿足該if條件.
                            //如果dispatchTransformedTouchEvent()返回true即子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;
                                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();
                    }
                    /**  
                     * 該if條件表示:  
                     * 經(jīng)過(guò)前面的for循環(huán)沒(méi)有找到子View接收Touch事件并且之前的mFirstTouchTarget不為空的時(shí)候 坡脐,就返回最初的事件
                     */ 
                    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指向了最初的TouchTarget
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }
            /**  
             * 分發(fā)Touch事件至target(Dispatch to touch targets)  
             *   
             * 經(jīng)過(guò)上面對(duì)于ACTION_DOWN的處理后mFirstTouchTarget有兩種情況:  
             * 1 mFirstTouchTarget為null  
             * 2 mFirstTouchTarget不為null  
             *   
             * 當(dāng)然如果不是ACTION_DOWN就不會(huì)經(jīng)過(guò)上面較繁瑣的流程  
             * 而是從此處開(kāi)始執(zhí)行,比如ACTION_MOVE和ACTION_UP  
             */   
            // Dispatch to touch targets.分發(fā)事件
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                /*
                mFirstTouchTarget為null就是說(shuō)Touch事件未被消費(fèi).  
                即沒(méi)有找到能夠消費(fèi)touch事件的子組件或Touch事件被攔截了。
                則調(diào)用ViewGroup的dispatchTransformedTouchEvent()方法處理Touch事件則和普通View一樣.
                即子View沒(méi)有消費(fèi)Touch事件,那么子View的上層ViewGroup才會(huì)調(diào)用其onTouchEvent()處理Touch事件.
                也就是說(shuō)此時(shí)ViewGroup像一個(gè)普通的View那樣調(diào)用dispatchTouchEvent(),
                且在dispatchTouchEvent() 中會(huì)去調(diào)用onTouchEvent()方法
                具體的說(shuō)就是在調(diào)用dispatchTransformedTouchEvent()時(shí)第三個(gè)參數(shù)為null. 
                第三個(gè)參數(shù)View child為null會(huì)做什么樣的處理呢?  
                請(qǐng)參見(jiàn)下面dispatchTransformedTouchEvent()的源碼分析

                這就是為什么子view對(duì)于Touch事件處理返回true那么其上層的ViewGroup就無(wú)法處理Touch事件
                這就是為什么子view對(duì)于Touch事件處理返回false那么其上層的ViewGroup才可以處理Touch事件
                */
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
            //mFirstTouchTarget不為null即找到了可以消費(fèi)Touch事件的子View且后續(xù)Touch事件可以傳遞到該子View  
                // 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;
                         //對(duì)于非ACTION_DOWN事件繼續(xù)傳遞給目標(biāo)子組件進(jìn)行處理,依然是遞歸調(diào)用dispatchTransformedTouchEvent()
                        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;
                }
            }

        /**  
             * 處理ACTION_UP和ACTION_CANCEL  
             * Update list of touch targets for pointer up or cancel, if needed.  
             * 在此主要的操作是還原狀態(tài)  
             */  
            // Update list of touch targets for pointer up or cancel, if needed.
            if (canceled
                    || actionMasked == MotionEvent.ACTION_UP
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                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);
        }
        return handled;
    }

dispatchTransformedTouchEvent源碼:這個(gè)是把事件交給子view去處理的方法

/*
    第一個(gè)參數(shù):傳遞事件咯
    第二個(gè)參數(shù):是否還原狀態(tài)  
    第三個(gè)參數(shù):View child
    在dispatchTouchEvent()中多次調(diào)用了dispatchTransformedTouchEvent(),但是有時(shí)候第三個(gè)參數(shù)為null,有時(shí)又不是房揭。
    那么這個(gè)參數(shù)是否為null有什么區(qū)別呢备闲?
    在如下dispatchTransformedTouchEvent()源碼中可見(jiàn)多次對(duì)于child是否為null的判斷,如下:
    if (child == null) {
           handled = super.dispatchTouchEvent(event); 
     } else { 
           handled = child.dispatchTouchEvent(event);  
     }
     
*/
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;//是否處理了標(biāo)記

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            /*
            當(dāng)child == null時(shí)會(huì)將Touch事件傳遞給該ViewGroup自身的dispatchTouchEvent()處理.  
            當(dāng)child != null時(shí)會(huì)調(diào)用該子view(當(dāng)然該view可能是一個(gè)View也可能是一個(gè)ViewGroup)的dispatchTouchEvent(event)處理.即child.dispatchTouchEvent(event); 
            */
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
        //計(jì)算傳遞的指針數(shù)
        // Calculate the number of pointers to deliver.
        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        // If for some reason we ended up in an inconsistent state where it looks like we
        // might produce a motion event with no pointers in it, then drop the event.
        //判斷事件一致性捅暴,傳遞的數(shù)量不一致就返回false
        if (newPointerIdBits == 0) {
            return false;
        }

        // If the number of pointers is the same and we don't need to perform any fancy
        // irreversible transformations, then we can reuse the motion event for this
        // dispatch as long as we are careful to revert any changes we make.
        // Otherwise we need to make a copy.
         //判斷事件一致性恬砂,一致的話,我們就不用執(zhí)行些不可逆的轉(zhuǎn)換蓬痒,只要小心回復(fù)我們所做的修改泻骤,我們就可以重用該事件。否則就要copy該事件
        final MotionEvent transformedEvent;
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                 //當(dāng)child == null時(shí)會(huì)將Touch事件傳遞給該ViewGroup自身的dispatchTouchEvent()處理. 
                    handled = super.dispatchTouchEvent(event);
                } else {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);

                    handled = child.dispatchTouchEvent(event);

                    event.offsetLocation(-offsetX, -offsetY);
                }
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            transformedEvent = event.split(newPointerIdBits);
        }
        //執(zhí)行一些必要的轉(zhuǎn)換和調(diào)度
        // Perform any necessary transformations and dispatch.
        if (child == null) {
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }

            handled = child.dispatchTouchEvent(transformedEvent);
        }
        //回收事件
        // Done.
        transformedEvent.recycle();
        return handled;
    }

參考博客:

生命壹號(hào)

郭朝


源碼下載:Android-多列表的項(xiàng)目Rxjava+Rtrofit+Recyclerview+Glide+Adapter封裝

好了,Android--焦點(diǎn)問(wèn)題以及討論事件傳遞機(jī)制問(wèn)題講完了瞪讼。本博客是這個(gè)系列的第四篇钧椰,討論的是我在項(xiàng)目中遇到的一些細(xì)節(jié)坑,以及它們的相關(guān)機(jī)制符欠。另外嫡霞,這個(gè)系列還有一些我在外包項(xiàng)目過(guò)程中做的優(yōu)化,以及一些發(fā)布簽名等等技巧希柿,我會(huì)盡快出完給大家诊沪,分享經(jīng)驗(yàn)給大家。歡迎在下面指出錯(cuò)誤曾撤,共同學(xué)習(xí)6艘Α!你的點(diǎn)贊是對(duì)我最好的支持<废ぁ渐裸!

更多內(nèi)容,可以訪問(wèn)JackFrost的博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末装悲,一起剝皮案震驚了整個(gè)濱河市昏鹃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌诀诊,老刑警劉巖洞渤,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異属瓣,居然都是意外死亡载迄,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門抡蛙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)护昧,“玉大人,你說(shuō)我怎么就攤上這事溜畅∧笞浚” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵慈格,是天一觀的道長(zhǎng)怠晴。 經(jīng)常有香客問(wèn)我,道長(zhǎng)浴捆,這世上最難降的妖魔是什么蒜田? 我笑而不...
    開(kāi)封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮选泻,結(jié)果婚禮上冲粤,老公的妹妹穿的比我還像新娘美莫。我一直安慰自己,他們只是感情好梯捕,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布厢呵。 她就那樣靜靜地躺著,像睡著了一般傀顾。 火紅的嫁衣襯著肌膚如雪襟铭。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天短曾,我揣著相機(jī)與錄音寒砖,去河邊找鬼。 笑死嫉拐,一個(gè)胖子當(dāng)著我的面吹牛哩都,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播婉徘,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼漠嵌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了判哥?” 一聲冷哼從身側(cè)響起献雅,我...
    開(kāi)封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤碉考,失蹤者是張志新(化名)和其女友劉穎塌计,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體侯谁,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锌仅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了墙贱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片热芹。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖惨撇,靈堂內(nèi)的尸體忽然破棺而出伊脓,到底是詐尸還是另有隱情,我是刑警寧澤魁衙,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布报腔,位于F島的核電站,受9級(jí)特大地震影響剖淀,放射性物質(zhì)發(fā)生泄漏纯蛾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一纵隔、第九天 我趴在偏房一處隱蔽的房頂上張望翻诉。 院中可真熱鬧炮姨,春花似錦、人聲如沸碰煌。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)芦圾。三九已至吁津,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間堕扶,已是汗流浹背碍脏。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留稍算,地道東北人典尾。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像糊探,于是被迫代替她去往敵國(guó)和親钾埂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容