和 invalidateO的调用有点相似,requestFocusO也是不能独自完成的,当一个视图想要获取焦点时,
必须请求它的父视图完成该操作,为什么呢?因为父视图知道当前哪个视图正在拥有焦点,如果要进行
焦点切换,则必须先告诉原先的视图放弃焦点,而这些操作所需要的信息是在父视图中保存的,所以
requestFocus()也必须由父视图完成。
该函数有如下三个不同的版本。
• requestFocusO:无参数,它被转换成 requestFocus(View.FOCUS__DOWN)。
• requestFocus(int direction):它被转换成 requestFocus(direction, null)。
• requestFocus(int direction, Rect preFocusRec):第一个参数代表往哪个方向上寻找下一个视图,第
二个参数为当前拥有焦点的视图所占的矩形区,这个区域是相对该视图的直接父视图。
从这三个函数的转换关系可以看出,应用程序中针对某个视图调用requestFocus (无参数 时,并
不一定就会把焦点赋给该视图。因为该函数内部实际上在DOWN方向上找下一个可以获得焦点的视图,
至于是哪个视图就不一定了,这取决于父视图的执行逻辑,这就是为什么该函数的返回值是一个boolean
类型的原因,其意义是该视图到底能不能获得焦点。
// @return Whether this view or one of its descendants actually took focus.
* 第一个参数代表往哪个方向上寻找下一个视图,第
* 二个参数为当前拥有焦点的视图所占的矩形区,这个区域是相对该视图的直接父视图。
*
*/
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
// need to be focusable
/**
* 1 判断该视图是不是FOCUSABLE的,如果不是,则直接返回false
*/
if ((mViewFlags & FOCUSABLE_MASK) != FOCUSABLE ||
(mViewFlags & VISIBILITY_MASK) != VISIBLE) {
return false;
}
**/
//need to be focusable in touch mode if in touch mode
/*
* 如果当前是Touch模式,但是视图的FOCUSABLE_IN—TOUCH_MODE却 为 false,即该视图
* 不能在 Touch模式下获得焦点,则直接返回false,代码中调用View类 的 isInTouchMode()判断是否是
* Touch模式
*/
if (isInTouchMode() &&
(FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
return false;
}
/**
* 调 用 hasAncestorThatBlockDescendantFocus()判断是否父视图阻止该子视图获得焦点,如果阻
* 止,则直接返回 false。用程序可以调用 ViewGroup 的 setDescendantFocusability(int focusability)方法设
* 置该ViewGroup是否阻止其子视图获得焦点,默认情况下都不阻止。
*/
// need to not have any parents blocking us
if (hasAncestorThatBlocksDescendantFocus()) {
return false;
}
/**
* 以上三步实际上执行的都是前期检查,下面将真正进行焦点获取的操作。该步调用
* handleFocusGainIntemal(dir, rect)进行具体的焦点获取操作,执行完该函数后,则该视图肯定会获取焦点,所以返回true。
*/
handleFocusGainInternal(direction, previouslyFocusedRect);
return true;
}
void handleFocusGainInternal(int direction, Rect previouslyFocusedRect) {
if (DBG) {
System.out.println(this + " requestFocus()");
}
/**
* 1)判断当前窗口是否已经获得焦点,如果已经获得,则直接返回。相当于说,应用程序连续调
* 用两次requestFocus(),第二次调用时就直接返回了。
* 2)
* 如果还没拥有焦点, 则给mPrivateFlags变量中添加FOCUSED标识,这意味着该视图已经真
* 正拥有焦点了。
* 3)调用父视图mParent的 requestChildFocus(this, this),第一个参数代表child视图,第二个参数
* 代 表 focused视 图 。比如,C 中 包 含 B ,B 中 包 含 A ,如 果 从 中 调 用 该 函 数 时 ,就会执行
* C.requestChildFocus(B, A)。该函数是requestFocusO函数的核心过程,其内部会进行递归调用,并最终
* 调用到 ViewRoot 中的 requestChildFocus()
* 4)执行完上一步操作后,该视图的父视图及父父视图都已经获知了本次焦点获取请求,因此,
* 接下来需要做一些收尾工作了。本步中回调onFocusChanged(),应用程序可以重载该函数以便进行其他
* 操 作 。这 里 大 家 顺 便 区 分 一 下 两 个 英 语 时 态 的 语 义 ,这 里 回 调 的 是 onFocusChanged(),而不是
* onFocusChange(),抽象一下就是xxxed(},而 不 是 xxx()。在 Framework的其他地方,有时回调的是
* onXXXed(),有时却是onXXX(),两者的区别在于前者是当执行完指定操作后才回调,而后者是在指定
* 操作执行前回调。程序员需要遵守这种函数命名规则,以便调用者能够更清晰地理解代码逻辑
* 5)调用refreshDrawableState(),因为focus状态改变后,视图的背景图有可能也需要改变。
*/
if ((mPrivateFlags & FOCUSED) == 0) {
mPrivateFlags |= FOCUSED;
if (mParent != null) {
mParent.requestChildFocus(this, this);
}
onFocusChanged(true, direction, previouslyFocusedRect);
refreshDrawableState();
}
}
- 下面再来具体分析上面第 3 ) 步中提到的requestChildFocus(View child, View focus)函数的内部执 行过程。一般来讲,父视图也是一个ViewGroup对象,直到最后一个父视图,才是 ViewRoot对象,因 此,首先来看ViewGroup中的该函数
/**
* {@inheritDoc}
*/
public void requestChildFocus(View child, View focused) {
if (DBG) {
System.out.println(this + " requestChildFocus()");
}
/**
* 判 断 mGroupFlags是否包含FOCUS_BLOCK_DESCENDANTS标识,如果有,则意味着阻止子
* 视图获得焦点。这一步其实是多余的,因为在前面第3 步中已经进行过同样的判断了,能执行到这里
* 肯定不会阻止。
*/
if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
return;
}
/**
* 调 用 super.unFocus()。这里把该ViewGroup当作一个普通的视图处理,因为ViewGroup本身也
* 是一个View,所 以 ViewGroup本身也可以获取焦点。此处调用super.unFocus()就是当该ViewGroup本
* 身拥有焦点时,就先让它释放焦点
*/
// Unfocus us, if necessary
super.unFocus();
/**
* mFocused变量代表了之前拥有焦点的子视图,如果该变量不为空,则需要先通知该视图释放焦
* 点,此处调用mFocused.unFocus(),然后给该变量赋上新值child。该 child是该ViewGroup的直接子视
* 图,而不是真正拥有焦点的视图。举个例子,D 中包含C,C 中包含B,B 中包含A,假 设 A 是最终会
* 获得焦点的视图,那么执行完毕后,
*/
// We had a previous notion of who had focus. Clear it.
if (mFocused != child) {
if (mFocused != null) {
mFocused.unFocus();
}
mFocused = child;
}
/**
* 4)如果该 ViewGroup也有父视图,则递归调用父视图的requestChildFocus(this, focused)。第一个
* 参数为该ViewGmup对象,每次递归调用时都不同;第二个参数是focused,该参数一直都指向最终应
* 该获得焦点的视图。
*/
if (mParent != null) {
mParent.requestChildFocus(this, focused);
}
}
5)以上步骤最终递归到ViewRoot中的requestChildFocus()函数,该函数的执行过程如下
public void requestChildFocus(View child, View focused) {
/**
* 1)调用checkThread()确保是在UI线程中执行该调用
*/
checkThread();
/**
* 如果目标焦点视图就是当前焦点视图,则什么都不用做,ViewRoot中用变量mFocusedView保
* 存真正拥有焦点的视图。这个判断是多余的,因 为 在 前 面 第 步 第 4) 中已经检查过目标视图是否已
* 经拥有焦点,所以,能执行到这里, 意味着mFocusedView不是目标焦点视图,于是调用scheduleTraversalO
* 发起一个View遍历请求
*/
if (mFocusedView != focused) {
mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mFocusedView, focused);
scheduleTraversals();
}
/**
* 最后,将新的焦点视图赋值给mFocusedView
*/
mFocusedView = mRealFocusedView = focused;
if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Request child focus: focus now "
+ mFocusedView);
}