平常做手机开发的时候不太需要关心焦点获取,但是如果做Android Tv或盒子开发的时候就要做好View之间焦点切换了。
View焦点流程图
源码分析
view通过以下几个方法来获取焦点
- View.java -> requestFocus(int direction, Rect previouslyFocusedRect)
public final boolean requestFocus() {
return requestFocus(View.FOCUS_DOWN);
}
public final boolean requestFocus(int direction) {
return requestFocus(direction, null);
}
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
return requestFocusNoSearch(direction, previouslyFocusedRect);
}
从代码中可以看到最终都会调到requestFocusNoSearch方法
- view.java -> requestFocusNoSearch(int direction, Rect previouslyFocusedRect)
private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
// 先判断当前视图是否具有获取焦点的能力,如果没有获取焦点的能力或没有显示则直接返回false
if ((mViewFlags & FOCUSABLE) != FOCUSABLE
|| (mViewFlags & VISIBILITY_MASK) != VISIBLE) {
return false;
}
// 判断是否在Touch模式下,如果是在touch模式下,就必须要有Touch模式下获取焦点的能力(FOCUSABLE_IN_TOUCH_MODE)
if (isInTouchMode() &&
(FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
return false;
}
// 判断父容器是否拦截当前视图获取焦点(注意:是一层一层遍历所有的父容器不是只判断当前视图的直接父容器),如果某一个父容器阻止则直接返回false,可以通过设置setDecendantFocusability(int focusability)方法来设置ViewGroup是否阻止子视图获取焦点,默认是不阻止的
if (hasAncestorThatBlocksDescendantFocus()) {
return false;
}
//真正获取焦点的函数,执行完后返回true
handleFocusGainInternal(direction, previouslyFocusedRect);
return true;
}
- View.java -> handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect)
void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {
if (DBG) {
System.out.println(this + " requestFocus()");
}
//先判断是否已获取焦点,防止多次调用
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) {
mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
}
//执行回调方法,并且触发invalidate进行刷新
onFocusChanged(true, direction, previouslyFocusedRect);
//更新绘制状态
refreshDrawableState();
}
}
- ViewGroup.java ->requestChildFocus(View child, View focused)
public void requestChildFocus(View child, View focused) {
if (DBG) {
System.out.println(this + " requestChildFocus()");
}
//先判断DescendantFocusability是否为FOCUS_BLOCK_DESCENDANTS, 也就是是否阻止子视图获取焦点,如果阻止则直接跳出
if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
return;
}
// 释放当前视图的焦点
super.unFocus();
// mFocused是之前有焦点的子视图,如果之前获取焦点的视图和当前要获取焦点视图不是同一个的话就更新
if (mFocused != child) {
if (mFocused != null) {
//释放焦点
mFocused.unFocus(focused);
}
//重新赋值
mFocused = child;
}
//逐层上调requestChildFocus方法,直到View树的root节点
if (mParent != null) {
mParent.requestChildFocus(this, focused);
}
}
ViewGroup获取焦点处理
ViewGroup获取焦点比View获取焦点要稍微复杂点多了分发机制
- ViewGroup.java -> requestFocus(int direction, Rect previouslyFocusedRect)
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
if (DBG) {
System.out.println(this + " ViewGroup.requestFocus direction="
+ direction);
}
//获取当前焦点分发机制
int descendantFocusability = getDescendantFocusability();
switch (descendantFocusability) {
case FOCUS_BLOCK_DESCENDANTS: //阻止子View获取焦点
return super.requestFocus(direction, previouslyFocusedRect);
case FOCUS_BEFORE_DESCENDANTS: { //先交给自身获取焦点,如果自身不处理则交给子视图处理(默认)
final boolean took = super.requestFocus(direction, previouslyFocusedRect);
return took ? took : onRequestFocusInDescendants(direction, previouslyFocusedRect);
}
case FOCUS_AFTER_DESCENDANTS: { //先交给子视图处理,如果子不处理再交给自身处理
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);
}
}
onRequestFocusInDescendants方法是调用子视图获取焦点的方法,默认是按照子视图顺序处理,direction如果向下或向右则从第一个开始,如果向上或向左则从最后一个开始遍历,直到某个子视图获取焦点。