Android控件系統(八)——按鍵事件分發


Android版本:7.0(API27)算法

[TOC]數組


  按鍵事件分發須要根據控件樹對焦點的管理進行事件分發,那控件樹是如何管理焦點的呢?就是指經過視圖的根View(例如Activity的DecorView)如何能找到控件樹中當前獲取焦點的控件。咱們將核心內容分爲以下三部分:bash

  • 控件樹對焦點的管理;
  • 按鍵事件分發;
  • 下一個焦點控件的查找;

控件焦點體系創建

咱們經過View.requestFoucs()的實現來揭示控件樹對焦點的管理方式。ide

public final boolean requestFocus() {
    return requestFocus(View.FOCUS_DOWN);
}

public final boolean requestFocus(int direction) {
    return requestFocus(direction, null);
}

public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
    return requestFocusNoSearch(direction, previouslyFocusedRect);
}
複製代碼
  • direction:表示焦點的尋找方向。當控件是ViewGroup時將會從左上角開始沿着這個方向查找能夠獲取焦點的子控件。本例只分析View,因此該參數並沒有任何效果;
  • previouslyFocusedRect:以前擁有焦點的控件的位置;

獲取焦點條件判斷

private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
    // need to be focusable
    // 1.控件可見而且能夠得到焦點
    if ((mViewFlags & FOCUSABLE) != FOCUSABLE
            || (mViewFlags & VISIBILITY_MASK) != VISIBLE) {
        return false;
    }

    // need to be focusable in touch mode if in touch mode
    // 2.在觸摸模式下能夠獲取焦點
    if (isInTouchMode() &&
        (FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
           return false;
    }

    // need to not have any parents blocking us
    if (hasAncestorThatBlocksDescendantFocus()) {
        return false;
    }

    /* 4.調用handleFocusGainInternal使此控件獲取焦點 */
    handleFocusGainInternal(direction, previouslyFocusedRect);
    return true;
}
複製代碼

標記3:
  若是該View的任意一父類控件(父親、爺爺、祖爺爺等等)的mGroupFlags設置了FOCUS_BLOCK_DESCENDANTS時,則阻止該控件獲取焦點。hasAncestorThatBlocksDescendantFocus會沿着控件樹一路回溯到整個控件樹的根控件並逐一檢查mGroupFlags的設置。
  mGroupFlags的設置能夠有三種取值,其中一種爲FOCUS_BLOCK_DESCENDANTS。當父控件的這一特性取值爲FOCUS_BLOCK_DESCENDANT時,父控件將會阻止其子控件、孫子控件等獲取焦點。在介紹ViewGroup.requestFocus()時會詳細介紹。
標記4:
  調用handleFocusGainInternal使此控件獲取焦點ui

獲取焦點

void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {
    if (DBG) {
        System.out.println(this + " requestFocus()");
    }

    if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
        // 1
        mPrivateFlags |= PFLAG_FOCUSED;

        View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;

        if (mParent != null) {
            // 2
            mParent.requestChildFocus(this, this);
            updateFocusedInCluster(oldFocus, direction);
        }

        if (mAttachInfo != null) {
            // 3
            mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
        }

        // 4
        onFocusChanged(true, direction, previouslyFocusedRect);
        // 5
        refreshDrawableState();
    }
}
複製代碼

handleFocusGainInternal讓該View獲取焦點:
標記1:
  設置mPrivateFlags標誌爲PFLAG_FOCUSED,表示該控件獲取焦點。
標記2:
  將這一變化通知其父控件,以將焦點從上一個焦點控件奪走並轉移到本控件身上,並在ViewRootImpl中觸發一次"遍歷"以便對控件樹進行重繪。
標記3:
  觸發經過以下代碼添加的監聽this

view.getViewTreeObserver().addOnGlobalFocusChangeListener(this); 
複製代碼

標記4:
  觸發mOnFocusChangeListener的監聽
