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



以下內容為原創,歡迎轉載,轉載請注明
來自天天博客:http://www.cnblogs.com/tiantianbyconan/p/7286503.html

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

public View focusSearch(@FocusRealDirection int direction) {
   if (mParent != null) {
       return mParent.focusSearch(this, direction);
   } else {
       return null;
   }
}
不斷地調用父控件來進行搜索,focusSearch有兩個實現:`ViewGroup`和`RecyclerView`,先看`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`方法進行搜索;否則調用父控件的`focusSearch`。`FocusFinder::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 邏輯一致。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM