我们在写安卓应用程序的时候经常要写这一句:
setContentView(R.layout.activity_main);
这一句代表的含义就是将这个Activity的布局设置成指定资源文件,那么我们所看的界面是如何构建的呢?这就要从源码中去分析.
DecorView
DecorView是Window的一个内部类,Window是抽象类,在整个源码中源码中Window只有一个实现类:PhoneWindow,这个类才是activity对应的实际的Window,这个类里面有一个DecorView的引用:
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
google工程师为这个变量添加了一段注释,很显然这段解释说明DecroView是window的顶层view视图,也就是Activity里面所有View的根结点,那么我们就从Activity的setContentView方法看看,setContentView这个方法是如何构建整个View的。
Activity里面setContentView的定义:
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initActionBar();
}
首先获取和Activity关联的Window,上面已经说了Window实例是PhoneWindow对象,然后通过PhoneWindow对象调用setContentView方法:
PhoneWindow.setContentView方法:
public void setContentView(int layoutResID) {
//mContentParent是我们调用setContentView(xxx)时,xxx这个布局文件对象的父view
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
首先判断mContentParent是否是null,第一次进去肯定是null,于是这时在installDecor这个方法里面开始构建DecorView 对象。installDecor方法较长,只挑重点讲:
PhoneWindow.installDecor方法:
if (mDecor == null) {
//仅仅是new一个mDecor对象,赋值给mDecor
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
........
首先在第3行,为mDecor 这个变量赋值,在generateDecor()方法中仅仅是new了一个对象,将引用赋给mDecor。在第11行,generateLayout这个方法会根据当前应用的主题构建一个布局,所以会在这个方法里面里面看到很多的判断,为的是判断某个属性是否加入这个布局,不论布局如何变化,总要有内容吧,因此这个主题布局里面总包含一个id为android.R.id.content的frameLayout,这个frameLayout就是我们setContentView时的布局的父容器。其对应的过程在代码中的体现:
//PhoneWindow.generateLayout方法:
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
//ID_ANDROID_CONTENT就对应android.R.id.content
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
显然,根据主题得到一个布局,然后将这个布局inflate成一个view,将这个view添加到decor中,是以子view的形式放进decor中的,也就是加到顶层视图中,然后通过findViewById,找到id为content的view,最后返回这个view,将引用赋值给上面说的mContentParent,再次强调这个mContentParent就是包含着我们在Activity里面写的setContentView(xxx),xxx布局的父容器.
根据上面源码的过程,可以总结出一个view的整体视图,如下

介绍完原理,举个例子,下面是主布局的内容
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rl"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.testdemo.MainActivity" >
<Button
android:id="@+id/btn_hello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
</LinearLayout>
现在我们有什么方法可以获取LinearLayout 的view对象呢?
有这么几种方法:
1、直接为LinearLayout 添加一个id,通过findViewById查找
2、上面说了activity_main.xml的父布局的id为android.R.id.content,而这个父布局又只有一个子view,因此先获取父布局的view,然后获取第一个子view即可。
3、从mContentParent入手,但是我们发现mContentParent是PhoneWindow私有数据,而且没有获取的方法,因此我们往上找发现可以找到mDecor的获取方法,有了mDecor就可以一路找下来,先看代码如何实现:
View r1 = findViewById(R.id.rl);
View r2 = ((ViewGroup) findViewById(android.R.id.content))
.getChildAt(0);
ViewGroup decorView = (ViewGroup) getWindow().getDecorView();
ViewGroup view = (ViewGroup) ((ViewGroup) decorView.getChildAt(0))
.getChildAt(2);
View r3 = view.getChildAt(0);
if (r1 != null) {
if (r1 == r2)
System.out.println("r1==r2");
if (r1 == r3)
System.out.println("r1==r3");
}
r1、r2、r3分别对应上面三种方法找到的LinearLayout 的view对象,一般来说r1==r2是没有问题的,但是r3的话就要看情况,因为在view视图中,mDecor往下找会发现主题布局文件拥有的子view个数是不确定的,因为有时候设置主题为NoTitle的,那么当然就没有这个view了,对应的android.R.id.content布局文件在父布局中的位置就会不确定,在笔者的环境下,如果设置Activity为有TitleBar时,android.R.id.content布局对象对应第3个子view,也就是上面代码的情况;如果设置为无TitleBar时,对应第2个子view。
只要理解好上面的图,那么结构体系就会非常清楚了。
本文探讨了在Android应用开发中,setContentView()方法如何构建布局。通过源码分析,揭示了DecorView作为Window的顶层View,以及Activity中setContentView()如何通过PhoneWindow和安装Decor来设置内容视图。同时,介绍了获取特定布局对象的多种方法,并讨论了不同主题设置对视图结构的影响。

443

被折叠的 条评论
为什么被折叠?



