ViewRoot.ensureTouchMode(boolean inTouchMode)
boolean ensureTouchMode(boolean inTouchMode) {
if (DBG) Log.d("touchmode", "ensureTouchMode(" + inTouchMode + "), current "
+ "touch mode is " + mAttachInfo.mInTouchMode);
//判断参数传入的inTouchMode和当前的Touch模式是否相同,如果相同就直接返回false,什么都不做。当前的值存放在View.mAttachInfo.mInTouchMode中。
if (mAttachInfo.mInTouchMode == inTouchMode) return false;
// tell the window manager
try {
//以下都是需要改变,首先报告 WindowManagerService 当前的Touch模式发生变化,因为 WindowManagerService 在进行客户窗口布局时,需要根据客户窗口的Touch模式进行不同的处理。
sWindowSession.setInTouchMode(inTouchMode);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
// handle the change
//调用ensureTouchModeLocally()函数进行报告
return ensureTouchModeLocally(inTouchMode);
}
ViewRoot.ensureTouchModeLocally(boolean inTouchMode)
private boolean ensureTouchModeLocally(boolean inTouchMode) {
if (DBG) Log.d("touchmode", "ensureTouchModeLocally(" + inTouchMode + "), current "
+ "touch mode is " + mAttachInfo.mInTouchMode);
if (mAttachInfo.mInTouchMode == inTouchMode) return false;
//首先给mInTouchMode重新赋值,因为当前状态发生改变。
mAttachInfo.mInTouchMode = inTouchMode;
//每个View中都包含一个mAttachInfo对象,该对象来源于ViewRoot中的mAttachInfo,该对象内部又有一个mTreeObserver变量,它是一个ViewTreeObserver对象,调用该对象的dispatchOnTouchModeChanged()。
mAttachInfo.mTreeObserver.dispatchOnTouchModeChanged(inTouchMode);
//如果函数是要进入Touch状态,则调用enterTouchMode(),否则调用leaveTouchMode()。
return (inTouchMode) ? enterTouchMode() : leaveTouchMode();
}
对于应用程序而言,可以调用View类的 getTreeObserver()函数获取该View所在窗口的ViewTreeObserver对象,同一个窗口中不同View对象的该方法返回的是同一个对象,即一个窗口中只有一个该对象。ViewTreeObserver内部有一个列表容器,我们可以调用该类的addOnGloabalFocusChangedListener()向该列表中添加一个接口元素。上面的dispatchOnTouchModeChanged()实际上是循环调用该列表容器中多个接口对象的onGloabalFocusChanged()方法,而这个方法是程序员可以自定义实现的,以便处理Focus改变的情况。
ViewTreeObserver类除了有Focus改变的接口,还提供了Layout、pre-draw 、 scroll等不同的接口。
View.AttachiInfo.mTreeObserver.dispatchOnTouchModeChanged(inTouchMode);
final void dispatchOnTouchModeChanged(boolean inTouchMode) {
// NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
// perform the dispatching. The iterator is a safe guard against listeners that
// could mutate the list by calling the various add/remove methods. This prevents
// the array from being modified while we iterate it.
final CopyOnWriteArrayList<OnTouchModeChangeListener> listeners =
mOnTouchModeChangeListeners;
//遍历容器中的Listner,分别调用。
if (listeners != null) {
for (OnTouchModeChangeListener listener : listeners) {
listener.onTouchModeChanged(inTouchMode);
}
}
}
ViewRoot.enterTouchMode()
private boolean enterTouchMode() {
if (mView != null) {
//判断是否拥有焦点(一整枝都拥有焦点),该方法是View类的,ViewGroup中重载了该函数。View.getFocusChild(),表示找到当前View(Group)中包含焦点的子视图,该函数返回当前View直接子视图。
if (mView.hasFocus()) {
// note: not relying on mFocusedView here because this could
// be when the window is first being added, and mFocused isn't
// set yet.
//找到真正拥有焦点的View或者ViewGroup,这里的mView就是根视图。
final View focused = mView.findFocus();
//如果存在焦点,并且该视图没有在Touch模式下获取焦点的能力,直接返回false,这种情况下比较少。应用程序可以调用setFocusableInTouchMode()改变该属性,在默认情况下,视图在Touch模式下是不能拥有焦点的。
if (focused != null && !focused.isFocusableInTouchMode()) {
//【1】
final ViewGroup ancestorToTakeFocus =
findAncestorToTakeFocusInTouchMode(focused);
//如果可以,则直接调用父类视图的.requestFocus(),并将结果直接返回。就是自己没能力问问老爸有没有这个Touch获取焦点的能力。
if (ancestorToTakeFocus != null) {
// there is an ancestor that wants focus after its descendants that
// is focusable in touch mode.. give it focus
return ancestorToTakeFocus.requestFocus();
} else {
//一般程序都会走到这步,说明可以正常进入Touch并需要清除其焦点(自己没有,但是有可能父视图有)。
// nothing appropriate to have focus in touch mode, clear it out
//该函数将从根上清除所有子视图中的焦点。
mView.unFocus();
//调用ViewTreeObserver的dispatchOnFocusChanged(),以便应用程序对此执行相关的操作,注意~这里是Focus的回调,上面是Touch的回调。
mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(focused, null);
//置空mFocusedView
mFocusedView = null;
//代表是做了改变了。
return true;
}
}
}
}
//表示没变,自己没有这个能力。
return false;
}
【1】大多数情况下,Touch模式下不能拥有焦点,因此需要清除焦点。不过,该步骤中首先判断该视图的父视图是否可以在Touch模式下拥有焦点,如果可以的话,则调用父视图的requestFocus(),并返回其结果,如果不可以才开始真正清除焦点。
ViewRoot.leaveTouchMode()
private boolean leaveTouchMode() {
if (mView != null) {
//如果该View体系中是有焦点的
if (mView.hasFocus()) {
// i learned the hard way to not trust mFocusedView :)
//找到真正拥有焦点的View。
mFocusedView = mView.findFocus();
if (!(mFocusedView instanceof ViewGroup)) {
//如果是View对象,就不用重新给该视图赋予焦点了,直接返回false。
// some view has focus, let it keep it
return false;
} else if (((ViewGroup)mFocusedView).getDescendantFocusability() !=
ViewGroup.FOCUS_AFTER_DESCENDANTS) {
//如果该视图是一个ViewGroup对象,并且该ViewGroup对象阻止其子视图获得焦点,那么也直接返回false。(ListView就是可以获得焦点的ViewGroup,并且ListView不会直接把焦点传递给其包含的item,而是使用特别的逻辑让item获得焦点。)
// some view group has focus, and doesn't prefer its children
// over itself for focus, so let them keep it.
return false;
}
}
//如果没有找到拥有焦点的视图,则调用ViewRoot.focusSearch()使该视图获得焦点。【2】
// find the best view to give focus to in this brave new non-touch-mode
// world
final View focused = focusSearch(null, View.FOCUS_DOWN);
if (focused != null) {
return focused.requestFocus(View.FOCUS_DOWN);
}
}
return false;
}
【2】参数null代表当前拥有焦点的视图,即当前没有视图拥有焦点。DOWN代表往下继续找,这就是为什么当用户从按键模式切换到Touch模式,然后再进入按键模式时,上一个有用焦点的视图不能再次获得焦点,而是第一个视图获得焦点的原因。对于ListView而言,上一个获得焦点的视图,当重新切换到按键模式后依然能够获得焦点,因为ListView使用了特别的逻辑为所包含的item赋予焦点。
View.unFocus()
void unFocus() {
if (DBG) {
System.out.println(this + " unFocus()");
}
if ((mPrivateFlags & FOCUSED) != 0) {
mPrivateFlags &= ~FOCUSED;
onFocusChanged(false, 0, null);
refreshDrawableState();
}
}