点可以理解为选中态,在Android TV上起很重要的作用。一个视图控件只有在获得焦点的状态下,才能响应按键的Click事件。
相对于手机上用手指点击屏幕产生的Click事件, 在TV中通过点击遥控器的方向键来控制焦点的移动。当焦点移动到目标控件上之后,按下遥控器的确定键,才会触发一个Click事件,进而去做下一步的处理
在处理焦点的时候,有一些基础的用法需要知道。
首先,一个控件isFocusable()需要为true才有资格可以获取到焦点。如果想要在触摸模式下获取焦点,需要通过setFocusableInTouchMode(boolean)来设置。也可以直接在xml布局文件中指定:
android:focusable="true",
android:focusableInTouchMode="true"
一、KeyEvent分发
keyEvent 分发过程:
而当按下遥控器的按键时,会产生一个按键事件,就是KeyEvent,包含“上”,“下”,“左”,“右”,“返回”,“确定”等指令。焦点的处理就在KeyEvent的分发当中完成。
首先,KeyEvent会流转到ViewRootImpl中开始进行处理,具体方法是内部类ViewPostImeInputStage
中的processKeyEvent
:
private int processKeyEvent(QueuedInputEvent q) {
// 将系统输入转为keyevent事件
final KeyEvent event = (KeyEvent)q.mEvent;
// 1. 先由DecorView进行按键事件派发
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
......
// Handle automatic focus changes.
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (groupNavigationDirection != 0) {
if (performKeyboardGroupNavigation(groupNavigationDirection)) {
return FINISH_HANDLED;
}
} else {
//2. 处理键盘的上下左右的焦点查找
if (performFocusNavigation(event)) {
return FINISH_HANDLED;
}
}
}
return FORWARD;
}
- mView.dispatchKeyEvent 由DecorView进行按键事件派发。返回 true事件消耗,不往下执行焦点搜索与请求,返回 false,继续往下执行。
- 如果事件没有被view框架消耗,之后会通过focusSearch去找下一个焦点view
接下来先看一下KeyEvent在view框架中的分发:
- DecorView 的 dispatchKeyEvent 函数
public boolean dispatchKeyEvent(KeyEvent event) {
final int keyCode = event.getKeyCode();
final int action = event.getAction();
final boolean isDown = action == KeyEvent.ACTION_DOWN;
......
if (!mWindow.isDestroyed()) {
// 这里的cb就是activity对象,Activity实现了Window.Callback接口
final Window.Callback cb = mWindow.getCallback();
// cb.dispatchKeyEvent 调用的是 Activity的dispatchKeyEvent
final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
: super.dispatchKeyEvent(event);
// 是否消耗事件
if (handled) {
return true;
}
}
return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
: mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
}
- Activity 的 dispatchKeyEvent 函数
public boolean dispatchKeyEvent(KeyEvent event) {
onUserInteraction();
final int keyCode = event.getKeyCode();
if (keyCode == KeyEvent.KEYCODE_MENU &&
mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
return true;
}
Window win = getWindow();
// 调用 PhoneWindow 的 superDispatchKeyEvent
// 里面又调用 mDecor.superDispatchKeyEvent(event)
// mDecor为 DecorView.
if (win.superDispatchKeyEvent(event)) {
return true;
}
View decor = mDecor;
if (decor == null) decor = win.getDecorView();
return event.dispatch(this, decor != null
? decor.getKeyDispatcherState() : null, this);
}
这里也是可以做焦点控制,最好是在 event.getAction() == KeyEvent.ACTION_DOWN 进行.
因为android 的 ViewRootlmpl 的 processKeyEvent 焦点搜索与请求的地方 进行了判断if (event.getAction() == KeyEvent.ACTION_DOWN)
- DecorView 的 superDispatchKeyEvent 函数
public boolean superDispatchKeyEvent(KeyEvent event) {
// Give priority to closing action modes if applicable.
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
final int action = event.getAction();
// Back cancels action modes first.
if (mPrimaryActionMode != null) {
if (action == KeyEvent.ACTION_UP) {
mPrimaryActionMode.finish();
}
return true;
}
}
//DecorView继承FrameLayout 这里调用的是 ViewGroup.dispatchKeyEvent
if (super.dispatchKeyEvent(event)) {
return true;
}
return (getViewRootImpl() != null) && getViewRootImpl().dispatchUnhandledKeyEvent(event);
}
- ViewGroup 的 dispatchKeyEvent 函数
public boolean dispatchKeyEvent(KeyEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 1);
}
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
// 调用 view.dispatchKeyEvent
if (super.dispatchKeyEvent(event)) {
return true;
}
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
// 调用 focus view 的 dispatchKeyEvent
if (mFocused.dispatchKeyEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
return false;
}
• 首先ViewGroup会一层一层往上执行父类的dispatchKeyEvent方法,如果返回true那么父类的dispatchKeyEvent方法就会返回true,也就代表父类消费了该焦点事件,那么焦点事件自然就不会往下进行分发。
• 然后ViewGroup会判断mFocused这个view是否为空,如果为空就会return false,焦点继续往下传递;如果不为空,那就会return mFocused的dispatchKeyEvent方法返回的结果。这个mFocused其实是ViewGroup中当前获取焦点的子View
- 最后调用 View 的 dispatchKeyEvent
public boolean dispatchKeyEvent(KeyEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 0);
}
// 调用 mOnKeyListener onKey回调,如果这里也没有消耗事件,继续往下面执行
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
return true;
}
// 主要是处理一些回调,比如 onKeyDown,onKeyLongPress,onKeyUp等等
if (event.dispatch(this, mAttachInfo != null
? mAttachInfo.mKeyDispatchState : null, this)) {
return true;
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
发现执行了onKeyListener中的onKey方法,如果onKey方法返回true,那么dispatchKeyEvent方法也会返回true
如果想要修改ViewGroup焦点事件的分发
• 重写view的dispatchKeyEvent方法
• 给某个子view设置onKeyListener监听
二、第一次获取焦点
下面再来看一下如果一个页面第一次进入,系统是如何确定焦点是定位在哪个view上的
- ViewRootImpl中 performTraversals方法发起焦点获取
if (mFirst) {
if (sAlwaysAssignFocus || !isInTouchMode()) {
// handle first focus request
if (DEBUG_INPUT_RESIZE) {
Log.v(mTag, "First: mView.hasFocus()=" + mView.hasFocus());
}
if (mView != null) {
if (!mView.hasFocus()) {
// 调用 View 的 restoreDefaultFocus
mView.restoreDefaultFocus();
if (DEBUG_INPUT_RESIZE) {
Log.v(mTag, "First: requested focused view=" + mView.findFocus());
}
} else {
if (DEBUG_INPUT_RESIZE) {
Log.v(mTag, "First: existing focused view=" + mView.findFocus());
}
}
}
} else {
// Some views (like ScrollView) won't hand focus to descendants that aren't within
// their viewport. Before layout, there's a good change these views are size 0
// which means no children can get focus. After layout, this view now has size, but
// is not guaranteed to hand-off focus to a focusable child (specifically, the edge-
// case where the child has a size prior to layout and thus won't trigger
// focusableViewAvailable).
View focused = mView.findFocus();
if (focused instanceof ViewGroup
&& ((ViewGroup) focused).getDescendantFocusability()
== ViewGroup.FOCUS_AFTER_DESCENDANTS) {
focused.restoreDefaultFocus();
}
}
}
- View.restoreDefaultFocus
public boolean restoreDefaultFocus() {
return requestFocus(View.FOCUS_DOWN);
}
由于DecorView继承自FrameLayout,这里调用的是ViewGroup的requestFocus
- ViewGroup.requestFocus
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
int descendantFocusability = getDescendantFocusability();
boolean result;
switch (descendantFocusability) {
case FOCUS_BLOCK_DESCENDANTS:
result = super.requestFocus(direction, previouslyFocusedRect);
break;
case FOCUS_BEFORE_DESCENDANTS: {
final boolean took = super.requestFocus(direction, previouslyFocusedRect);
result = took ? took : onRequestFocusInDescendants(direction,
previouslyFocusedRect);
break;
}
case FOCUS_AFTER_DESCENDANTS: {
// 调用 onRequestFocusInDescendants 遍历子控件进行请求
final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect);
result = took ? took : super.requestFocus(direction, previouslyFocusedRect);
break;
}
default:
throw new IllegalStateException("descendant focusability must be "
+ "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS "
+ "but is " + descendantFocusability);
}
if (result && !isLayoutValid() && ((mPrivateFlags & PFLAG_WANTS_FOCUS) == 0)) {
mPrivateFlags |= PFLAG_WANTS_FOCUS;
}
return result;
}
descendantFocusability:
• FOCUS_AFTER_DESCENDANTS:先分发给Child View进行处理,如果所有的Child View都没有处理,则自己再处理
• FOCUS_BEFORE_DESCENDANTS:ViewGroup先对焦点进行处理,如果没有处理则分发给child View进行处理
• FOCUS_BLOCK_DESCENDANTS:ViewGroup本身进行处理,不管是否处理成功,都不会分发给ChildView进行处理
因为 PhoneWindow 给 DecoreView 初始化时设置 了 setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS),所以这里默认是FOCUS_AFTER_DESCENDANTS
- ViewGroup.onRequestFocusInDescendants 遍历子控件
protected boolean onRequestFocusInDescendants(int direction,Rect previouslyFocusedRect) {
int index;
int increment;
int end;
int count = mChildrenCount;
if ((direction & FOCUS_FORWARD) != 0) {
index = 0;
increment = 1;
end = count;
} else {
index = count - 1;
increment = -1;
end = -1;
}
final View[] children = mChildren;
for (int i = index; i != end; i += increment) {
View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
// 遍历子view调用requestFocus
if (child.requestFocus(direction, previouslyFocusedRect)) {
return true;
}
}
}
return false;
}
- 子view争取焦点
# View.java
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
return requestFocusNoSearch(direction, previouslyFocusedRect);
}
private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
// 如果focusable为false直接return
if (!canTakeFocus()) {
return false;
}
if (isInTouchMode() &&
(FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
return false;
}
// need to not have any parents blocking us
if (hasAncestorThatBlocksDescendantFocus()) {
return false;
}
if (!isLayoutValid()) {
mPrivateFlags |= PFLAG_WANTS_FOCUS;
} else {
clearParentsWantFocus();
}
// 关键函数
handleFocusGainInternal(direction, previouslyFocusedRect);
return true;
}
void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {
if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
mPrivateFlags |= PFLAG_FOCUSED;
// 获取父布局的老焦点.
View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;
if (mParent != null) {
// 调用requestChildFocus,告诉上一层父布局,
mParent.requestChildFocus(this, this);
updateFocusedInCluster(oldFocus, direction);
}
if (mAttachInfo != null) {
//全局焦点监听的回调.
// 调用方式: View.getViewTreeObserver().addOnGlobalFocusChangeListener
mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
}
// 回调处理.
onFocusChanged(true, direction, previouslyFocusedRect);
refreshDrawableState();
}
}
# ViewGroup.java
public void requestChildFocus(View child, View focused) {
if (DBG) {
System.out.println(this + " requestChildFocus()");
}
if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
return;
}
// Unfocus us, if necessary
super.unFocus(focused);
// We had a previous notion of who had focus. Clear it.
if (mFocused != child) {
if (mFocused != null) {
mFocused.unFocus(focused);
}
// 保存焦点
mFocused = child;
}
if (mParent != null) {
// 一层一层的回调父布局
mParent.requestChildFocus(this, focused);
}
}
到此第一次请求焦点的过程基本告一个段落
三、焦点搜索
焦点移动的时候,默认的情况下,会按照一种算法去找在指定移动方向上最近的邻居。在一些情况下,焦点的移动可能跟开发者的意图不符,这时开发者可以在布局文件中使用下面这些XML属性来指定下一个焦点对象:
nextFocusDown
nextFocusLeft
nextFocusRight
nextFocusUp
在KeyEvent分发中已经知道如果分发过程中event没有被消耗,就会根据方向搜索以及请求焦点View
- performFocusNavigation
dispatchKeyEvent过程中没有view消耗keyEvent,如果event.getAction() == KeyEvent.ACTION_DOWN 则调用performFocusNavigation搜索下一个焦点
private boolean performFocusNavigation(KeyEvent event) {
int direction = 0;
......
if (direction != 0) {
// 一层一层的查找,找到真正的焦点view
View focused = mView.findFocus();
if (focused != null) {
// 调用焦点view的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);
}
// 调用搜索到的view的requestFocus进行焦点获取,流程同第一次焦点获取
if (v.requestFocus(direction, mTempRect)) {
playSoundEffect(SoundEffectConstants
.getContantForFocusDirection(direction));
return true;
}
}
// 进行最后的垂死挣扎,
// 这里可以处理一些焦点问题或者滚动翻页问题
if (mView.dispatchUnhandledMove(focused, direction)) {
return true;
}
} else {
if (mView.restoreDefaultFocus()) {
return true;
}
}
}
return false;
}
- 调用View的focusSearch开始搜索焦点
View并不会直接去找焦点,而是交给它的parent去找。逐渐调用VIewGroup的focusSearch方法去搜索知道最外层的布局。最终实际上调用的是FocusFinder.getInstance().findNextFocus
# View
public View focusSearch(@FocusRealDirection int direction) {
if (mParent != null) {
return mParent.focusSearch(this, direction);
} else {
return null;
}
}
# ViewGroup
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的indNextFocus方法
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
View next = null;
ViewGroup effectiveRoot = getEffectiveRoot(root, focused);
// 流程一:
if (focused != null) {
// 寻找用户指定的下一个焦点
next = findNextUserSpecifiedFocus(effectiveRoot, focused, direction);
}
//如果找到,直接返回用户指定的焦点
if (next != null) {
return next;
}
// 流程二:
ArrayList focusables = mTempList;
try {
focusables.clear();
// 把当前root下的所有direction方向上可以获得焦点的view加入列表
effectiveRoot.addFocusables(focusables, direction);
if (!focusables.isEmpty()) {
// 继续寻找当前root下的焦点
next = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables);
}
} finally {
focusables.clear();
}
return next;
}
流程一:查找用户指定的下一个焦点
1. FocusFinder findUserSetNextFocus()找到用户指定的下一个焦点
private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction) {
// check for user specified next focus
View userSetNextFocus = focused.findUserSetNextFocus(root, direction);
View cycleCheck = userSetNextFocus;
boolean cycleStep = true; // we want the first toggle to yield false
while (userSetNextFocus != null) {
// 判断是否可以获得焦点
if (userSetNextFocus.isFocusable()
&& userSetNextFocus.getVisibility() == View.VISIBLE
&& (!userSetNextFocus.isInTouchMode()
|| userSetNextFocus.isFocusableInTouchMode())) {
return userSetNextFocus;
}
userSetNextFocus = userSetNextFocus.findUserSetNextFocus(root, direction);
if (cycleStep = !cycleStep) {
cycleCheck = cycleCheck.findUserSetNextFocus(root, direction);
if (cycleCheck == userSetNextFocus) {
// found a cycle, user-specified focus forms a loop and none of the views
// are currently focusable.
break;
}
}
}
return null;
}
2. View.findUserSetNextFocus
View findUserSetNextFocus(View root, @FocusDirection int direction) {
switch (direction) {
case FOCUS_LEFT:
// 如果mNextFocusLeftId == View.NO_ID 即用户没有指定下一个焦点直接返回null
if (mNextFocusLeftId == View.NO_ID) return null;
return findViewInsideOutShouldExist(root, mNextFocusLeftId);
case FOCUS_RIGHT:
......
}
}
return null;
}
3. View.findViewInsideOutShouldExist
private View findViewInsideOutShouldExist(View root, int id) {
if (mMatchIdPredicate == null) {
mMatchIdPredicate = new MatchIdPredicate();
}
// 要寻找的下一个焦点的view的id
mMatchIdPredicate.mId = id;
View result = root.findViewByPredicateInsideOut(this, mMatchIdPredicate);
if (result == null) {
Log.w(VIEW_LOG_TAG, "couldn't find view with id " + id);
}
return result;
}
4. View.findViewByPredicateInsideOut
public final T findViewByPredicateInsideOut(
View start, Predicate predicate) {
View childToSkip = null;
for (;;) {
// 判断一下start跟id指定的view是否是同一个,同一个直接返回
T view = start.findViewByPredicateTraversal(predicate, childToSkip);
if (view != null || start == this) {
return view;
}
// 如果没有找到,则一层层找到start的父view继续比较
ViewParent parent = start.getParent();
if (parent == null || !(parent instanceof View)) {
return null;
}
childToSkip = start;
start = (View) parent;
}
}
5. View.findViewByPredicateTraversal 查找子view中是否有对应id的view
protected T findViewByPredicateTraversal(Predicate predicate,View childToSkip) {
if (predicate.test(this)) {
return (T) this;
}
final View[] where = mChildren;
final int len = mChildrenCount;
for (int i = 0; i < len; i++) {
View v = where[i];
if (v != childToSkip && (v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
v = v.findViewByPredicate(predicate);
if (v != null) {
return (T) v;
}
}
}
return null;
}
流程二:获取搜索方向上所有可以获取焦点的view,使用算法查找下一个view
addFocusables() 获取搜索方向上可获得焦点的view
public void addFocusables(ArrayList views, int direction, int focusableMode) {
final int focusableCount = views.size();
final int descendantFocusability = getDescendantFocusability();
final boolean blockFocusForTouchscreen = shouldBlockFocusForTouchscreen();
final boolean focusSelf = (isFocusableInTouchMode() || !blockFocusForTouchscreen);
// 覆盖子view,自己获取焦点
if (descendantFocusability == FOCUS_BLOCK_DESCENDANTS) {
if (focusSelf) {
super.addFocusables(views, direction, focusableMode);
}
return;
}
if (blockFocusForTouchscreen) {
focusableMode |= FOCUSABLES_TOUCH_MODE;
}
// 自己优先获取焦点
if ((descendantFocusability == FOCUS_BEFORE_DESCENDANTS) && focusSelf) {
super.addFocusables(views, direction, focusableMode);
}
int count = 0;
final View[] children = new View[mChildrenCount];
for (int i = 0; i < mChildrenCount; ++i) {
View child = mChildren[i];
// 判断view是否可见
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
children[count++] = child;
}
}
// 根据位置对children进行排序
FocusFinder.sort(children, 0, count, this, isLayoutRtl());
for (int i = 0; i < count; ++i) {
children[i].addFocusables(views, direction, focusableMode);
}
// When set to FOCUS_AFTER_DESCENDANTS, we only add ourselves if
// there aren't any focusable descendants. 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) && focusSelf
&& focusableCount == views.size()) {
super.addFocusables(views, direction, focusableMode);
}
}
descendantFocusability属性决定了ViewGroup和其子view的聚焦优先级
• FOCUS_BLOCK_DESCENDANTS:viewgroup会覆盖子类控件而直接获得焦点
• FOCUS_BEFORE_DESCENDANTS:viewgroup会覆盖子类控件而直接获得焦点
• FOCUS_AFTER_DESCENDANTS:viewgroup只有当其子类控件不需要获取焦点时才获取焦点
addFocusables
的第一个参数views是由root决定的。在ViewGroup的focusSearch方法中传进来的root是DecorView,也可以主动调用FocusFinder的findNextFocus方法,在指定的ViewGroup中查找焦点。
FocusFinder.findNextFocus
查找焦点
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,
int direction, ArrayList focusables) {
// 1. 焦点不为空的情况
if (focused != null) {
if (focusedRect == null) {
focusedRect = mFocusedRect;
}
// fill in interesting rect from focused
focused.getFocusedRect(focusedRect);
root.offsetDescendantRectToMyCoords(focused, focusedRect);
} else {
// 2. 焦点为空的情况
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);
}
}
View findNextFocusInAbsoluteDirection(ArrayList 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在focusedReact的右边,并且距离focusedReact的右边一个像素
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);
//找到最佳的候选的view,则返回
if (isBetterCandidate(direction, focusedRect, mOtherRect, mBestCandidateRect)) {
mBestCandidateRect.set(mOtherRect);
closest = focusable;
}
}
return closest;
}