在我们coding时,在Activity.onCreate()里面常用的教科书式的代码是:
进入Activity的源码,查看setContentView()和findViewById()这两个函数查看原型:
Window是个抽象类。这个Window的子类是什么呢?
接着是findViewById(),它是调用DecorView.findViewById()。 DecorView是PhoneWindow的内部类,它表示整个window的顶层view,默认情况下它有两个子view,一个就是显示灰色的标题(也就是titlebar),另一个就是程序的显示区域。关于DecorView更详细的资料,大家可以参考: http://www.cnblogs.com/beenupper/archive/2012/07/13/2589749.html
DecorView其实是FrameLayout的子类,跟进FrameLayout!!!FrameLayout里面没发现findViewById().....进入FrameLayout的父类ViewGroup还是没有发现,继续深入...直到View。Bingo!
首先是:
通过findViewById()我们知道了在Activity里面使用findViewById()其实是调用DecorView这个Window的顶层view的findViewById();
我们更知道了为什么有的时候findViewById()会返回null,为什么我们有的时候需要指定findViewById()的baseView,形如:
setContentView(R.layout.main);
然后我们就可通过:
View view=findViewById(R.id.helloworld);
获取某个控件,但是这一切是如何完成的,本文会去探讨一下。
进入Activity的源码,查看setContentView()和findViewById()这两个函数查看原型:
/***
* Set the activity content from a layout resource. The resource will be
* inflated, adding all top-level views to the activity.
*
* @param layoutResID Resource ID to be inflated.
*/
public void setContentView( int layoutResID) {
getWindow().setContentView(layoutResID);
}
/***
* Set the activity content to an explicit view. This view is placed
* directly into the activity's view hierarchy. It can itself be a complex
* view hierarhcy.
*
* @param view The desired content to display.
*/
public void setContentView(View view) {
getWindow().setContentView(view);
}
他们都是通过getWindow()获得一个Windows的实例化对象的相关方法。
Window是个抽象类。这个Window的子类是什么呢?
是PhoneWindow,至于为什么是它,这里暂不讨论。有兴趣的同学可以查看这篇博客:
http://www.cppblog.com/fwxjj/archive/2013/01/13/197231.html
下面我们进入PhoneWindow的源码看一看。PhoneWindow在com.android.internal.policy.impl包下面。 @Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
上面是setContentView在PhoneWindow里的实现,而findViewById()在Window里面实现:
/**
* Finds a view that was identified by the id attribute from the XML that
* was processed in {@link android.app.Activity#onCreate}. This will
* implicitly call {@link #getDecorView} for you, with all of the
* associated side-effects.
*
* @return The view if found or null otherwise.
*/
public View findViewById(int id) {
return getDecorView().findViewById(id);
}
下面我们挨个分析,首先是setContent(),其中关键的代码是这行:
mLayoutInflater.inflate(layoutResID, mContentParent);
它将layoutResID所指向的布局实例化成view然后挂在了mContentParent后面。挂接上去之后剩下的就是view的绘制工作了,这个按下不表。
接着是findViewById(),它是调用DecorView.findViewById()。 DecorView是PhoneWindow的内部类,它表示整个window的顶层view,默认情况下它有两个子view,一个就是显示灰色的标题(也就是titlebar),另一个就是程序的显示区域。关于DecorView更详细的资料,大家可以参考: http://www.cnblogs.com/beenupper/archive/2012/07/13/2589749.html
DecorView其实是FrameLayout的子类,跟进FrameLayout!!!FrameLayout里面没发现findViewById().....进入FrameLayout的父类ViewGroup还是没有发现,继续深入...直到View。Bingo!
/**
* Look for a child view with the given id. If this view has the given
* id, return this view.
*
* @param id The id to search for.
* @return The view that has the given id in the hierarchy or null
*/
public final View findViewById(int id) {
if (id < 0) {
return null;
}
return findViewTraversal(id);
}
它非常爽快的调用了findViewTraversal(),但是注意ViewGroup是Override了这个方法的,所以需要进入ViewGroup查看findViewTraversal()。
/**
* {@hide}
*/
@Override
protected View findViewTraversal(int id) {
if (id == mID) {
return this;
}
final View[] where = mChildren;
final int len = mChildrenCount;
for (int i = 0; i < len; i++) {
View v = where[i];
if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
v = v.findViewById(id);
if (v != null) {
return v;
}
}
}
return null;
}
这里还是有地方需要注意的:
首先是:
if (id == mID) {
return this ;
}
mID是本View的ID,也就是说findViewById()是可以find到baseview的。然后就是:
final View[] where = mChildren;
final int len = mChildrenCount;
for ( int i = 0 ; i < len; i ++ ) {
View v = where[i];
if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0 ) {
v = v.findViewById(id);
if (v != null) {
return v;
}
}
}
return null;
获取ViewGroup的所有子view,然后循环的查询子view中是否有view.getId()==id。这里其实还隐藏了一个递归过程,在<
Android的UI两大基石>中,我们知道View和ViewGroup是使用Composite的组合模式,ViewGroup是View的子类。所以v.findViewById()可能调用View或者ViewGroup的findViewById(),这里也贴上View.findViewById()的源码。
/**
* Look for a child view with the given id. If this view has the given
* id, return this view.
*
* @param id The id to search for.
* @return The view that has the given id in the hierarchy or null
*/
public final View findViewById(int id) {
if (id < 0) {
return null;
}
return findViewTraversal(id);
}
View.findViewById()只是检查id是不是等于View自己的id,这样就保证了这个深度优先遍历的收敛性。
通过findViewById()我们知道了在Activity里面使用findViewById()其实是调用DecorView这个Window的顶层view的findViewById();
我们更知道了为什么有的时候findViewById()会返回null,为什么我们有的时候需要指定findViewById()的baseView,形如:
baseView.findViewById(R.id.helloworld);
--------------------------------------------------------------------------------
如果文中有任何错误,欢迎指出。