焦點處理相關記錄
以下所涉及的焦點部分,只是按鍵移動部分,不明確包含Touch Focus部分
需解決問題
控件的下一個焦點是哪?
分析思路
當用戶通過按鍵(遙控器等)觸發焦點切換時,事件指令會通過底層進行一系列處理。 在ViewRootImpl.java中有一個方法,deliverKeyEventPostIme(...),因為涉及到底層代碼,所以沒有詳細的跟蹤分析此方法的調用邏輯,根據網上的資料,按鍵相關的處理會經過此方法。
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;
}
}
...
}
}
由此方法可以看出,最主要的兩個核心過程:
1
2
View v = focused.focusSearch(direction);
v.requestFocus(direction, mTempRect)
接下來詳細的分析下,看看過程中進行了什麼操作
具體分析
在具體分析前,首先我們先明確下相關變量的定義
View mView : 主體View[DecorView]
//一般把主View“DecorView”添加到WindowManagerImpl中(通過addView)
//WindowManagerImpl.java
private void addView(View view...) {
ViewRootImpl root;
root = new ViewRootImpl(view.getContext());
...
root.setView(view, wparams, panelParentView);
...
}<br>
//ViewRootImpl.java
public void setView(View view....) {
synchronized (this) {
if (mView == null) {
mView = view;
...
}
...
}
}
所以mView是一個DecorView類型的變量.
View focused :
View focused = mView.findFocus();<br>
//PhoneWindow.java
private final class DecorView extends FrameLayout implements RootVie.... {
...
}<br>
//FrameLayout.java
public class FrameLayout extends ViewGroup {
...
}<br>
//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.
在明確的相關變量後,我們開始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;
}<br>
//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