在Activity中加載佈局的時候,我們都知道調用的是setContentView方法,那麼具體是如何實現的呢?
本文基於參考其他人博客以及自己翻閱源碼做一個記錄。
隨便找到一個Activity,點擊setContentView
方法可以看到實現:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);//1
initWindowDecorActionBar();//2
}
layoutResID
是我們傳入的佈局id,所以我們先主要關注1處。可以看到這裏調用了getWindow().setContentView
,點進去getWindow
看一下:
public Window getWindow() {
return mWindow;
}
獲取的是一個Window
對象,找到Window
類,它是一個抽象類,在類註釋中我們可以看到下面一段:
The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
可以看到,Window
唯一的實現類就是PhoneWindow
。再點進去PhoneWindow
的setContentView
方法:
public class PhoneWindow extends Window implements MenuBuilder.Callback {
//...
//...
//...
@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();
}
}
@Override
public void setContentView(View view) {
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mContentParent.addView(view, params);
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
//...
//...
//...
}
這裏我們關注setContentView(int layoutResID)實現。
當mContentParent
不爲空時,會先移除其上所以子view,然後通過mLayoutInflater.inflate(layoutResID, mContentParent)
將我們的佈局文件加載上去,當mContentParent
爲空時,會先調用installDecor()
方法,click it:
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
//...
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
//...
}
}
}
}
只看主流程代碼,邏輯很清晰,mContentParent
爲空的時候,會執行generateLayout()
方法,同時需要傳入一個mDecor
,那麼mDecor
是什麼東西呢,我們可以點進generateDecor
方法看一下:
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
嘿,又出來了一個DecorView
,這是個什麼東西呢?查看它的源碼:
private final class DecorView extends FrameLayout {}
可以看到,DecorView
繼承FrameLayout
,是一個ViewGroup
。回到前面的generateLayout(mDecor)
,看一下我們往DecorView
裏都有什麼東西:
public class PhoneWindow extends Window implements MenuBuilder.Callback {
protected ViewGroup generateLayout(DecorView decor) {
// 此處省去一堆代碼,設置窗口屬性
int layoutResource;
// 此處省去一堆代碼,根據不同的主題使用不同的佈局資源
// 這裏纔是重點,向 DecorView 添加布局,並且從 DecorView 中查找出 contentParent
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
// 此處省去一堆代碼設置窗口背景和標題
return contentParent;
}
}
在這個方法裏,我們contentParent
其實指向的就是ID_ANDROID_CONTENT
,也就是R.id.content
。到目前爲止,我們可以用一張圖來概括上邊兒的流程:
找到mContentParent
之後,回到setContentView
方法中:
// 解析我們設置的佈局資源並且設置 mContentParent 爲父佈局
mLayoutInflater.inflate(layoutResID, mContentParent);
至此,我們自己的佈局就加載在了Activity上,