Android焦點處理流程(源碼分析)

主要涉及到的類:ViewRootImp筹裕,ViewGroup抖僵,View,F(xiàn)ocusFinder

當事件發(fā)生時镰绎,最主要是從ViewRootImpl的processKeyEvent開始處理分發(fā)脓斩。

1.ViewRootImpl

? ? 1.連接WindowManager和DecorView的紐帶
? ? 2.完成view的measure,layout,draw
? ? 3.向DecorView分發(fā)按鍵畴栖、觸摸事件等随静。
? ? ? 1.先判斷是否有按鍵事件處理
? ?? 1.若返回true,則打斷該方向上的焦點尋找。
? ??2.若返回fasle燎猛,則根據(jù)指定的方向?qū)ふ易罱铱色@取焦點的view
? ?? ? 1.如果mView.findFocus()找到了focused
? ?? ? ?1.1判斷mView的類型恋捆,是否為ViewGroup。
? ?? ? ? 1.2判斷該focused是否是mView內(nèi)的view
offsetDescendantRectToMyCoords()該方法判斷
? ?? ? 2.如果沒有找到則調(diào)用自身的focusSearch() ===此處后文會展開==
補充:ViewRootImpl中的mView指的是Activity中的DecorView后文中會頻繁的對mView進行判斷或調(diào)用mView的方法重绷。

final class ViewPostImeInputStage extends InputStage {
    public ViewPostImeInputStage(InputStage next) {
        super(next);
    }
    @Override
    protected int onProcess(QueuedInputEvent q) {
        if (q.mEvent instanceof KeyEvent) {
            return processKeyEvent(q); //若是按鍵事件沸停,則走該方法處理按鍵和焦點
        } else {
            final int source = q.mEvent.getSource();
            if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                return processPointerEvent(q);  //若是觸摸事件,則走該方法處理
            } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                return processTrackballEvent(q);
            } else {
                return processGenericMotionEvent(q);
            }
        }
    }
    private int processKeyEvent(QueuedInputEvent q) {
        final KeyEvent event = (KeyEvent)q.mEvent;
        //首先由dispatchKeyEvent進行分發(fā)昭卓,如果返回true的愤钾,則不再繼續(xù)。
       //未被處理KeyEvent處理候醒,則進入尋找下一個焦點的流程能颁。
        if (mView.dispatchKeyEvent(event)) {
         //該方法內(nèi)部首先判斷擁有focus的view,是否重寫了onKeyDown火焰、onKeyUp方法劲装,事件會交給它優(yōu)先處理。
//當它返回true時昌简,那么事件不再繼續(xù)傳遞占业。也就是說我們可以通過重寫返true來攔截。
            return FINISH_HANDLED;
        }
        if (shouldDropInputEvent(q)) {
            return FINISH_NOT_HANDLED;
        }
        if (event.getAction() == KeyEvent.ACTION_DOWN
                && event.isCtrlPressed()
                && event.getRepeatCount() == 0
                && !KeyEvent.isModifierKey(event.getKeyCode())) {
  
            if (mView.dispatchKeyShortcutEvent(event)) {
                return FINISH_HANDLED;
            }
            if (shouldDropInputEvent(q)) {
                return FINISH_NOT_HANDLED;
            }
        }
     //若未被子view攔截纯赎,開始處理按鍵谦疾,根據(jù)direction進行處理
        if (event.getAction() == KeyEvent.ACTION_DOWN) {
            int direction = 0;
            switch (event.getKeyCode()) {
                //根據(jù)code設置direction的值...............
            }
            if (direction != 0) {
         //DecorView會一層一層往下調(diào)用findFocus方法找到當前獲取焦點的View
                View focused = mView.findFocus();
                if (focused != null) {
                 ========= 1. 展開分析focused.focusSearch()   ===========
                    View v = focused.focusSearch(direction);
                    if (v != null && v != focused) ;
                        focused.getFocusedRect(mTempRect);
              //若是ViewGroup類型,計算被聚焦的view犬金,是否在mView內(nèi)部
                        if (mView instanceof ViewGroup) {
                            ((ViewGroup) mView).offsetDescendantRectToMyCoords(
                                    focused, mTempRect);
                            ((ViewGroup) mView).offsetRectIntoDescendantCoords(
                                    v, mTemRect);
                        }
                     ========= 2.此處下面展開分析 =========
                        if (v.requestFocus(direction, mTempRect)) {
                            playSoundEffect(SoundEffectConstants
                                    .getContantForFocusDirection(direction));
                            return FINISH_HANDLED;
                        }
                    }
                    if (mView.dispatchUnhandledMove(focused, direction)) {
                        return FINISH_HANDLED;
                    }
                } else {
                =========  3.如果focused為null念恍,以下展開分析  ===========
                 View v = focusSearch(null, direction);
                    if (v != null && v.requestFocus(direction)) {
                        return FINISH_HANDLED;
                    }
                }
            }
        }
        return FORWARD;
}

