當前位置:
首頁 > 知識 > [Android]Android焦點流程代碼分析

[Android]Android焦點流程代碼分析

以下內容為原創,歡迎轉載,轉載請註明

通過View的View::focusSearch進行焦點搜索對應方向上的下一個可以獲取焦點的View:

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

不斷地調用父控制項來進行搜索,focusSearch有兩個實現:ViewGroupRecyclerView,先看ViewGroup

@Override
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;
}

如果是最頂層,則直接調用FocusFinder::findNextFocus方法進行搜索;否則調用父控制項的focusSearchFocusFinder::findNextFocus如下:

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;
}

上面的root參數代表的是最頂層的view。

首先,通過嘗試通過findNextUserSpecifiedFocus來查找下一個「指定的」可獲得焦點的View,這個指定是開發者通過SDK自帶的setNextFocusLeftId等方法進行手動設置的。如果查找到指定的下一個可獲得焦點的View,則返回該View;否則,執行View::addFocusables方法,通過這個最頂層的View去拿到所有直接或間接的Focusable的子View,並添加到ArrayList<View> focusables中。

View::addFolcusables方法中有4種實現:

第一種是View中默認實現:

public void addFocusables(ArrayList<View> views, @FocusDirection int direction,
@FocusableMode int focusableMode) {
if (views == null) {
return;
}
if (!isFocusable) {
return;
}
if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE
&& !isFocusableInTouchMode) {
return;
}
views.add(this);
}

如果自己是focusable的話,直接把自己添加進去。

第二種是ViewGroup實現:

@Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
final int focusableCount = views.size;

final int descendantFocusability = getDescendantFocusability;

if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
if (shouldBlockFocusForTouchscreen) {
focusableMode |= FOCUSABLES_TOUCH_MODE;
}

final int count = mChildrenCount;
final View children = mChildren;

for (int i = 0; i < count; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
child.addFocusables(views, direction, focusableMode);
}
}
}

// we add ourselves (if focusable) in all cases except for when we are
// FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is
// to avoid the focus search finding layouts when a more precise search
// among the focusable children would be more interesting.
if ((descendantFocusability != FOCUS_AFTER_DESCENDANTS
// No focusable descendants
|| (focusableCount == views.size)) &&
(isFocusableInTouchMode || !shouldBlockFocusForTouchscreen)) {
super.addFocusables(views, direction, focusableMode);
}
}

先會處理自身ViewGroup與它後代的關係(descendantFocusability),前面提到過,可能的幾種情況:

  • FOCUS_BEFORE_DESCENDANTS

    : ViewGroup本身先對焦點進行處理,如果沒有處理則分發給child View進行處理

  • FOCUS_AFTER_DESCENDANTS

    : 先分發給Child View進行處理,如果所有的Child View都沒有處理,則自己再處理
  • FOCUS_BLOCK_DESCENDANTS

    : ViewGroup本身進行處理,不管是否處理成功,都不會分發給ChildView進行處理

所以,以上:

  1. 如果不是FOCUS_BLOCK_DESCENDANTS,則首先檢查blockForTouchscreen並重置掉focusableMode,然後遍歷所有的子View,調用child::addFocusables
  2. 如果不是FOCUS_AFTER_DESCENDANTS或者沒有focusable的子View時自己處理,所以把自己加入到views中。

第三種是ViewPager實現:

@Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
final int focusableCount = views.size;

final int descendantFocusability = getDescendantFocusability;

if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
for (int i = 0; i < getChildCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility == VISIBLE) {
ItemInfo ii = infoForChild(child);
if (ii != null && ii.position == mCurItem) {
child.addFocusables(views, direction, focusableMode);
}
}
}
}

// we add ourselves (if focusable) in all cases except for when we are
// FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is
// to avoid the focus search finding layouts when a more precise search
// among the focusable children would be more interesting.
if (descendantFocusability != FOCUS_AFTER_DESCENDANTS
|| (focusableCount == views.size)) { // No focusable descendants
// Note that we can"t call the superclass here, because it will
// add all views in. So we need to do the same thing View does.
if (!isFocusable) {
return;
}
if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE
&& isInTouchMode && !isFocusableInTouchMode) {
return;
}
if (views != null) {
views.add(this);
}
}
}

ViewGroup基本一致,在descendantFocusability不是FOCUS_BLOCK_DESCENDANTS時,遍歷子View時判斷view是否屬於當前的page,如果是才加進去。如果不是FOCUS_AFTER_DESCENDANTS或者沒有focusable的子View時自己處理,所以把自己加入到views中。

