[Android]Android焦点流程代码分析

时间:2022-09-25 13:29:25



以下内容为原创,欢迎转载,转载请注明

来自天天博客: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 逻辑一致。

[Android]Android焦点流程代码分析的更多相关文章

  1. Ecshop的购物流程代码分析详细说明

    Ecshop的购物流程代码分析详细说明 (2012-07-30 10:41:12) 转载▼ 标签: 购物车 结算中心 商品价格 ecshop ecshop购物流程 杂谈 分类: ECSHOP研究院 同 ...

  2. Openfire注册流程代码分析

    Openfire注册流程代码分析 一.客户端/服务端注册用户流程 经过主机连接消息确认后,客户端共发送俩条XML完成注册过程.服务器返回两条XML. 注:IQ消息节点用于处理用户的注册.好友.分组.获 ...

  3. Linux Kernel文件系统写I&sol;O流程代码分析(二)bdi&lowbar;writeback

    Linux Kernel文件系统写I/O流程代码分析(二)bdi_writeback 上一篇# Linux Kernel文件系统写I/O流程代码分析(一),我们看到Buffered IO,写操作写入到 ...

  4. Linux Kernel文件系统写I&sol;O流程代码分析(一)

    Linux Kernel文件系统写I/O流程代码分析(一) 在Linux VFS机制简析(二)这篇博客上介绍了struct address_space_operations里底层文件系统需要实现的操作 ...

  5. Android 4&period;2启动代码分析&lpar;一&rpar;

    Android系统启动过程分析 Android系统的框架架构图如下(来自网上):   Linux内核启动之后----->就到Android的Init进程 ----->进而启动Android ...

  6. android recovery 主系统代码分析

    阅读完上一篇文章: http://blog.csdn.net/andyhuabing/article/details/9226569 我们已经清楚了如何进入正常模式和Recovery模式已有深刻理解了 ...

  7. android recovery 主系统代码分析【转】

    本文转载自:http://blog.csdn.net/andyhuabing/article/details/9248713 阅读完上一篇文章: http://blog.csdn.net/andyhu ...

  8. 例子Architecting Android…The clean way&quest;----代码分析

    Presention层:   整个应用启动的时候,就执行依赖的初始化.编译项目之后,Dagger依赖框架使用ApplicationComponent生成一个DaggerApplicationCOmpo ...

  9. pf&lowbar;ring DNA接收流程代码分析

    经过一个月的学习,对pf_ring DNA的内核部分有了一些认识,本文侧重pf_ring对ixgbe的改动分析. 先说一说接收流程吧,流程如下: 其中,硬中断处理函数是ixgbe_msix_clean ...

随机推荐

  1. codeforces 732

    A. Buy a Shovel time limit per test 1 second memory limit per test 256 megabytes input standard inpu ...

  2. Android图片处理-图片压缩处理

    这里先重复温习一下上一篇,调用相册获取图片: /*** * 这个是调用android内置的intent,来过滤图片文件 ,同时也可以过滤其他的 */ Intent intent = new Inten ...

  3. 库zlog的使用手册

    库官方网址: 使用手册:    http://hardysimpson.github.io/zlog/UsersGuide-CN.html#htoc11 [formats] simple = &quo ...

  4. bug制造者又上线了

    上一家公司,领导经常这样表扬一位同事,“你写的bug远比你的功能值钱...” 今天特么的突然觉得我好像也有这样的功能,不知道是上次回家把脑子落家里了还是,前几天淋雨脑子进了水了. 呢么简单一个功能,愣 ...

  5. linux中一些简便的命令之tac&sol;comm

    tac tac是cat的反写,即反序显示文件内容 如文件a.txt内容如下: 1 2 3 4 5 则tac a.txt打印如下: 54321 我们可以使用awk来实现tac的功能: awk '{arr ...

  6. 【xsy2425】容器 dp

    题目大意:有$n$个人,区间大小为$m$,每个人必须覆盖一段区间$[l_i,r_i]$,问你存在多少种不同的覆盖方案,使得区间上每个位置被覆盖的次数不超过$t$. 两种方案被定义为不同当且仅当存在第i ...

  7. javascript 60行编写的俄罗斯方块游戏

    转自 http://***/share/1759652641295360.htm <!doctype html><html><head></head>& ...

  8. Flask初级(五)flash在模板中使用继承,模板的模板

    Project name :Flask_Plan templates:templates static:static 继续上一篇文章. 我们不希望每个页面都写一遍引入js,css,导航条……………… ...

  9. 黑马JavaScript学习一 BOM之Window对象定时器功能

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. 惠普台式机在UEFI BIOS设置通电自动开机 影响电脑自动重启关不了机设置

    设置通电自动开机 影响电脑自动重启关不了机设置   惠普台式机在UEFI BIOS中 1. 开机时不断点击F10键进入BIOS,选择Advanced(高级)然后选择Boot Options,点击回车 ...