focus只有两个点:
1.刚启动activity时,焦点默认在哪里,为什么在那里?
2.上下按键,焦点如何查找的,它怎么知道下一个获取焦点的是谁?
其中2只是针对键盘即key事件,touch事件则直接是皇统继承,点谁是谁。
一、启动activity时,焦点默认行为:
首先是wms进行setview,将decorView添加进来,此时viewRootImpl中保存了decorView,viewroot中的mView保存这个decorView。
1.第一步performTraversals
看ViewRootImp中的函数 private void performTraversals()
private void performTraversals() {
//跳过n多代码,只关注焦点处理的部分
if (mFirst) {
// handle first focus request
if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: mView.hasFocus()="
+ mView.hasFocus());
if (mView != null) {
if (!mView.hasFocus()) {
mView.requestFocus(View.FOCUS_FORWARD);
if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: requested focused view="
+ mView.findFocus());
} else {
if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: existing focused view="
+ mView.findFocus());
}
}
}
一般来讲decorView不会是hasFocus的(也有是的,这部分是逻辑还没找到,哪里给decorView设置的焦点),需要走到
mView.requestFocus(View.FOCUS_FORWARD)
2.requesFocus
A. 如何找到获取焦点的view:
在viewroot中可以看到哦啊,调用了requestfocus。mview是啥,是decorview。走到viewgroup里。这里开始递归,规规矩矩的挨个查找。先看图在看code就明白了。
直接描述查找的逻辑:
做个简单的比喻,在女儿国,媒婆来提亲,直接找的家里的长老,现在长老说:好,我看看哪个孩子出嫁。
DecorView(mview)就是长老,child就是他的女儿,第一代孙、第二代孙...。
长老家有规矩,按家族辈份来,先看老大家,没有再看老二家...;老大家也是这样,先看他大女儿家是否有待出嫁的,没有在看二女儿家...,依次类推。
当然,家族庞大,已经有了n待悬孙。长老不会自己去问,而是告诉大女儿,大女儿家没有找到待嫁的孩子,告诉长老没有,长老才会去问二女儿...,这个询问的过程也是依次类推。
最后,找到n代孙女:meimei.han待家,通过meimei.han告诉她妈,她妈在告诉她奶奶...,一辈一辈的把消息通知到长老。
这个传统封建式的婚嫁,就是view焦点查找的过程。
下面根据图片,再结合代码来具体讲一下。
1)mView是decorView,他有1个child(linearlayout:尾号cb8),第一步就是从decorView查到这个大女儿linearlayout;长老告诉大女儿cb8,去看看你孩子们,是否有待出嫁的。
2)长子cb8家有3孩子:老大798,老二9f8,老三078.
2.1) cb8把他的大女儿798叫过来,问他:家里是否有带出嫁的闺女。
798说:我光棍,没有。
cb8说:滚。
2.2)然后把老二9f8叫过来,问他:家里是否有带出嫁的闺女。
2.1.1)98f有一女360,于是回家问女儿:是否愿出嫁。
360说:未成年,不出嫁。
98f速度返回他爹cb8处:我家没有可以出嫁的。
2.3)然后把老三078叫过来,问他:家里是否有带出嫁的闺女。
2.3.1)老三078家有一女6d4,...
这个过程类似了就,不再多说。虽然不是很恰当,但是这个比喻能比较浅显的解释清楚具体的查找过程。再结合代码,就基本可以搞明白了。
代码解析:
知道view图谱是decorview--viewgroup--...view。
这里也是一样,在viewgroup。这里只关注:
FOCUS_BEFORE_DESCENDANTS,因为block的没啥看的,更简单。
@Override
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
if (true) {
System.out.println(this + " ViewGroup.requestFocus direction="
+ direction);
}
int descendantFocusability = getDescendantFocusability();
switch (descendantFocusability) {
case FOCUS_BLOCK_DESCENDANTS:
return super.requestFocus(direction, previouslyFocusedRect);
case FOCUS_BEFORE_DESCENDANTS: {
final boolean took = super.requestFocus(direction, previouslyFocusedRect);//调用view中的函数,如果不是根view,继续走viewgroup,否则走view中xxx函数,下面会讲到。
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);
}
}
这个函数就是上面说的一代一代问的过程了:
@SuppressWarnings({"ConstantConditions"})
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;
for (int i = index; i != end; i += increment) {
View child = children[i];//同一辈份的,老大优先
Log.d("lstlog","onRequestFocusInDescendants i: "+i+", child: "+child);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
if (child.requestFocus(direction, previouslyFocusedRect)) {//看看孩子是否可以获取焦点,并设置
return true;
}
}
}
return false;
}
经过这个遍历,最终会找到并设置焦点给特定的那个view:可能是未出嫁的女孩也可能是她妈。。。孩子是亲生的(在xml里面写出来的)就不会发生这种情况,不是亲生的(xml里面么有的,通过code add进去的)就可能这样。
上面图中的最终获取焦点的是就listview 6a8,而不是里面那些子布局了。
B.穿嫁衣
在找到具体的女孩或者女孩她妈以后,就要准备出嫁了,要认识出嫁的女孩的妈,奶奶...一直到长老。他们这里就不需要认识她的啊姨们了。
这里就是一个反向查找了,并要记录下来。(HierarchyState中需要记录)
在A的过程中,之所以没记录,需要在B的过成中再查找一遍,是因为第一次查找,不确定这个家族就能有,是哪个分支,也可能没有,所以没办法记录,只能反向查找并设置一下HierarchyState的状态,这样可以在Hierarchy工具中看到。
code:
view中的函数,上面也说了,到拿到焦点后,就会调用view中的函数了。
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
return requestFocusNoSearch(direction, previouslyFocusedRect);
}
private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
// need to be focusable
if ((mViewFlags & FOCUSABLE_MASK) != 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
if (hasAncestorThatBlocksDescendantFocus()) {
return false;
}
handleFocusGainInternal(direction, previouslyFocusedRect);
return true;
}
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) {
mParent.requestChildFocus(this, this);
}
if (mAttachInfo != null) {
mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
}
onFocusChanged(true, direction, previouslyFocusedRect);
refreshDrawableState();
}
}
viewgroup中
public void requestChildFocus(View child, View focused) {
if (DBG) {
System.out.println(this + " requestChildFocus()");
}
if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
return;
}
// Unfocus us, if necessary
super.unFocus();
Log.d("lstlog","this: "+this+", mFocused: "+mFocused+", focused: "+focused);
// We had a previous notion of who had focus. Clear it.
if (mFocused != child) {
if (mFocused != null) {
mFocused.unFocus();
}
mFocused = child;
}
if (mParent != null) {
mParent.requestChildFocus(this, focused);
}
}
关注函数
requestChildFocus:
第一个参数是父视图,第二个是focused视图,该函数内部进行递归调用,从获取焦点的view逐层往外一直到辈份最大的。这里看都是this,为啥说第一个参数是父呢?看viewgroup中的函数就明白了。
4.requestChildFocus(ViewGroup)
public void requestChildFocus(View child, View focused) {
if (DBG) {
System.out.println(this + " requestChildFocus()");
}
if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
return;
}
// Unfocus us, if necessary
super.unFocus();
// We had a previous notion of who had focus. Clear it.
if (mFocused != child) {
if (mFocused != null) {
mFocused.unFocus();
}
mFocused = child;
}
if (mParent != null) {
mParent.requestChildFocus(this, focused);
}
}
先清除自己的焦点(
super.unFocus()), 如果原来内部有焦点, 先清除其焦点(
mFocused.unFocus()), 保存获取焦点的view(
mFocused = child),然后调用上一层的requestChildFocus. 最后的调用可知,这个方法会一直调用到View的树的root节点。
使用
Hierarchy Viewer,
可以看到最小的view如果有焦点,则其父布局的hasfocus也是true。就上上面这段逻辑了。
其中一句log:(从view中传递的是this,this两个参数,但是调用的时候是parent,所以到viewgroup中的this就是parent了,focused就是view的this)
至此,启动一个新的activity的焦点派发过程分析到此,基本框架和思想就是这样了。
下一篇文章分析一下:方向按键,如何查找焦点的。
touch就不要关注这些了,点击比较简单。