android一般都是手机或者平板,一般都是点击的时候获取焦点,当我们添加遥控或手柄支持焦点移动时,这个时候焦点的查找就比较明显了,那么Android的焦点是怎么查找的呢。
我们从handleImeFinishedEvent(ViewRootImpl.java)开始了解焦点的查找流程,handleImeFinishedEven是由dispatchImeFinishedEvent触发,dispatchImeFinishedEvent又是由InputMethodManager触发来的,
handleImeFinishedEvent中跟焦点相关的代码:
if (direction != 0) {
View focused = mView.findFocus();//当前拥有焦点的控件
if (focused != null) {
View v = focused.focusSearch(direction);//根据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);
}
if (v.requestFocus(direction, mTempRect)) {//请求焦点网
playSoundEffect(SoundEffectConstants
.getContantForFocusDirection(direction));
finishInputEvent(q, true);
return;
}
}
// Give the focused view a last chance to handle the dpad key.
if (mView.dispatchUnhandledMove(focused, direction)) {//以前的控件 焦点改变事件
finishInputEvent(q, true);
return;
}
}
public View focusSearch(int direction) {
if (mParent != null) {//父控件不为空,调用它的focusSearch
return mParent.focusSearch(this, direction);
} else {
return null;
}
}
public View focusSearch(View focused, int direction) {
if (isRootNamespace()) {//已经是Root层 (installDecor mDecor.setIsRootNamespace(true);)
// 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) {//继续调用父控件的focusSearch
return mParent.focusSearch(focused, direction);
}
return null;
}
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
View next = null;
if (focused != null) {
next = findNextUserSpecifiedFocus(root, focused, direction);//是xml里通过android:nextFocusUp="..."等或者代码特别指定的焦点顺序
}
if (next != null) {//已经找到
return next;
}
ArrayList focusables = mTempList;//mTempList
try {
focusables.clear();
root.addFocusables(focusables, direction);//获取所有可以获取焦点的控件
if (!focusables.isEmpty()) {
next = findNextFocus(root, focused, focusedRect, direction, focusables);//查找下一个焦点控件
}
} finally {
focusables.clear();
}
return next;
}
先看一下该控件是否已经设置过它的焦点移动事件,indNextUserSpecifiedFocus就是干这个事的,此方法先去判断特定Id值是否存在,若存在则查询出Id对应的view.其实这些Id就是xml里通过android:nextFocusUp="..."等或者代码特别指定的焦点顺序.所以在此过程先判断,若存在,说明下个焦点已经找到,直接返回.,未找到,则调用findNextFocus继续查找
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,
int direction, ArrayList 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);
}
}
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);//设置mBestCandidateRect
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)) {//比较和Best哪个更好
mBestCandidateRect.set(mOtherRect);
closest = focusable;//更合适
}
}
return closest;//返回
}
关于移动的时候有时候没有焦点,我们可以查看在findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction)这个函数中,root.addFocusables(focusables, direction);/可能获取到了很多有焦点的控件,最后找到的合适的控件可能不是我们想要的,这时我们可以把某些控件设置为不能获取焦点,这样我们移动焦点时,可能是我们想要的焦点(偷懒的做法)
mDaySpinner.setFocusable(false);
mMonthSpinner.setFocusable(false);
mYearSpinner.setFocusable(false);
mYearSpinnerInput.setSelection(0, 4); //获取焦点时选中的text