標記5:
  刷新drawable獲取焦點時的狀態spa

  上述全部的工做都是在改變舊的焦點體系的狀態,設置新的焦點體系;PFLAG_FOCUSED是一個控件擁有焦點的最直接體現,然而這並非焦點管理的所有。這一標記僅僅體現了焦點在個體上的特性;而mParent.requestChildFocus則體現了焦點在控件樹級別上的特性。rest

控件樹中的焦點體系

mParent.requestChildFocus是一個定義在ViewParent接口中的方法,其實現者爲ViewGroup和ViewRootImpl。
ViewGroup的實現目的有兩個:code

  • 將焦點從上一個焦點控件手中奪走,即將PFLAG_FOCUSED標記從控件的mPrivateFlags中移除;
  • 將這一操做繼續向控件樹的根部進行回溯,直到ViewRootImpl,ViewRootImpl的requestChildFocus會將焦點控件保存起來備用,並引起一次「遍歷」使得整個控件樹進行重繪;

ViewGroup.requestChildFocusorm

@Override
public void requestChildFocus(View child, View focused) {
    if (DBG) {
        System.out.println(this + " requestChildFocus()");
    }
    if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
        return;
    }

    // Unfocus us, if necessary
    // 1.
    super.unFocus(focused);

    // We had a previous notion of who had focus. Clear it.
    if (mFocused != child) {
        // 2.
        if (mFocused != null) {
            mFocused.unFocus(focused);
        }

        mFocused = child;
    }
    // 3.
    if (mParent != null) {
        mParent.requestChildFocus(this, focused);
    }
}
複製代碼

標記1:
  若是以前的焦點控件就是這個ViewGroup,則釋放其焦點;
標記2:

  • 擁有焦點的舊控件mFocused與指望獲取焦點的新控件child不爲同一個控件,且mFocused不爲null,則釋放舊控件mFocused的焦點;
  • 將child賦值給mFocused,注:mFocused指向的是新擁有焦點的控件或者新擁有焦點的控件的父親、爺爺、祖爺爺等;

標記3:
將這一操做繼續向控件樹的根部回溯;

  此方法中的mFocused是一個View類型的變量,它是控件樹焦點管理的核心所在,圍繞着mFocused,ViewGroup.requestChildFocus方法包含了新的焦點體系的創建過程,以及舊的焦點體系的銷燬過程。

新焦點體系創建

  新的焦點體系的創建過程是經過在ViewGroup.requestChildFocus方法的回溯過程當中進行mFocused = child這一賦值操做完成的。當回溯完成後,mFocused = child將會創建起一個單向鏈表,使得從根控件開始經過mFocused成員能夠沿着這一單向鏈表找到實際擁有焦點的控件,即實際擁有焦點的控件位於這個單向鏈表的尾端,以下圖所示:

image

舊焦點體系銷燬

  舊的焦點體系的銷燬過程則是經過在回溯過程當中調用mFocused.unFocus完成的。unFocus有ViewGroup和View兩種實現。首先看一下ViewGroup.unfocus的實現:

void unFocus(View focused) {
    if (DBG) {
        System.out.println(this + " unFocus()");
    }
    if (mFocused == null) {
        super.unFocus(focused);
    } else {
        mFocused.unFocus(focused);
        mFocused = null;
    }
}
複製代碼

  可見ViewGroup.unfocus將unfocus調用沿着mFocused所描述的鏈表沿着控件樹向下遍歷,知道焦點的實際擁有者。焦點的實際擁有者會調用View.unFocus(),它會將PFLAG_FOCUSED移除,固然也少不了更新DrawableState以及onFocusChanged()方法的調用。 View.unfocus()

void unFocus(View focused) {
    if (DBG) {
        System.out.println(this + " unFocus()");
    }

    clearFocusInternal(focused, false, false);
}

void clearFocusInternal(View focused, boolean propagate, boolean refocus) {
    if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
        mPrivateFlags &= ~PFLAG_FOCUSED;

        if (propagate && mParent != null) {
            mParent.clearChildFocus(this);
        }

        onFocusChanged(false, 0, null);
        refreshDrawableState();

        if (propagate && (!refocus || !rootViewRequestFocus())) {
            notifyGlobalFocusCleared(this);
        }
    }
}
複製代碼

重繪控件樹

控件樹最終會調用根控件的requestChildFocus,這裏的根控件其實就是ViewRootImpl

public void requestChildFocus(View child, View focused) {
    if (DEBUG_INPUT_RESIZE) {
        Log.v(mTag, "Request child focus: focus now " + focused);
    }
    checkThread();
    scheduleTraversals();
}
複製代碼

前面的文章咱們分析過View的繪製流程,ViewRootImpl.scheduleTraversals會觸發整個控件樹從新繪製,這樣整個焦點變化的過程就完成了。

總結

image

  以上圖所示的控件樹的焦點狀態爲例來描述舊有焦點體系的銷燬以及新焦點體系的創建過程。當View2-1-1經過View.requestFocus()嘗試獲取焦點時,首先會將PLFAGL_FOCUSED標記加入其mPrivateFlags成員中以聲明其擁有焦點。而後調用ViewGroup2-1的requestChildFocus(),此時ViewGroup2-1會嘗試經過unFocus()銷燬舊的焦點體系,可是因爲其mFocused爲null,它沒法進行銷燬,因而它將其mFocused設置爲View2-1-1後將requestChildFocus()傳遞給ViewGroup2。此時ViewGroup2的mFocused指向ViewGroup2-2,因而調用ViewGroup2-2的unFocus()銷燬舊的焦點體系。ViewGroup2-2的unFocus()將次操做傳遞給View2-2-2的unFocus()以移除View2-2-2的PLFAGL_FOCUSED標記,並將其mFocused置爲null。回到ViewGroup2的requestChildFocus()方法後,ViewGroup2將其mFocused從新指向到ViewGroup2-1。在這些工做完成後,就造成了新的焦點體系,以下圖所示
image

  總而言之,控件樹的焦點管理分爲兩部分:

  1. 描述個體級別的焦點狀態的PLFAGL_FOCUSED標記,用於表示一個控件是否擁有焦點;
  2. 描述控件樹級別的焦點狀態的ViewGroup.mFocused成員,用於提供一條連接控件樹的根控件到實際擁有焦點的子控件的單向鏈表。這條鏈表提供了再控件樹中快速查找焦點控件的簡便辦法。另外,因爲焦點的排他性,當一個控件經過requestFocus()獲取焦點以建立新的焦點體系時伴隨着舊有焦點體系的銷燬過程;

ViewGroup.requestFocus

public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
    int descendantFocusability = getDescendantFocusability();

    switch (descendantFocusability) {
        case FOCUS_BLOCK_DESCENDANTS:
            return super.requestFocus(direction, previouslyFocusedRect);
        case FOCUS_BEFORE_DESCENDANTS: {
            final boolean took = super.requestFocus(direction, previouslyFocusedRect);
            return took ? took : onRequestFocusInDescendants(direction, previouslyFocusedRect);
        }
        case FOCUS_AFTER_DESCENDANTS: {
            final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect);
            return took ? took : super.requestFocus(direction, previouslyFocusedRect);
        }
        default:
            throw new IllegalStateException("descendant focusability must be "
                    + "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS "
                    + "but is " + descendantFocusability);
    }
}
複製代碼

根據descendantFocusability不一樣的取值,執行的處理方式不一樣:

  • FOCUS_BLOCK_DESCENDANTS:阻止全部子控件獲取焦點,調用super.requestFocus嘗試本身獲取焦點;
  • FOCUS_BEFORE_DESCENDANTS:ViewGroup優先於子控件獲取焦點,若是ViewGroup不知足獲取焦點的條件,則經過調用onRequestFocusInDescendants方法將獲取焦點的請求轉發給子控件;
  • FOCUS_AFTER_DESCENDANTS:與FOCUS_BEFORE_DESCENDANTS相反,子控件優先ViewGroup獲取焦點,若是子控件不知足獲取焦點的條件,則ViewGroup嘗試本身獲取焦點;
