• [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 逻辑一致。

  • 相关阅读:
    Python基础04 字典基本操作
    Python基础03 列表、元组基本操作
    Python基础02 字符串基本操作
    Python基础07 函数作用域、嵌套函数、闭包函数、高阶函数及装饰器的理解
    Python随机数random模块学习,并实现生成6位验证码
    Python与时间相关的time、datetime模块的使用
    Python PIL库安装
    Python中可变对象和不可变对象
    Mac环境下Docker及Splash的安装运行教程
    redis 链表(list)操作
  • 原文地址:https://www.cnblogs.com/tiantianbyconan/p/7286503.html
Copyright © 2020-2023  润新知