Android開發之setContentView的那些事
原文:
setContentView方法位於Window類,實現Window的子類PhoneWindow。
每一個Activity都有一個PhoneWindow
以下為setContentView在PhoneWindow上的實現:
public class PhoneWindow extends Window implements MenuBuilder.Callback {
//...
//...
//...
//過載方法1
@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();
}
}
//過載方法2
@Override
public void setContentView(View view) {
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
//過載方法3
@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();
}
}
//...
//...
//...
}
程式碼中可看出當mContentParent不為空時,清除mContentParent中的內容,若為空,則執行initallDecor方法。
以下為initallDecor方法:
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
//...
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
//...
}
}
}
}
通過generateLayout方法得到mDecor物件,再將mDecor作為形參執行generateLayout方法
以下為generateDecor方法程式碼:
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
以下為DecorView的結構圖
以下為generateLayout(mDecor)方法程式碼:
protected ViewGroup generateLayout(DecorView decor) {
//...
//省略一些設定Window樣式的程式碼,直接來看我們最關心的程式碼!
ViewGroup contentParent =(ViewGroup)findViewById(ID_ANDROID_CONTENT);
//...
return contentParent;
}
}
ID_ANDROID_CONTENT為R.id.content,就是下圖的那個FrameLayout
所以ContentView即為這個FramLayout
重回PhoneWindiow類中對setContentView的實現程式碼:
public class PhoneWindow extends Window implements MenuBuilder.Callback {
//...
//...
//...
//過載方法1
@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();
}
}
//過載方法2
@Override
public void setContentView(View view) {
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
//過載方法3
@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();
}
}
//...
//...
//...
}
總結
首先判斷mContentParent是否為空,為空則獲取mContentParent,獲取mContentParent 需要呼叫installDecor方法獲取DecorView物件,然後通過DecorView物件再獲取ContentParentView物件。若不為空,則清除掉mContentParent裡的所有View檢視。
接下來通過反射載入到我們傳入的佈局,接著下面會通過呼叫getCallBack得到一個CallBack物件cb,其實這個cb就是我們的Activity,接著會呼叫Activity的onContentChanged方法,這個方法是一個空實現,會在我們呼叫setContentView方法後呼叫。
注意:過載方法1是通過反射來傳入佈局的,而過載方法2和3是通過普通方法載入的,而這兩種方法的不同如下文所示
過載方法1使用載入佈局的方法是通過R.layout…反射得來的View,所以沒次呼叫setContentView方法拿到的View都是不同的。
而過載方法1和2都是通過直接傳View物件過來繫結佈局的,所以多次呼叫setContentView拿到的都是同一個View。