protected boolean onRequestFocusInDescendants(int direction,
            Rect previouslyFocusedRect) {
    int index;
    int increment;
    int end;
    int count = mChildrenCount;
    if ((direction & FOCUS_FORWARD) != 0) {
        index = 0;
        increment = 1;
        end = count;
    } else {
        index = count - 1;
        increment = -1;
        end = -1;
    }
    final View[] children = mChildren;
    for (int i = index; i != end; i += increment) {
        View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
            if (child.requestFocus(direction, previouslyFocusedRect)) {
                return true;
            }
        }
    }
    return false;
}
複製代碼

onRequestFocusInDescendants實際上是一種最簡單的焦點查找算法。它按照direction方向,在mChildren列表中依次調用子控件的requestFocus()方法,直到有一個子控件獲取焦點。另外,須要注意子控件也多是一個ViewGroup,因此這裏將會有一個遞歸的過程。

下一個焦點控件查找

  當一個控件獲取焦點後,用戶每每會經過方向鍵來移動焦點,這時控件系統須要在控件樹中指定的方向上尋找距離當前控件最近的一個控件,並將焦點賦予它。與ViewGroup.onRequestFocusInDescendants()方法按照控件在mChildren數組中的順序查找不一樣,這一查找依賴於控件在窗口中的位置。這一工做由View.focusSearch()方法完成。

public View focusSearch(@FocusRealDirection int direction) {
    if (mParent != null) {
        return mParent.focusSearch(this, direction);
    } else {
        return null;
    }
}
複製代碼

又會調用父類的focusSearch,父類就是ViewGroup

public View focusSearch(View focused, int direction) {
    if (isRootNamespace()) {
        // root namespace means we should consider ourselves the top of the
        // tree for focus searching; otherwise we could be focus searching
        // into other tabs.  see LocalActivityManager and TabHost for more info.
        return FocusFinder.getInstance().findNextFocus(this, focused, direction);
    } else if (mParent != null) {
        return mParent.focusSearch(focused, direction);
    }
    return null;
}
複製代碼

若是此ViewGroup不是根控件,則繼續向控件樹的根部回溯,直到根控件,而後使用FocusFinder的findNextFocus方法查找下一個焦點。這個方法三個參數的意義以下:

  • this:即root。findNextFocus方法經過這個參數獲取整個控件樹中全部的候選控件。
  • focused:當前擁有焦點的控件。findNextFocus方法會以這個控件所在的位置開始查找。
  • direction:查找的方向。
public final View findNextFocus(ViewGroup root, View focused, int direction) {
    return findNextFocus(root, focused, null, direction);
}

private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
    View next = null;
    ViewGroup effectiveRoot = getEffectiveRoot(root, focused);
    if (focused != null) {
        next = findNextUserSpecifiedFocus(effectiveRoot, focused, direction);
    }
    if (next != null) {
        return next;
    }
    ArrayList<View> focusables = mTempList;
    try {
        focusables.clear();
        effectiveRoot.addFocusables(focusables, direction);
        if (!focusables.isEmpty()) {
            next = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables);
        }
    } finally {
        focusables.clear();
    }
    return next;
}
複製代碼

  findNextFocus會首先嚐試經過findNextUserSpecifiedFocus獲取由開發者設置的下一個焦點控件。有時候控件系統內置的焦點查找算法並不能知足開發者的需求,所以須要開發者能夠經過View.setNextFocusXXXId()方法或XML中的nextFocusXXX屬性設置此控件的下一個可獲取焦點的控件Id。其中XXX能夠是Left、Right、Top、Bottom和Forword,分別用來設置不一樣方向上的下一個焦點控件。
  假若開發者在指定方向上沒有設置下一個焦點控件,則經過內置的搜索算法進行查找。這個內置算法會首先將控件樹中全部可獲取焦點的控件添加到一個名爲focusables的列表中,並以這個列表做爲焦點控件的候選集合。這樣作的目的並不只僅是提升效率,更重要的是這個列表打破了控件在控件樹中的層次關係。它在必定程度上體現了焦點查找的一個原則,即控件在窗口上的位置是惟一查找依據,與控件在控件樹中的層次無關。

  至於具體的查找算法,咱們就不深刻去研究了,有興趣的同窗能夠自行閱讀。