1.1展開分析ViewRootImpl,調(diào)用了View.focusSearch()

作用:1.尋找指定方向上的view
2.判斷是否有mParent,即其父view晚顷,交給父view處理峰伙,如其父view是RecyclerView,則先讓RecylerView的focusSearch()執(zhí)行该默,若內(nèi)部調(diào)用了super.focusSearch()瞳氓,則還會交給ViewGroup處理。
如下圖:


image.png

ViewGroup內(nèi)部會不斷地向上調(diào)父View的focusSearch()(如下圖),具體代碼可以看后文關于ViewGroup的分析栓袖。


image.png
public View focusSearch(@FocusRealDirection int direction) {
    if (mParent != null) {
        return mParent.focusSearch(this, direction);
    } else {
        return null;
    }
}

關于mParent的由來匣摘,如果view存在mParent,則其父view是ViewGroup裹刮。
子view被add在ViewGrop中音榜,調(diào)用addView()---->addViewInne()時,會為子view賦值parent為this捧弃。

private void addViewInner(View child, int index, LayoutParams params,
        boolean preventRequestLayout) {
    addInArray(child, index);
   //此處省略N行.......
    if (preventRequestLayout) {
        child.assignParent(this);
    } else {
        child.mParent = this;
    }
  //此處省略N行.......
}

1.2展開分析ViewRootImpl內(nèi)部調(diào)用View的requestFocus()

requestFocus() ----> requestFocusNoSearch() ----->handleFocusGainInternal()
1.調(diào)用mParent.requestChildFocus()通知父控件赠叼,即將獲取焦點。
2.通知其他部件,焦點即將發(fā)生變化梅割。
3.通知回調(diào)霜第。
4.強制布局更新繪制。

void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {
        if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
            mPrivateFlags |= PFLAG_FOCUSED;
            View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;
            if (mParent != null) {
             //此時調(diào)用view的parent的requestChildFocus的回調(diào)户辞,
            //可重寫RecyclerView的requestChildFocus做一些處理泌类。
                mParent.requestChildFocus(this, this);
                updateFocusedInCluster(oldFocus, direction);
            }
            if (mAttachInfo != null) {
               //調(diào)用globalFocus回調(diào)                mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
            }
            onFocusChanged(true, direction, previouslyFocusedRect);
            refreshDrawableState();
        }
    }

1.3展開分析當mView沒有找到focused時,ViewRootImp調(diào)用自身requestFocus

最終會調(diào)用DecorView的focusSearch方法底燎。而DecorView的focusSearch方法找到的焦點view是通過FocusFinder來找到的.
1.檢查線程
2.判斷mView是否為ViewGroup
3.使用FocusFinder尋找焦點 ----->后文會分析如何尋找焦點

