![](https://obs-emcsapp-public.obs.cn-north-4.myhwclouds.com:443/wechatSpider/modb_71d6277a-45b6-11eb-a366-5254001c05fe.png)
罗宁,六年安卓踩坑经验,致力于底层平台、上层应用等多领域开发。文能静坐弹吉他,武能通宵写代码。
<View.java>
public final boolean requestFocus() {
// 默认使用 FOCUS_DOWN 进行聚焦
return requestFocus(View.FOCUS_DOWN);
}
<View.java>
private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
// need to be focusable
// 如果该 view 设置的 focusable = false,直接返回
if ((mViewFlags & FOCUSABLE) != FOCUSABLE
|| (mViewFlags & VISIBILITY_MASK) != VISIBLE) {
return false;
}
// need to be focusable in touch mode if in touch mode
// 触摸模式下
if (isInTouchMode() &&
(FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
return false;// 如果该 view 设置的 focusableInTouchMode = false,直接返回
}
// need to not have any parents blocking us
if (hasAncestorThatBlocksDescendantFocus()) {
return false;// 如果 parent 中设置了 FOCUS_BLOCK_DESCENDANTS,直接返回
}
handleFocusGainInternal(direction, previouslyFocusedRect);
return true;// 聚焦成功
}
1 requestFocusNoSearch
首先第一步会判断当前 View 的 focusable 状态,如果是 false,说明该 View并不能获取焦点,也就没有必要再往下走了。 接着会判断是否触摸模式,在触摸模式下,如果 focusableInTouchMode 是 false 的话,也说明该 View 通过触摸并不能获取焦点,也没必要往下走了。 继续看下面一个判断 hasAncestorThatBlocksDescendantFocus() 方法:
<View.java>
private boolean hasAncestorThatBlocksDescendantFocus() {
final boolean focusableInTouchMode = isFocusableInTouchMode();
ViewParent ancestor = mParent;
while (ancestor instanceof ViewGroup) {
final ViewGroup vgAncestor = (ViewGroup) ancestor;
if (vgAncestor.getDescendantFocusability() == ViewGroup.FOCUS_BLOCK_DESCENDANTS
|| (!focusableInTouchMode && vgAncestor.shouldBlockFocusForTouchscreen())) {
return true;
} else {
ancestor = vgAncestor.getParent();
}
}
return false;
}
2 hasAncestorThatBlocksDescendantFocus
<View.java>
void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {
if (DBG) {
System.out.println(this + " requestFocus()");
}
if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
mPrivateFlags |= PFLAG_FOCUSED;// 更新标记位(isFocused判断依据)
// 当前状态下的焦点
View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;
if (mParent != null) {
mParent.requestChildFocus(this, this);// 清除当前焦点,将 mFocus 变量更新值为当前期望聚焦的 view
updateFocusedInCluster(oldFocus, direction);// android 高版本新增的方法,此方法和键盘相关,在此不作重点关注
}
if (mAttachInfo != null) {
// 通知 ViewTreeObserver 焦点变化
mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
}
onFocusChanged(true, direction, previouslyFocusedRect);// 通知焦点变化回调
refreshDrawableState();// 当前 view 聚焦,刷新 drawable 状态
}
}
3 handleFocusGainInternal
<ViewGroup.java>
@Override
public void requestChildFocus(View child, View focused) {
if (DBG) {
System.out.println(this + " requestChildFocus()");
}
// 再次判断是否设置了 FOCUS_BLOCK_DESCENDANTS
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);
}
}
4 requestChildFocus
<ViewParent.java>
/**
* Called when a child of this parent wants focus
*
* @param child The child of this ViewParent that wants focus. This view
* will contain the focused view. It is not necessarily the view that
* actually has focus.
* @param focused The view that is a descendant of child that actually has
* focus
*/
public void requestChildFocus(View child, View focused);
再次判断是否设置了 FOCUS_BLOCK_DESCENDANTS,如果拦截则不继续往下走。 一般情况下,当前焦点 mFocused 都和我们期望聚焦的 view 并非同一个,则进入分支调用 mFocused.unFocus(focused)
<View.java>
void unFocus(View focused) {
if (DBG) {
System.out.println(this + " unFocus()");
}
clearFocusInternal(focused, false, false);
}
// 最终调用这个方法
void clearFocusInternal(View focused, boolean propagate, boolean refocus) {
// 其实没用到focused这个参数
if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
mPrivateFlags &= ~PFLAG_FOCUSED;
if (propagate && mParent != null) {
mParent.clearChildFocus(this);// 通知 parent 清除自己(当前的焦点)的 mFocus 值,因为焦点已经不在该 View 树节点下
}
onFocusChanged(false, 0, null);// 回调焦点状态变更的通知
refreshDrawableState();// 刷新失去焦点后的 drawable 状态
if (propagate && (!refocus || !rootViewRequestFocus())) {
notifyGlobalFocusCleared(this);
}
}
}
5 clearFocusInternal
通知 parent 调用 clearChildFocus 将 mFocus 变量置 null,因为焦点已经不在该 View 树节点下。 回调自身的焦点状态变更的通知,我们通常所设置的 setOnFocusChangeListener 的监听就是在这里面进行触发回调的。 由于第 1 步中清除了自己的焦点状态,失焦之后自然需要刷新视图状态,这里会调用 refreshDrawableState 进行 drawableState 的刷新,也就是我们通常在 xml 中设置的 selector 状态属性。
注意一点: 这里面的 focused 参数其实根本没用到,但是这个 focused 才是真正最直接的焦点。
<ViewGroup.java>
ViewGroup.requestChildFocus
...
// 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);
}
...
<ViewGroup.java>
@Override
public View findFocus() {
if (DBG) {
System.out.println("Find focus in " + this + ": flags="
+ isFocused() + ", child=" + mFocused);
}
// 如果当前 isFocused 了,说明我自己已经是焦点了,直接返回自己
if (isFocused()) {
return this;
}
// mFocus 不为 null,说明焦点在这个 mFocus 的 View 树下
if (mFocused != null) {
return mFocused.findFocus();
}
return null;
}
<View.java>
public View findFocus() {
// 当遍历到直接子 View 之后就是根据标志位进行判断
return (mPrivateFlags & PFLAG_FOCUSED) != 0 ? this : null;
}
<ViewGroup.java>
public boolean hasFocus() {
return (mPrivateFlags & PFLAG_FOCUSED) != 0 || mFocused != null;
}
<ViewRootImpl.java>
@Override
public void requestChildFocus(View child, View focused) {
if (DEBUG_INPUT_RESIZE) {
Log.v(mTag, "Request child focus: focus now " + focused);
}
checkThread();
scheduleTraversals();// UI 重绘
}
<View.java>
@ViewDebug.ExportedProperty(category = "focus")
public boolean isFocused() {
return (mPrivateFlags & PFLAG_FOCUSED) != 0;
}
<ViewGroup.java>
@Override
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
if (DBG) {
System.out.println(this + " ViewGroup.requestFocus direction="
+ direction);
}
int descendantFocusability = getDescendantFocusability();
// 主要还是看 ViewGroup 设置的焦点拦截模式
switch (descendantFocusability) {
case FOCUS_BLOCK_DESCENDANTS:// 拦截掉了焦点,直接调用 super 的逻辑在自己中 requestFocus
return super.requestFocus(direction, previouslyFocusedRect);
case FOCUS_BEFORE_DESCENDANTS: {// 首先调用 super 的逻辑在自己中 requestFocus,如果自己请求焦点失败再遍历子 View 进行 requestFocus
final boolean took = super.requestFocus(direction, previouslyFocusedRect);
return took ? took : onRequestFocusInDescendants(direction, previouslyFocusedRect);
}
case FOCUS_AFTER_DESCENDANTS: {// 与 FOCUS_BEFORE_DESCENDANTS 相反,先遍历子 View 进行 requestFocus,如果子 View 都请求焦点失败后再调用 super 的逻辑在自己中 requestFocus
final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect);
return took ? took : super.requestFocus(direction, previouslyFocusedRect);
}
default:
throw new IllegalStateException("descendant focusability must be "
+ "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS "
+ "but is " + descendantFocusability);
}
}
FOCUS_BLOCK_DESCENDANTS:自身拦截掉焦点,直接对自己进行 requestFocus 调用去请求焦点; FOCUS_BEFORE_DESCENDANTS:自身优先子 View 获得焦点,先对自己进行 requestFocus 调用去请求焦点,如果失败再遍历子 View 让子 View 进行聚焦; FOCUS_AFTER_DESCENDANTS:先遍历子 View 让子 View 进行聚焦,如果子 View 都没有聚焦,则再对自己进行 requestFocus 调用去请求焦点。
<ViewGroup.java>
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;// mChildren 数组中保存了所有的 childView
for (int i = index; i != end; i += increment) {
View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {// 遍历子 View,并且 View 可见
if (child.requestFocus(direction, previouslyFocusedRect)) {// 该子 View 请求焦点
return true;// 请求焦点成功,直接返回
}
}
}
return false;
}
![](https://obs-emcsapp-public.obs.cn-north-4.myhwclouds.com:443/wechatSpider/modb_71ed8c62-45b6-11eb-a366-5254001c05fe.png)
![](https://obs-emcsapp-public.obs.cn-north-4.myhwclouds.com:443/wechatSpider/modb_723dea54-45b6-11eb-a366-5254001c05fe.png)
文章转载自悦专栏,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。