按鍵事件分發

  在「Android事件分發」分析了事件分發的分水嶺:

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);
            }
        }
    }
}
複製代碼

processKeyEvent處理按鍵事件

private int processKeyEvent(QueuedInputEvent q) {
    final KeyEvent event = (KeyEvent)q.mEvent;

    // Deliver the key to the view hierarchy.
    if (mView.dispatchKeyEvent(event)) {
        return FINISH_HANDLED;
    }

    if (shouldDropInputEvent(q)) {
        return FINISH_NOT_HANDLED;
    }

    int groupNavigationDirection = 0;

    if (event.getAction() == KeyEvent.ACTION_DOWN
            && event.getKeyCode() == KeyEvent.KEYCODE_TAB) {
        if (KeyEvent.metaStateHasModifiers(event.getMetaState(), KeyEvent.META_META_ON)) {
            groupNavigationDirection = View.FOCUS_FORWARD;
        } else if (KeyEvent.metaStateHasModifiers(event.getMetaState(),
                KeyEvent.META_META_ON | KeyEvent.META_SHIFT_ON)) {
            groupNavigationDirection = View.FOCUS_BACKWARD;
        }
    }

    // If a modifier is held, try to interpret the key as a shortcut.
    if (event.getAction() == KeyEvent.ACTION_DOWN
            && !KeyEvent.metaStateHasNoModifiers(event.getMetaState())
            && event.getRepeatCount() == 0
            && !KeyEvent.isModifierKey(event.getKeyCode())
            && groupNavigationDirection == 0) {
        if (mView.dispatchKeyShortcutEvent(event)) {
            return FINISH_HANDLED;
        }
        if (shouldDropInputEvent(q)) {
            return FINISH_NOT_HANDLED;
        }
    }

    // Apply the fallback event policy.
    if (mFallbackEventHandler.dispatchKeyEvent(event)) {
        return FINISH_HANDLED;
    }
    if (shouldDropInputEvent(q)) {
        return FINISH_NOT_HANDLED;
    }

    // Handle automatic focus changes.
    if (event.getAction() == KeyEvent.ACTION_DOWN) {
        if (groupNavigationDirection != 0) {
            if (performKeyboardGroupNavigation(groupNavigationDirection)) {
                return FINISH_HANDLED;
            }
        } else {
            if (performFocusNavigation(event)) {
                return FINISH_HANDLED;
            }
        }
    }
    return FORWARD;
}
複製代碼

processKeyEvent的代碼較長,不過咱們的摸底是抽離核心步驟進行分析便可,processKeyEvent有兩個核心內容:

  • 調用mView.dispatchKeyEvent(event),在控件樹中進行按鍵事件分發;
  • 若是在上一步中沒有View消耗事情,那麼再調用performFocusNavigation判斷該事件是不是方向鍵,控制焦點的移動;

事件分發

這裏是調用ViewRootImpl內部字段mView.dispatchPointerEvent就行觸摸事件分發,mView是什麼呢?經過前面文章的分析咱們能知道mView實際上是PhoneWindow內部維護的DecorView,而DecorView繼承FrameLayout,因此最終調用的是ViewGroup.dispatchKeyEvent