public View focusSearch(View focused, int direction) {
    checkThread();
    if (!(mView instanceof ViewGroup)) {
        return null;
    }
    return FocusFinder.getInstance().findNextFocus((ViewGroup) mView, focused, direction);
}

2.ViewGroup內(nèi)的dispatchKeyEvent方法

規(guī)則:
1.如果這個viewGroup持有焦點, 那么就會直接調(diào)用super.dispatchKeyEvent()
2.如果是它的子控件持有焦點, 那么就會調(diào)用子控件的view.dispatchKeyEvetn()
關于其分發(fā)策略的標記:
FOCUS_BLOCK_DESCENDANTS: 攔截焦點, 直接自己嘗試獲取焦點
FOCUS_BEFORE_DESCENDANTS: 首先自己嘗試獲取焦點, 如果自己不能獲取焦點, 則嘗試讓子控件獲取焦點
FOCUS_AFTER_DESCENDANTS: 首先嘗試把焦點給子控件, 如果所有子控件都不要, 則自己嘗試獲取焦點

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
  //mInputEventConsistencyVerifier是調(diào)試用的刃榨,暫時不理會。
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onKeyEvent(event, 1);
    }

    if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
            == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
 //如果viewgroup持有焦點双仍,先調(diào)用其自身的dispacthKeyevent()
        if (super.dispatchKeyEvent(event)) {
            return true;
        }
    } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
            == PFLAG_HAS_BOUNDS) {
        //如果子view持有焦點枢希,先將事件傳給子view。
        if (mFocused.dispatchKeyEvent(event)) {
            return true;
        }
    }
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
    }
    return false;
}

3.View內(nèi)的dispatchKeyEvent方法

1.先處理當前view的onKey監(jiān)聽
2.再處理其他監(jiān)聽的回調(diào)

public boolean dispatchKeyEvent(KeyEvent event) {
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onKeyEvent(event, 0);
    }
    ListenerInfo li = mListenerInfo;
    //判斷view是否注冊了onKeyListener監(jiān)聽朱沃,先判斷其返回值苞轿,若為true,則事件處理到此為止逗物。
    if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
            && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
        return true;
    }
   //dispatch()內(nèi)部處理其他的回調(diào)事件搬卒,判斷是否被攔截處理。
    if (event.dispatch(this, mAttachInfo != null
            ? mAttachInfo.mKeyDispatchState : null, this)) {
        return true;
    }
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }
    return false;
}

4.FocusFinder

實現(xiàn)根據(jù)給定的按鍵方向翎卓,通過已獲取焦點的View契邀,查找下一個獲取焦點的view規(guī)則算法的類。焦點沒有被攔截的情況下失暴,F(xiàn)ocusFinder的查找規(guī)則來查找坯门。

關鍵方法findNextFocus,通過給定的矩形坐標逗扒,尋找根視圖的子view中可以獲取focus的view
規(guī)則:
1.優(yōu)先尋找用戶在direction上已經(jīng)指定獲取focus的view古戴。
如果有,則直接返回該view矩肩。如果不存在现恼,則進入2.
2.把根root中所有可以獲取focus的view添加到focusables列表中。
根root一般是viewGroup蛮拔,則調(diào)用其addFocusablse述暂,其會遍歷所有child痹升,調(diào)用child的addFocusable建炫。
【 這里有一個誤區(qū),認為會取direction方向上的view疼蛾,實際上未以direction來確定添加肛跌,而是將所有的v可focus的view都add到列表中】
3.根據(jù)現(xiàn)有focused,所有可focus的focusables,尋找下一個合適的view

