如下所涉及的焦點部分,只是按鍵移動部分,不明確包含Touch Focus部分java
控件的下一個焦點是哪?android
當用戶經過按鍵(遙控器等)觸發焦點切換時,事件指令會經過底層進行一系列處理。
在ViewRootImpl.java中有一個方法,deliverKeyEventPostIme(...),由於涉及到底層代碼,因此沒有詳細的跟蹤分析此方法的調用邏輯,根據網上的資料,按鍵相關的處理會通過此方法。app
private void deliverKeyEventPostIme(QueuedInputEvent q) { ... // Handle automatic focus changes. if (event.getAction() == KeyEvent.ACTION_DOWN) { 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; ... } if (direction != 0) { View focused = mView.findFocus(); if (focused != null) { View v = focused.focusSearch(direction); if (v != null && v != focused) { ..... if (v.requestFocus(direction, mTempRect)) { ...finishInputEvent(q, true); return; } } ... } }
由此方法能夠看出,最主要的兩個核心過程:ide
View v = focused.focusSearch(direction); v.requestFocus(direction, mTempRect)
接下來詳細的分析下,看看過程當中進行了什麼操做佈局
在具體分析前,首先咱們先明確下相關變量的定義this
View mView : 主體View[DecorView]spa
//通常把主View「DecorView」添加到WindowManagerImpl中(經過addView) //WindowManagerImpl.java private void addView(View view...) { ViewRootImpl root; root = new ViewRootImpl(view.getContext()); ... root.setView(view, wparams, panelParentView); ... } //ViewRootImpl.java public void setView(View view....) { synchronized (this) { if (mView == null) { mView = view; ... } ... } }
因此mView是一個DecorView類型的變量.rest
View focused :code
View focused = mView.findFocus(); //PhoneWindow.java private final class DecorView extends FrameLayout implements RootVie.... { ... } //FrameLayout.java public class FrameLayout extends ViewGroup { ... } //ViewGroup.java //mFocused記錄的是當前被焦點選中的view @Override public View findFocus() { if (DBG) { System.out.println("Find focus in " + this + ": flags=" + isFocused() + ", child=" + mFocused); } if (isFocused()) { return this; } if (mFocused != null) { return mFocused.findFocus(); } return null; }
因此最終獲得的focused爲當前頁面中獲得焦點的view.xml
在明確的相關變量後,咱們開始View v = focused.focusSearch(direction)的具體分析.
//View.java public View focusSearch(int direction) { //若是存在父控件,則執行父控件的focusSearch方法 if (mParent != null) { return mParent.focusSearch(this, direction); } else { return null; } } //ViewGroup.java public View focusSearch(View focused, int direction) { //判斷是否爲頂層佈局,如果則執行對應方法,若不是則繼續向上尋找,說明會從內到外的一層層進行判斷,直到最外層的佈局爲止 if (isRootNamespace()) { return FocusFinder.getInstance().findNextFocus(this, focused, direction); } else if (mParent != null) { return mParent.focusSearch(focused, direction); } return null; }
說明在這個過程當中,實際上是從裏層開始一直遍歷到最外層佈局,而後在最外層佈局將處理交給了FocusFinder中的方法.
FocusFinder.getInstance().findNextFocus(this, focused, direction);
那咱們來看看此方法具體作了什麼操做
//FocusFinder.java public final View findNextFocus(ViewGroup root, View focused, int direction) { return findNextFocus(root, focused, null, direction); }
//FocusFinder.java private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) { View next = null; if (focused != null) { next = findNextUserSpecifiedFocus(root, focused, direction); } if (next != null) { return next; } ArrayList<View> focusables = mTempList; try { focusables.clear(); root.addFocusables(focusables, direction); if (!focusables.isEmpty()) { next = findNextFocus(root, focused, focusedRect, direction, focusables); } } finally { focusables.clear(); } return next; }
發如今findNextFocus的執行過程的開始,先執行了findNextUserSpecifiedFocus(...)方法,由代碼能夠看出,此方法先去判斷特定Id值是否存在,若存在則查詢出Id對應的view.其實這些Id就是xml裏經過android:nextFocusUp="..."等或者代碼特別指定的焦點順序.因此在此過程先判斷,若存在,說明下個焦點已經找到,直接返回.
//FocusFinder.java private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction) { // check for user specified next focus View userSetNextFocus = focused.findUserSetNextFocus(root, direction); if (userSetNextFocus != null && userSetNextFocus.isFocusable() && (!userSetNextFocus.isInTouchMode() || userSetNextFocus.isFocusableInTouchMode())) { return userSetNextFocus; } return null; } //View.java View findUserSetNextFocus(View root, 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; }
若是上面過程沒有查詢到,則會執行到findNextFocus(...)方法.在這個方法中,先經過offsetDescendantRectToMyCoords(...)方法得到焦點控件的位置矩陣.而後經過比較獲得下一個焦點的控件。具體的比較規則能夠查看findNextFocusInRelativeDirection(...)方法與findNextFocusInAbsoluteDirection(...)方法.
//FocusFinder.java 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); } }
查找焦點的過程,主要是從View的focusSearch(...)方法開始,從當前焦點開始逐層往外,最終在最外層佈局執行FocusFinder中的核心方法來得到下個焦點所在的視圖view.
若是須要指定跳轉,能夠在逐層focusSearch(...)的時候,返回特定的view