public boolean dispatchKeyEvent(KeyEvent event) {
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onKeyEvent(event, 1);
    }

    if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
            == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
        // 1.
        if (super.dispatchKeyEvent(event)) {
            return true;
        }
    } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
            == PFLAG_HAS_BOUNDS) {
        // 2.
        if (mFocused.dispatchKeyEvent(event)) {
            return true;
        }
    }

    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
    }
    return false;
}
複製代碼

標記1
若是此ViewGroup擁有焦點,則調用super.dispatchKeyEvent嘗試消費事件;
標記2
若是此ViewGroup不擁有焦點,則將事件沿折mFocused鏈表進行傳遞;若是mFocused爲ViewGroup,那麼進入遞歸過程;

View.dispatchKeyEvent()

public boolean dispatchKeyEvent(KeyEvent event) {
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onKeyEvent(event, 0);
    }

    // Give any attached key listener a first crack at the event.
    //noinspection SimplifiableIfStatement
    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
            && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
        return true;
    }

    if (event.dispatch(this, mAttachInfo != null
            ? mAttachInfo.mKeyDispatchState : null, this)) {
        return true;
    }

    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }
    return false;
}
複製代碼

首先由mOnKeyListener監聽者嘗試處理事件,若是mOnKeyListener返回true,那麼整個案件事件結束。而後經過event.dispatch方法將事件發給View的指定回調,如onKeyDown()/onKeyUp()等。

焦點移動

performFocusNavigation方法:

private boolean performFocusNavigation(KeyEvent event) {
    int direction = 0;
    switch (event.getKeyCode()) {
        case KeyEvent.KEYCODE_DPAD_LEFT:
            if (event.hasNoModifiers()) {
                direction = View.FOCUS_LEFT;
            }
            break;
        case KeyEvent.KEYCODE_DPAD_RIGHT:
            if (event.hasNoModifiers()) {
                direction = View.FOCUS_RIGHT;
            }
            break;
        case KeyEvent.KEYCODE_DPAD_UP:
            if (event.hasNoModifiers()) {
                direction = View.FOCUS_UP;
            }
            break;
        case KeyEvent.KEYCODE_DPAD_DOWN:
            if (event.hasNoModifiers()) {
                direction = View.FOCUS_DOWN;
            }
            break;
        case KeyEvent.KEYCODE_TAB:
            if (event.hasNoModifiers()) {
                direction = View.FOCUS_FORWARD;
            } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
                direction = View.FOCUS_BACKWARD;
            }
            break;
    }
    if (direction != 0) {
        View focused = mView.findFocus();
        if (focused != null) {
            View v = focused.focusSearch(direction);
            if (v != null && v != focused) {
                // do the math the get the interesting rect
                // of previous focused into the coord system of
                // newly focused view
                focused.getFocusedRect(mTempRect);
                if (mView instanceof ViewGroup) {
                    ((ViewGroup) mView).offsetDescendantRectToMyCoords(
                            focused, mTempRect);
                    ((ViewGroup) mView).offsetRectIntoDescendantCoords(
                            v, mTempRect);
                }
                if (v.requestFocus(direction, mTempRect)) {
                    playSoundEffect(SoundEffectConstants
                            .getContantForFocusDirection(direction));
                    return true;
                }
            }

            // Give the focused view a last chance to handle the dpad key.
            if (mView.dispatchUnhandledMove(focused, direction)) {
                return true;
            }
        } else {
            if (mView.restoreDefaultFocus()) {
                return true;
            }
        }
    }
    return false;
}
複製代碼
  1. 轉換獲取焦點移動的方向direction;
  2. mView.findFocus找到控件樹中當前得到焦點的View;
  3. 以當前得到焦點控件focused爲啓動,調用focusSearch(direction)在direction方向上查找下一個獲取焦點的控件;
  4. 調用requestFocus方法,實現舊焦點體系的移除和新焦點體系的創建(這點參考前面的分析);
相關文章
相關標籤/搜索