private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
    View next = null;
 //若當前focus的不為空
    if (focused != null) {
   //優(yōu)先一層一層尋找該用戶已經(jīng)指定的可獲取焦點的view
  //執(zhí)行當前focus的view的findUserSetNextFocus方法
  //如果該方法返回的View不為空衍慎,且isFocusable = true && isInTouchMode() = true的話转唉。
  //FocusFinder找到的焦點就是findNextUserSpecifiedFocus()返回的View。
        next = findNextUserSpecifiedFocus(root, focused, direction);
    }
    if (next != null) {
  //若能找到稳捆,則直接返回赠法。
        return next;
    }
    ArrayList<View> focusables = mTempList;
    try {
  //賦值后,先清空該對象的歷史值
        focusables.clear();
//添加任何可聚焦的view乔夯,這些view是root的子view(可能)
//包括這個視圖砖织,如果它本身可以聚焦到視圖。如果我們處于觸摸模式末荐,添加在觸摸模式中也是可聚焦的視圖侧纯。
        root.addFocusables(focusables, direction);
        if (!focusables.isEmpty()) {
//根據(jù)root,當前focus的view甲脏,其坐標矩形眶熬,按鍵方向,所有可獲取焦點的view块请,尋找下一個符合條件的view
         next = findNextFocus(root, focused, focusedRect, direction, focusables);
        }
    } finally {
        focusables.clear();
    }
    return next;
}

4.1優(yōu)先尋找用戶在direction上已經(jīng)指定獲取focus的view

private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction) {
    View userSetNextFocus = focused.findUserSetNextFocus(root, direction);
    if (userSetNextFocus != null && userSetNextFocus.isFocusable()
            && (!userSetNextFocus.isInTouchMode()
                    || userSetNextFocus.isFocusableInTouchMode())) {
        return userSetNextFocus;
    }
    return null;
}

調(diào)用view中的findUserSetNextFocus()

View findUserSetNextFocus(View root, @FocusDirection int direction) {
    switch (direction) {
        case FOCUS_LEFT:
            if (mNextFocusLeftId == View.NO_ID) return null;
            return findViewInsideOutShouldExist(root, mNextFocusLeftId);
        case FOCUS_RIGHT:
            if (mNextFocusRightId == View.NO_ID) return null;
            return findViewInsideOutShouldExist(root, mNextFocusRightId);
        case FOCUS_UP:
            if (mNextFocusUpId == View.NO_ID) return null;
            return findViewInsideOutShouldExist(root, mNextFocusUpId);
        case FOCUS_DOWN:
            if (mNextFocusDownId == View.NO_ID) return null;
            return findViewInsideOutShouldExist(root, mNextFocusDownId);
        case FOCUS_FORWARD:
            if (mNextFocusForwardId == View.NO_ID) return null;
            return findViewInsideOutShouldExist(root, mNextFocusForwardId);
        case FOCUS_BACKWARD: {
            if (mID == View.NO_ID) return null;
            final int id = mID;
            return root.findViewByPredicateInsideOut(this, new Predicate<View>() {
                @Override
                public boolean apply(View t) {
                    return t.mNextFocusForwardId == id;
                }
            });
        }
    }
    return null;
}

4.2尋找所有可focus的child中合適的view規(guī)則

具體沒有獲取指定view后娜氏,尋找該方向上,可獲取view的findNextFocus方法

private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,
        int direction, ArrayList<View> focusables) {
    if (focused != null) {
        if (focusedRect == null) {
            focusedRect = mFocusedRect;
        }
        // fill in interesting rect from focused
        focused.getFocusedRect(focusedRect);
        root.offsetDescendantRectToMyCoords(focused, focusedRect);
    } else {
        if (focusedRect == null) {
            focusedRect = mFocusedRect;
            // make up a rect at top left or bottom right of root
            switch (direction) {
                case View.FOCUS_RIGHT:
                case View.FOCUS_DOWN:
                    setFocusTopLeft(root, focusedRect);
                    break;
                case View.FOCUS_FORWARD:
                    if (root.isLayoutRtl()) {
                        setFocusBottomRight(root, focusedRect);
                    } else {
                        setFocusTopLeft(root, focusedRect);
                    }
                    break;

                case View.FOCUS_LEFT:
                case View.FOCUS_UP:
                    setFocusBottomRight(root, focusedRect);
                    break;
                case View.FOCUS_BACKWARD:
                    if (root.isLayoutRtl()) {
                        setFocusTopLeft(root, focusedRect);
                    } else {
                        setFocusBottomRight(root, focusedRect);
                    break;
                }
            }
        }
    }
switch (direction) {
        case View.FOCUS_FORWARD:
        case View.FOCUS_BACKWARD:
            return findNextFocusInRelativeDirection(focusables, root, focused, focusedRect,
                    direction);
        case View.FOCUS_UP:
        case View.FOCUS_DOWN:
        case View.FOCUS_LEFT:
        case View.FOCUS_RIGHT:
            return findNextFocusInAbsoluteDirection(focusables, root, focused,
                    focusedRect, direction);
        default:
            throw new IllegalArgumentException("Unknown direction: " + direction);
    }
}