第四種是RecyclerView實現:

@Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
if (mLayout == null || !mLayout.onAddFocusables(this, views, direction, focusableMode)) {
super.addFocusables(views, direction, focusableMode);
}
}

通過LayoutManager::onAddFocusables來進行管理,如果返回false,則直接調用父類ViewGroup的方法,看下LayoutManager::onAddFocusables的實現:

public boolean onAddFocusables(RecyclerView recyclerView, ArrayList<View> views,
int direction, int focusableMode) {
return false;
}

直接返回false,並且沒有看到相關的LayoutManager對該方法的重寫,所以,這裡應該是直接調用了父類ViewGroup的方法。

addFocusables方法完畢,回到FocusFinder::findNextFocus方法中通過root.addFocusables(focusables, direction);加入所有可獲取焦點的View之後,在非空的情況下調用如下代碼:

next = findNextFocus(root, focused, focusedRect, direction, focusables);

所以重點是FocusFinder::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);
}
}

  • 如果focused不是null,說明當前獲取到焦點的View存在,則獲得繪製焦點的Rect到focusedRect,然後根據rootView遍歷所有ParentView從子View糾正坐標到根View坐標。
  • 如果focused是null,則說明當前沒有View獲取到焦點,則把focusedRect根據不同的direction重置為「一點」。

最後根據direction調用FocusFinder::findNextFocusInAbsoluteDirection方法進行對比查找「下一個」View。

View findNextFocusInAbsoluteDirection(ArrayList<View> focusables, ViewGroup root, View focused,
Rect focusedRect, int direction) {
// initialize the best candidate to something impossible
// (so the first plausible view will become the best choice)
mBestCandidateRect.set(focusedRect);
switch(direction) {
case View.FOCUS_LEFT:
mBestCandidateRect.offset(focusedRect.width + 1, 0);
break;
case View.FOCUS_RIGHT:
mBestCandidateRect.offset(-(focusedRect.width + 1), 0);
break;
case View.FOCUS_UP:
mBestCandidateRect.offset(0, focusedRect.height + 1);
break;
case View.FOCUS_DOWN:
mBestCandidateRect.offset(0, -(focusedRect.height + 1));
}

View closest = null;

int numFocusables = focusables.size;
for (int i = 0; i < numFocusables; i++) {
View focusable = focusables.get(i);

// only interested in other non-root views
if (focusable == focused || focusable == root) continue;

// get focus bounds of other view in same coordinate system
focusable.getFocusedRect(mOtherRect);
root.offsetDescendantRectToMyCoords(focusable, mOtherRect);

if (isBetterCandidate(direction, focusedRect, mOtherRect, mBestCandidateRect)) {
mBestCandidateRect.set(mOtherRect);
closest = focusable;
}
}
return closest;
}

首先把最優選擇mBestCandidateRect設置為focusedRect,根據方向做1像素的偏移便於對比。遍歷所有剛剛查詢出來的focusables,拿到每一個的focusedRect區域並進行轉換,然後通過FocusFinder::isBetterCandidate方法進行對比,然後拿到更好的,遍歷完成後就是最優選擇。接下來看下FocusFinder::isBetterCandidate方法來了解下是怎麼做對比的:

下面代碼意思是:以source這個rect來說,作為對應derection上下一個focus view,rect1是否比rect2更優?

boolean isBetterCandidate(int direction, Rect source, Rect rect1, Rect rect2) {

// to be a better candidate, need to at least be a candidate in the first
// place :)
if (!isCandidate(source, rect1, direction)) {
return false;
}

// we know that rect1 is a candidate.. if rect2 is not a candidate,
// rect1 is better
if (!isCandidate(source, rect2, direction)) {
return true;
}

// if rect1 is better by beam, it wins
if (beamBeats(direction, source, rect1, rect2)) {
return true;
}

// if rect2 is better, then rect1 cant" be :)
if (beamBeats(direction, source, rect2, rect1)) {
return false;
}

// otherwise, do fudge-tastic comparison of the major and minor axis
return (getWeightedDistanceFor(
majorAxisDistance(direction, source, rect1),
minorAxisDistance(direction, source, rect1))
< getWeightedDistanceFor(
majorAxisDistance(direction, source, rect2),
minorAxisDistance(direction, source, rect2)));
}

首先確定rect1是否isCandidateisCandidate做的邏輯簡單來說就是確定rect是否滿足給定的derection作為下一個focus view這個條件,它的判斷依據如下:

boolean isCandidate(Rect srcRect, Rect destRect, int direction) {
switch (direction) {
case View.FOCUS_LEFT:
return (srcRect.right > destRect.right || srcRect.left >= destRect.right)
&& srcRect.left > destRect.left;
case View.FOCUS_RIGHT:
return (srcRect.left < destRect.left || srcRect.right <= destRect.left)
&& srcRect.right < destRect.right;
case View.FOCUS_UP:
return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom)
&& srcRect.top > destRect.top;
case View.FOCUS_DOWN:
return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top)
&& srcRect.bottom < destRect.bottom;
}
throw new IllegalArgumentException("direction must be one of "
+ "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
}

代碼比較簡單。再回到 FocusFinder::isBetterCandidate的代碼邏輯:

  • 如果rect1不滿足基本條件,則肯定返回false(基本的條件都不滿足)
  • 如果rect2不滿足基本條件,則返回true,認為rect1更優
  • 如果都滿足基本條件的情況下,通過FocusFinder::beamBeats方法來判斷哪種更優

接下來看下FocusFinder::beamBeats的實現:

boolean beamBeats(int direction, Rect source, Rect rect1, Rect rect2) {
final boolean rect1InSrcBeam = beamsOverlap(direction, source, rect1);
final boolean rect2InSrcBeam = beamsOverlap(direction, source, rect2);

// if rect1 isn"t exclusively in the src beam, it doesn"t win
if (rect2InSrcBeam || !rect1InSrcBeam) {
return false;
}

// we know rect1 is in the beam, and rect2 is not

// if rect1 is to the direction of, and rect2 is not, rect1 wins.
// for example, for direction left, if rect1 is to the left of the source
// and rect2 is below, then we always prefer the in beam rect1, since rect2
// could be reached by going down.
if (!isToDirectionOf(direction, source, rect2)) {
return true;
}

// for horizontal directions, being exclusively in beam always wins
if ((direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT)) {
return true;
}

// for vertical directions, beams only beat up to a point:
// now, as long as rect2 isn"t completely closer, rect1 wins
// e.g for direction down, completely closer means for rect2"s top
// edge to be closer to the source"s top edge than rect1"s bottom edge.
return (majorAxisDistance(direction, source, rect1)
< majorAxisDistanceToFarEdge(direction, source, rect2));
}

首先通過beamsOverlap方法來判斷兩個rect與source是否重疊等等。注意的是,在水平情況下,如果rect1重疊,則就是最優解(為什麼?比較奇怪),最後如果是豎直情況,通過FocusFinder::majorAxisDistance方法來判斷哪個離source最近。如果還是比較不出,則通過getWeightedDistanceFor方法來通過「主要距離」和「次要距離」做一個綜合的比較。

RecyclerView

繼續 focusSearch代碼的分析,剛剛只跟了ViewGroup,還有一個實現是RecyclerView的實現:

@Override
public View focusSearch(View focused, int direction) {
View result = mLayout.onInterceptFocusSearch(focused, direction);
if (result != null) {
return result;
}
// ...
}

首先通過onInterceptFocusSearch進行攔截,如果返回具體的focus View,則直接返回;否則繼續往下;onInterceptFocusSearch實現如下:

public View onInterceptFocusSearch(View focused, int direction) {
return null;
}

默認為空實現,返回null,也沒有其它的子類進行重寫,所以暫時不管這個處理,繼續看focusSearch

@Override
public View focusSearch(View focused, int direction) {
View result = mLayout.onInterceptFocusSearch(focused, direction);
if (result != null) {
return result;
}
final boolean canRunFocusFailure = mAdapter != null && mLayout != null
&& !isComputingLayout && !mLayoutFrozen;

final FocusFinder ff = FocusFinder.getInstance;
if (canRunFocusFailure
&& (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD)) {
// convert direction to absolute direction and see if we have a view there and if not
// tell LayoutManager to add if it can.
boolean needsFocusFailureLayout = false;
if (mLayout.canScrollVertically) {
final int absDir =
direction == View.FOCUS_FORWARD ? View.FOCUS_DOWN : View.FOCUS_UP;
final View found = ff.findNextFocus(this, focused, absDir);
needsFocusFailureLayout = found == null;
if (FORCE_ABS_FOCUS_SEARCH_DIRECTION) {
// Workaround for broken FOCUS_BACKWARD in API 15 and older devices.
direction = absDir;
}
}
if (!needsFocusFailureLayout && mLayout.canScrollHorizontally) {
boolean rtl = mLayout.getLayoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL;
final int absDir = (direction == View.FOCUS_FORWARD) ^ rtl
? View.FOCUS_RIGHT : View.FOCUS_LEFT;
final View found = ff.findNextFocus(this, focused, absDir);
needsFocusFailureLayout = found == null;
if (FORCE_ABS_FOCUS_SEARCH_DIRECTION) {
// Workaround for broken FOCUS_BACKWARD in API 15 and older devices.
direction = absDir;
}
}
if (needsFocusFailureLayout) {
consumePendingUpdateOperations;
final View focusedItemView = findContainingItemView(focused);
if (focusedItemView == null) {
// panic, focused view is not a child anymore, cannot call super.
return null;
}
eatRequestLayout;
mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState);
resumeRequestLayout(false);
}
result = ff.findNextFocus(this, focused, direction);
} else {
result = ff.findNextFocus(this, focused, direction);
if (result == null && canRunFocusFailure) {
consumePendingUpdateOperations;
final View focusedItemView = findContainingItemView(focused);
if (focusedItemView == null) {
// panic, focused view is not a child anymore, cannot call super.
return null;
}
eatRequestLayout;
result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState);
resumeRequestLayout(false);
}
}
if (result != null && !result.hasFocusable) {
if (getFocusedChild == null) {
// Scrolling to this unfocusable view is not meaningful since there is no currently
// focused view which RV needs to keep visible.
return super.focusSearch(focused, direction);
}
// If the next view returned by onFocusSearchFailed in layout manager has no focusable
// views, we still scroll to that view in order to make it visible on the screen.
// If it"s focusable, framework already calls RV"s requestChildFocus which handles
// bringing this newly focused item onto the screen.
requestChildOnScreen(result, null);
return focused;
}
return isPreferredNextFocus(focused, result, direction)
? result : super.focusSearch(focused, direction);
}

我們暫時只考慮direction為left,top,right,down的情況,則進入最外面if的else分支:

public View focusSearch(View focused, int direction) {
// ...
result = ff.findNextFocus(this, focused, direction);
if (result == null && canRunFocusFailure) {
consumePendingUpdateOperations;
final View focusedItemView = findContainingItemView(focused);
if (focusedItemView == null) {
// panic, focused view is not a child anymore, cannot call super.
return null;
}
eatRequestLayout;
result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState);
resumeRequestLayout(false);
}
// ...
}

首先通過FocusFinder::findNextFocus方法來獲取下一個應該獲得焦點的View,這裡獲取的結果與FocusFinder::findNextFocus邏輯一致。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 達人科技 的精彩文章:

vue-resource pos提交t數據時碰到Django csrf
Go語言學習筆記(六)net
MySQL日期、時間相關內容

TAG:達人科技 |

您可能感興趣

Oracle WebLogic Server反序列化遠程代碼執行漏洞成焦點
Beats Studio 3 Wirelss 一周使用體驗:眾人目光焦點
夜晚中的焦點!Nike 推出全新 「Tokyo Neon Collection」 別注系列
人類「染色體」潮流關注的焦點Pharrell x Adidas HU NMD扎染配色
大眾Atlas Cross Sport概念車發布,X6身段的途昂,成為焦點
焦點:Coach姐妹品牌Stuart Weitzman創意總監因行為不當辭職
再戰iBoy!Uzi與Meiko包下,Jinoo瑞文成全場焦點
iQOOpro亮相ChinaJoy,「5G」LOGO關注焦點
Facebook開源DeepFocus演算法,為VR提供逼真的焦點變換渲染
VirtualBox安全漏洞成焦點
2019 FW Supreme再爆新單品諜照?這款Box Logo短Tee是焦點
2019 FW Supreme再爆新單品諜照?這款Box Logo短Tee是焦點!
Nike Zoom Rookie 讓你成為夜晚的絕對焦點
編輯帶你遊覽華為展台 MateBook X Pro成焦點
Photonics West 2018關注焦點:3D感測應用市場
Kanye West 夫婦成 2 Chainz 婚禮現場焦點, UNDERCOVER 2018 新品上架 | HB Daily
iPhone新機無緣今年WWDC,新版iPad Pro或成全場焦點!
Red Velvet新造型備受關注,Irene的傷疤成為焦點
【焦點】R與Python之爭
Apink孫娜恩和BLACKPINK Jennie如何憑私服時尚成為熱議焦點!