关于View的焦点
##1.什么是View/ViewGroup的焦点,焦点的作用是什么?
从广义上来说,焦点就是用户当前正在或者下一步可能操作的目标,在按键模式下(现在大部分手机都是触摸模式)一般具有焦点的View都会高亮展示,以提示用户当前可以操作的目标。
从狭义上来说,焦点就是View中的mPrivateFlags成员字段被添加上了PFLAG_FOCUSED标示。
##2.View和ViewGroup中hasFocus方法的区别?
//View的hasFocus方法
public boolean hasFocus() {
return (mPrivateFlags & PFLAG_FOCUSED) != 0;
}
//ViewGroup的hasFocus方法
public boolean hasFocus() {
return (mPrivateFlags & PFLAG_FOCUSED) != 0 || mFocused != null;
}
上述是View的hasFocus方法就是去检查当前view的mPrivateFlags字段中是否具有PFLAG_FOCUSED,而ViewGroup的hasFocus方法出了检查PFLAG_FOCUSED,还可以检查mFocused变量,也就是说ViewGroup的hasFocus方法返回两种情况
- 当前ViewGroup自身就是焦点所有者
- 当前ViewGroup自身没有焦点,但是具有焦点的控件在其子View中,这个时候mFocused就是焦点链路上的一个节点
上述的绿色背景的节点就构成了一个焦点链路,最顶层的ViewGroup的mFocused执行第二层的ViewGroup,第二层的中的mFocused指向View,该View就是真正获取到焦点的控件
##3.View.requestFocus方法是怎么获取和释放焦点的?
public final boolean requestFocus() {
return requestFocus(View.FOCUS_DOWN);
}
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
return requestFocusNoSearch(direction, previouslyFocusedRect);
}
private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
// need to be focusable
// 检查当前View是否能具有焦点
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;
}
// need to not have any parents blocking us
// 检查其所属的ViewGroup是否管控了焦点分发策略,后面会说道
if (hasAncestorThatBlocksDescendantFocus()) {
return false;
}
//开始焦点更换流程
handleFocusGainInternal(direction, previouslyFocusedRect);
return true;
}
view的requestFocus方法先检查当前View是否具有获取焦点的资格,之后再检查是否在触摸模式下,如果在该模式下,则还会检查是否具有触摸模式的焦点资格(在控件系统中,存在两种模式,分别是按键模式和触摸模式)。之后会检查其ViewGroup是否允许子View获取焦点,当ViewGroup中具有FOCUS_BLOCK_DESCENDANTS标示时,就会阻止子View获取焦点,如果允许子View获取焦点,则开始焦点请求
//如果当前View不是焦点控件,才进行后续操作
if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
//给当前View加上焦点标示
mPrivateFlags |= PFLAG_FOCUSED;
//先找到最顶层的ViewGroup,之后根据焦点链路找到前一个真正的焦点控件
View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;
if (mParent != null) {
// 通过mParent向上一级View回溯,通知ViewGroup焦点控件更换了,让ViewGroup更新焦点链路
mParent.requestChildFocus(this, this);
updateFocusedInCluster(oldFocus, direction);
}
if (mAttachInfo != null) {
mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
}
//通知焦点观察者,焦点改变了
onFocusChanged(true, direction, previouslyFocusedRect);
// 刷新视图展示,比如在按键模式下,具有焦点的控件会高亮显示
refreshDrawableState();
}
接下来看下ViewGroup的requestChildFocus方法:
@Override
public void requestChildFocus(View child, View focused) {
// 再次检查当前ViewGroup焦点拦截策略
if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
return;
}
// Unfocus us, if necessary
super.unFocus(focused);
// 如果当前ViewGroup是之前的焦点链路上的一个节点,则mFocused节点去释放焦点(清空焦点标示)
if (mFocused != child) {
if (mFocused != null) {
mFocused.unFocus(focused);
}
mFocused = child;
}
if (mParent != null) {
//不断向上一集View回溯,直到更新了整个View树的焦点链路,注意这里的第一个参数不是之前的焦点控件了,而是当前ViewGroup了
mParent.requestChildFocus(this, focused);
}
}
看下View的unFocus方法:
void unFocus(View focused) {
clearFocusInternal(focused, false, false);
}
void clearFocusInternal(View focused, boolean propagate, boolean refocus) {
if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
//清空焦点标示
mPrivateFlags &= ~PFLAG_FOCUSED;
if (propagate && mParent != null) {
//向上级View回溯,清空焦点
mParent.clearChildFocus(this);
}
onFocusChanged(false, 0, null);
refreshDrawableState();
if (propagate && (!refocus || !rootViewRequestFocus())) {
notifyGlobalFocusCleared(this);
}
}
}