在tv端開發中,焦點處理是一個很是重要的技術。該篇主要是想整理相關知識。(本文檔依據sdk26進行分析)java
在android的繪製流程中**ViewRootImpl#performTraversals()**起着關鍵的做用,而焦點狀態也會經過影響視圖的繪製。
下面來看看android事如何進行第一次尋焦的android
private void performTraversals() {
......
if (mFirst && sAlwaysAssignFocus) {
// handle first focus request
if (mView != null) {
if (!mView.hasFocus()) {
mView.restoreDefaultFocus();
}
}
}
......
}
複製代碼
mView.restoreDefaultFocus()將會去查找當前試圖第一個可聚焦的View。將會執行requestFocus(int direction, Rect previouslyFocusedRect)。由於ViewGroup重寫了該方法,增長了是否攔截焦點處理的邏輯,下面咱們先來看看ViewGroup#requestFocus(int direction, Rect previouslyFocusedRect)ide
@Override
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
if (DBG) {
System.out.println(this + " ViewGroup.requestFocus direction="
+ direction);
}
int descendantFocusability = getDescendantFocusability();
switch (descendantFocusability) {
//攔截焦點,無論當前View是否被聚焦,子View必定獲取不到焦點。
case FOCUS_BLOCK_DESCENDANTS:
return super.requestFocus(direction, previouslyFocusedRect);
//在子View以前判斷是否應被聚焦,若是爲false則會去判斷其子View
case FOCUS_BEFORE_DESCENDANTS: {
final boolean took = super.requestFocus(direction, previouslyFocusedRect);
return took ? took : onRequestFocusInDescendants(direction, previouslyFocusedRect);
}
// 在子View判斷焦點以後判斷
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);
}
}
複製代碼
接着咱們來看看View#requestFocus(int direction, Rect previouslyFocusedRect)this
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
return requestFocusNoSearch(direction, previouslyFocusedRect);
}
private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
// 判斷是否可被聚焦
if ((mViewFlags & FOCUSABLE) != FOCUSABLE
|| (mViewFlags & VISIBILITY_MASK) != VISIBLE) {
return false;
}
// 判斷觸摸狀態下是否可被聚焦
if (isInTouchMode() &&
(FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
return false;
}
// 判斷父View是否須要攔截
if (hasAncestorThatBlocksDescendantFocus()) {
return false;
}
//執行聚焦操做
handleFocusGainInternal(direction, previouslyFocusedRect);
return true;
}
void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {
if (DBG) {
System.out.println(this + " requestFocus()");
}
if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
//設置聚焦標誌位
mPrivateFlags |= PFLAG_FOCUSED;
View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;
if (mParent != null) {
//通知父容器改變焦點View
mParent.requestChildFocus(this, this);
updateFocusedInCluster(oldFocus, direction);
}
if (mAttachInfo != null) {
//全局監聽回調
mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
}
//執行焦點變化與強制繪製
onFocusChanged(true, direction, previouslyFocusedRect);
refreshDrawableState();
}
}
複製代碼
首先會根據按鍵生成一個尋焦方向(能夠查看ViewRootImpl#ViewPostImeInputStage#processKeyEvent)spa
private int processKeyEvent(QueuedInputEvent q) {
......
//上面是判斷是否處理當前按鍵,如dispatchKeyEvent返回true則不會執行下面的焦點邏輯
// 根據當前事件生成一個尋焦方向。
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;
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,根據當前view查詢下一個方向的聚焦view
View focused = mView.findFocus();
if (focused != null) {
//因而可知,focusSearch 爲尋焦的主要方法,可從新該方法來修改焦點邏輯
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 FINISH_HANDLED;
}
}
// Give the focused view a last chance to handle the dpad key.
if (mView.dispatchUnhandledMove(focused, direction)) {
return FINISH_HANDLED;
}
} else {
// find the best view to give focus to in this non-touch-mode with no-focus
View v = focusSearch(null, direction);
if (v != null && v.requestFocus(direction)) {
return FINISH_HANDLED;
}
}
}
}
return FORWARD;
}
複製代碼
下面可查看View#focusSearch與ViewGroup#focusSearch 的相關處理rest
View.java public View focusSearch(@FocusRealDirection int direction) {
if (mParent != null) {
return mParent.focusSearch(this, direction);
} else {
return null;
}
}
ViewGroup.java public View focusSearch(View focused, int direction) {
if (isRootNamespace()) {
// 經過FocusFinder來查找下一個聚焦view
return FocusFinder.getInstance().findNextFocus(this, focused, direction);
} else if (mParent != null) {
return mParent.focusSearch(focused, direction);
}
return null;
}
複製代碼
FocusFinder 是一個焦點處理的類,主要用於在一個方向上,經過當前view與聚焦方案來合理判斷下一個被聚焦viewcode