5.小結(jié)

ViewRootImpl接收按鍵事件负乡,并對其進行分發(fā)處理牍白。
1.DecorView會調(diào)用dispatchKey逐層進行焦點的分發(fā),若某個view的dispatchKeyEvent方法返回true抖棘。則按鍵不再傳遞茂腥,焦點都不再繼續(xù)處理。(可對其設置OnKeyListener監(jiān)聽切省,返true即可達到不再傳遞目的)
2.如果焦點沒有被攔截的話最岗,則進入查找流程。首先判斷當前mView是否有可獲取focus的View朝捆。
???? 2.1若有:根據(jù)方向查找該View內(nèi)是否有符合條件的view般渡。若找到,先判斷其mView是否為ViewGroup芙盘,然后判斷該view是否在mView內(nèi)部驯用。然后請求獲取焦點requestFocus()
???? 2.2若無:直接調(diào)用ViewRootImp內(nèi)的focusSearch()方法,該方調(diào)用FocusFinder的findNextFocus來查找合適的控件儒老。
4.FocusFinder優(yōu)先尋找開發(fā)者指定該方向上下一個可獲取的view(比如在XML文件中指定了下一個可獲取焦點的View的ID蝴乔。如果沒有,則使用FocusFinder類內(nèi)的方法findNextFocus()來查找驮樊。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末薇正,一起剝皮案震驚了整個濱河市片酝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌挖腰,老刑警劉巖雕沿,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異猴仑,居然都是意外死亡审轮,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門辽俗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來断国,“玉大人,你說我怎么就攤上這事榆苞∥瘸模” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵坐漏,是天一觀的道長薄疚。 經(jīng)常有香客問我,道長赊琳,這世上最難降的妖魔是什么街夭? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮躏筏,結(jié)果婚禮上板丽,老公的妹妹穿的比我還像新娘。我一直安慰自己趁尼,他們只是感情好埃碱,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著酥泞,像睡著了一般砚殿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上芝囤,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天似炎,我揣著相機與錄音,去河邊找鬼悯姊。 笑死羡藐,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的悯许。 我是一名探鬼主播仆嗦,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼岸晦!你這毒婦竟也來了欧啤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤启上,失蹤者是張志新(化名)和其女友劉穎邢隧,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冈在,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡倒慧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了包券。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纫谅。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖溅固,靈堂內(nèi)的尸體忽然破棺而出付秕,到底是詐尸還是另有隱情,我是刑警寧澤侍郭,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布询吴,位于F島的核電站,受9級特大地震影響亮元,放射性物質(zhì)發(fā)生泄漏猛计。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一爆捞、第九天 我趴在偏房一處隱蔽的房頂上張望奉瘤。 院中可真熱鬧,春花似錦煮甥、人聲如沸盗温。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肌访。三九已至,卻和暖如春艇劫,著一層夾襖步出監(jiān)牢的瞬間吼驶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工店煞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蟹演,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓顷蟀,卻偏偏與公主長得像酒请,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鸣个,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348

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