上次的兩篇文章,我們討論了創(chuàng)建自定義View的基本流程.對(duì)View
有了基本的了解后,有好奇心的同學(xué)可能會(huì)對(duì)View
的基本原理充滿好奇(其實(shí)我也非常好奇View
在Android系統(tǒng)下是怎樣實(shí)現(xiàn)的),所以我就本著好奇心看了很多關(guān)于View
的實(shí)現(xiàn)原理和View
的基本工作流程的文章,也看了一些源碼,對(duì)View
有了更加深入的理解.在這我就跟大家分享一下我對(duì)View
的理解,希望能對(duì)大家的學(xué)習(xí)有所幫助.若有什么錯(cuò)誤的地方還希望大家?guī)臀抑赋?
從setContentView
開始
寫過Android應(yīng)用的都知道,我們最開始接觸View
是在布局文件activity_main.xml
中.在我們的MainActivity
中onCreate()
方法中,通過setContentView(R.layout.activity_main)
設(shè)置我們的Activity布局.View的所有工作都是從這里開始的,為了一探究竟,我們就進(jìn)到setContentView()
中去,看看它究竟是怎樣實(shí)現(xiàn)的.下面就是Activity的setContentView
的代碼.
/**
* 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.
*
* @see #setContentView(android.view.View)
* @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
*/
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
注意:我們這里的代碼是
Activity
的代碼,而不是AppCompatActivity
的代碼.AppCompatActivity
是Android為使用兼容庫的應(yīng)用所提供的一個(gè)Activity基類,這里為了研究方便,減少其他無關(guān)代碼的干擾,我們使用Activity
類進(jìn)行討論.
我們可以看到這里最終調(diào)用的是getWindow().setContentView(layoutID)
進(jìn)行布局設(shè)置.
// Window 類下的setContentView()
public abstract void setContentView(View view);
可以看到這是一個(gè)抽象方法,需要由子類來實(shí)現(xiàn).這就需要我們找到Window
的實(shí)現(xiàn)類了.我們再來看getWindow()
.
/**
* Retrieve the current {@link android.view.Window} for the activity.
* This can be used to directly access parts of the Window API that
* are not available through Activity/Screen.
*
* @return Window The current window, or null if the activity is not
* visual.
*/
public Window getWindow() {
return mWindow;
}
返回的是Activity的mWindow
成員對(duì)象.但我們還是沒找到Window
的實(shí)現(xiàn)類啊,先別急,我們在Activity
類里搜索mWindow
關(guān)鍵字,看看它是在哪里被實(shí)例化的.經(jīng)過搜索后,發(fā)現(xiàn)了在mWindow
是在Activity
類下的attach()
函數(shù)里被實(shí)例化的.下面是相關(guān)的代碼:
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
.......
// mWindow在這里被實(shí)例化
mWindow = new PhoneWindow(this);
........
}
由于attach()
的代碼比較長,我只貼出了我們關(guān)心的代碼,就是上面那一句.這里很容易就看出了mWindow
就是一個(gè)PhoneWindow
的一個(gè)實(shí)例.這樣我們就可以查看mWindow
中的setContentView()
的實(shí)現(xiàn)了.
一切都在PhoneWindow
PhoneWindow
并不是Android SDK內(nèi)的類,我們在Android Studio中無法看到其代碼.遇到這種情況肯定要到Internet上搜索一下,果然我們可以到一些在線的Android源碼網(wǎng)站上查看它的源代碼,我們可以找到PhoneWindow的源碼地址.然后經(jīng)過網(wǎng)頁內(nèi)的搜索找到了setContentView(int)
的代碼.
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();// 標(biāo)注1
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);// 標(biāo)注2
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
我在代碼中標(biāo)注了兩個(gè)地方,我們先來看標(biāo)注2
.這里可以清除的看到Android將我們傳進(jìn)來的xml布局文件進(jìn)行了inflate
并將它添加到mContentParent
這個(gè)容器中.我們找到mContentParent
的聲明,發(fā)現(xiàn)它是一個(gè)ViewGroup
.
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
private ViewGroup mContentParent;
這里的注釋說了,這個(gè)mContentParent
是存放窗口內(nèi)容的一個(gè)View
,它要不就是mDecor
自己,要不就是一個(gè)存放窗口內(nèi)容的mDecor
的child
.而在這行代碼的上面正好是mDecor
的聲明,它是一個(gè)DecorView
對(duì)象,是window的頂層view,里面有window decor
.
看到這里是不是有點(diǎn)暈?不要緊,先接著看,我馬上讓你的頭腦清晰起來.我們先轉(zhuǎn)到DecorView
這個(gè)PhoneWindow
的內(nèi)部類的聲明:
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
......
}
這里可以看到DecorView
繼承了FrameLayout
,而我們知道FrameLayout
繼承了ViewGroup
.所以說這里的DecorView
也是一個(gè)ViewGroup
對(duì)象,看到這里,上面關(guān)于mContentParent
和mDecor
的注釋也就清楚是什么意思了.mContentParent
就是mDecor
,或者就是mDecor
里的一個(gè)子View,而且里面的內(nèi)容就是頂層窗口的內(nèi)容,就是我們activity_main.xml
布局的父布局(可以說是Activity的根布局).
知道了mCOntentParent
和mDecor
是什么之后,我就在想:既然mContentParent
與mDecor
有關(guān)系,那究竟它倆是怎樣聯(lián)系起來的呢? 抱著這個(gè)問題先回到setContentView()
的代碼中,為了方便,我在這重新將代碼再貼一遍.
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();// 標(biāo)注1
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);// 標(biāo)注2
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
我們來看標(biāo)注1
的地方,這里是當(dāng)mContentParent
為空的時(shí)候才會(huì)執(zhí)行,我猜這個(gè)installDecor()
函數(shù)是為了對(duì)mDecor
進(jìn)行初始化設(shè)置并對(duì)mContentParent
進(jìn)行賦值的,這里就是mContentParent
和mDecor
產(chǎn)生聯(lián)系的地方.為了看一下我的猜測是否正確,我們找到installDecor()
的代碼.
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor(); //標(biāo)注1
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
}
if (mContentParent == null) { //標(biāo)注2
mContentParent = generateLayout(mDecor);
mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
if (mTitleView != null) {
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
View titleContainer = findViewById(com.android.internal.R.id.title_container);
if (titleContainer != null) {
titleContainer.setVisibility(View.GONE);
} else {
mTitleView.setVisibility(View.GONE);
}
if (mContentParent instanceof FrameLayout) {
((FrameLayout)mContentParent).setForeground(null);
}
} else {
mTitleView.setText(mTitle);
}
} else {
mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
if (mActionBar != null) {
if (mActionBar.getTitle() == null) {
mActionBar.setWindowTitle(mTitle);
}
final int localFeatures = getLocalFeatures();
if ((localFeatures & (1 << FEATURE_PROGRESS)) != 0) {
mActionBar.initProgress();
}
if ((localFeatures & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
mActionBar.initIndeterminateProgress();
}
// Post the panel invalidate for later; avoid application onCreateOptionsMenu
// being called in the middle of onCreate or similar.
mDecor.post(new Runnable() {
public void run() {
if (!isDestroyed()) {
invalidatePanelMenu(FEATURE_ACTION_BAR);
}
}
});
}
}
}
}
這里的代碼比較長,但這并不影響我們的閱讀,因?yàn)樵诤瘮?shù)的前面幾行,我看到了關(guān)鍵的部分,這些關(guān)鍵的部分我都在上面的代碼標(biāo)注了起來.我們先來看標(biāo)注1
.
if (mDecor == null) {
mDecor = generateDecor(); //標(biāo)注1
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
}
這里對(duì)mDecor
使用了generateDecor()
進(jìn)行賦值,可以從函數(shù)名看出這是一個(gè)創(chuàng)建DecorView
對(duì)象的函數(shù),我們來看一下是否如此.
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
果然如此,這里直接new
了一個(gè)DecorView
對(duì)象并返回.但是參數(shù)列表里的-1
是什么呢?我們來到DecorView
的構(gòu)造函數(shù).
/** The feature ID of the panel, or -1 if this is the application's DecorView */
private final int mFeatureId;
public DecorView(Context context, int featureId) {
super(context);
mFeatureId = featureId;
}
可以看到,第二個(gè)構(gòu)造參數(shù)是一個(gè)featureID
,而且將此賦值到了mFeatureId
.這個(gè)mFeatureId
是DecorView的feature ID
,-1表示這個(gè)DecorView是application
的DecorView
.這里的mFeatureId
應(yīng)該是用來標(biāo)注DecorView
的類型的,或者是用來設(shè)置窗口的具體樣式的.我也搞不太清楚,在Internet上搜了很久也沒找到答案,如果有同學(xué)知道答案希望能分享一下.雖然在這里遇到了一點(diǎn)問題,但這并不會(huì)對(duì)我們的后續(xù)分析有太大的影響.
我們清楚了generateDecor()
是用于初始化mDecor
對(duì)象,我們接著分析標(biāo)注1
后面的代碼,這里
- mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
- mDecor.setIsRootNamespace(true);
兩句代碼可以看得出是對(duì)mDecor
進(jìn)行的一些初始化的操作.然后我們來看標(biāo)注2
的代碼.
if (mContentParent == null) { //標(biāo)注2
mContentParent = generateLayout(mDecor);
mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
if (mTitleView != null) {
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
View titleContainer = findViewById(com.android.internal.R.id.title_container);
if (titleContainer != null) {
titleContainer.setVisibility(View.GONE);
} else {
mTitleView.setVisibility(View.GONE);
}
if (mContentParent instanceof FrameLayout) {
((FrameLayout)mContentParent).setForeground(null);
}
} else {
mTitleView.setText(mTitle);
}
}
這里同樣的也調(diào)用了generateLayout(mDecor)
函數(shù)為mContentParent
賦值,注意這里將初始化好的mDecor
作為參數(shù)傳入.按照慣例,轉(zhuǎn)到generateLayout()
的代碼.由于代碼有點(diǎn)長,為了方便分析,我就分開幾部分別貼出來.首先我們先看第一部分的代碼:
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
// 獲取window的style屬性
TypedArray a = getWindowStyle();
if (false) {
System.out.println("From style:");
String s = "Attrs:";
for (int i = 0; i < com.android.internal.R.styleable.Window.length; i++) {
s = s + " " + Integer.toHexString(com.android.internal.R.styleable.Window[i]) + "="
+ a.getString(i);
}
System.out.println(s);
}
mIsFloating = a.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false);
int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
& (~getForcedWindowFlags());
if (mIsFloating) {
setLayout(WRAP_CONTENT, WRAP_CONTENT);
setFlags(0, flagsToUpdate);
} else {
setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
}
if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionBarOverlay, false)) {
requestFeature(FEATURE_ACTION_BAR_OVERLAY);
}
if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionModeOverlay, false)) {
requestFeature(FEATURE_ACTION_MODE_OVERLAY);
}
if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) {
setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN&(~getForcedWindowFlags()));
}
if (a.getBoolean(com.android.internal.R.styleable.Window_windowShowWallpaper, false)) {
setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags()));
}
if (a.getBoolean(com.android.internal.R.styleable.Window_windowEnableSplitTouch,
getContext().getApplicationInfo().targetSdkVersion
>= android.os.Build.VERSION_CODES.HONEYCOMB)) {
setFlags(FLAG_SPLIT_TOUCH, FLAG_SPLIT_TOUCH&(~getForcedWindowFlags()));
}
a.getValue(com.android.internal.R.styleable.Window_windowMinWidthMajor, mMinWidthMajor);
a.getValue(com.android.internal.R.styleable.Window_windowMinWidthMinor, mMinWidthMinor);
if (getContext().getApplicationInfo().targetSdkVersion
< android.os.Build.VERSION_CODES.HONEYCOMB) {
addFlags(WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY);
}
if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion
>= android.os.Build.VERSION_CODES.HONEYCOMB) {
if (a.getBoolean(
com.android.internal.R.styleable.Window_windowCloseOnTouchOutside,
false)) {
setCloseOnTouchOutsideIfNotSet(true);
}
}
//........
}
第一部分的代碼是通過getWindowStyle()
得到Window的主題樣式屬性并進(jìn)行相應(yīng)設(shè)置,就是處理我們在AndroidManifest.xml
或在style.xml
對(duì)theme進(jìn)行的設(shè)置.我上次在View的構(gòu)造函數(shù)分析時(shí)講到了樣式屬性的獲取,這里的也是一樣的方法.我們可以找到Window
的getWindowStyle()
函數(shù)的代碼看看.
/**
* Return the {@link android.R.styleable#Window} attributes from this
* window's theme.
*/
public final TypedArray getWindowStyle() {
synchronized (this) {
if (mWindowStyle == null) {
mWindowStyle = mContext.obtainStyledAttributes(
com.android.internal.R.styleable.Window);
}
return mWindowStyle;
}
}
果然就是獲取了com.android.internal.R.styleable.Window
的資源屬性.相關(guān)的內(nèi)容可以參考Github上的Android文件platform_frameworks_base/core/res/res/values/attrs.xml
.
接下來我們來看第二部分的代碼:
....
WindowManager.LayoutParams params = getAttributes();
if (!hasSoftInputMode()) {
params.softInputMode = a.getInt(
com.android.internal.R.styleable.Window_windowSoftInputMode,
params.softInputMode);
}
if (a.getBoolean(com.android.internal.R.styleable.Window_backgroundDimEnabled,
mIsFloating)) {
/* All dialogs should have the window dimmed */
if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {
params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
}
params.dimAmount = a.getFloat(
android.R.styleable.Window_backgroundDimAmount, 0.5f);
}
if (params.windowAnimations == 0) {
params.windowAnimations = a.getResourceId(
com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
}
// The rest are only done if this window is not embedded; otherwise,
// the values are inherited from our container.
if (getContainer() == null) {
if (mBackgroundDrawable == null) {
if (mBackgroundResource == 0) {
mBackgroundResource = a.getResourceId(
com.android.internal.R.styleable.Window_windowBackground, 0);
}
if (mFrameResource == 0) {
mFrameResource = a.getResourceId(com.android.internal.R.styleable.Window_windowFrame, 0);
}
if (false) {
System.out.println("Background: "
+ Integer.toHexString(mBackgroundResource) + " Frame: "
+ Integer.toHexString(mFrameResource));
}
}
mTextColor = a.getColor(com.android.internal.R.styleable.Window_textColor, 0xFF000000);
}
....
第二部分的代碼就是設(shè)置WindowManager.LayoutParams
窗口布局參數(shù),然后對(duì)窗口的mBackgroundResource
和mFrameResource
進(jìn)行賦值.這部分的代碼也是對(duì)window進(jìn)行樣式的設(shè)置.我們接著看第三部分的代碼.
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
com.android.internal.R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = com.android.internal.R.layout.screen_title_icons;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
// System.out.println("Title Icons!");
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
// Special case for a window with only a progress bar (and title).
// XXX Need to have a no-title version of embedded windows.
layoutResource = com.android.internal.R.layout.screen_progress;
// System.out.println("Progress!");
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
// Special case for a window with a custom title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
com.android.internal.R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = com.android.internal.R.layout.screen_custom_title;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
// If no other features and not embedded, only need a title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
com.android.internal.R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
if ((features & (1 << FEATURE_ACTION_BAR_OVERLAY)) != 0) {
layoutResource = com.android.internal.R.layout.screen_action_bar_overlay;
} else {
layoutResource = com.android.internal.R.layout.screen_action_bar;
}
} else {
layoutResource = com.android.internal.R.layout.screen_title;
}
// System.out.println("Title!");
} else {
// Embedded, so no decoration is needed.
layoutResource = com.android.internal.R.layout.screen_simple;
// System.out.println("Simple!");
}
這部分的代碼定義了layoutResource
和feature
兩個(gè)局部變量,這兩個(gè)變量在后面會(huì)有特別的作用.第3部分的代碼就是根據(jù)feature
的值來對(duì)layoutResource
進(jìn)行賦值.layoutResource
的值可能會(huì)是下列值的其中一個(gè):
- com.android.internal.R.layout.screen_title_icons
- com.android.internal.R.layout.screen_progress
- com.android.internal.R.layout.screen_custom_title
- com.android.internal.R.layout.screen_action_bar_overlay
- com.android.internal.R.layout.screen_action_bar
- com.android.internal.R.layout.screen_title
- com.android.internal.R.layout.screen_simple
- 或是一個(gè)dialog的布局
這些資源id所對(duì)應(yīng)的布局文件同樣可以在Android的Github網(wǎng)站上找到.可以從這些布局文件的名字看出這些都是window screen的布局.看過這些文件的同學(xué)都會(huì)發(fā)現(xiàn)它們都有一個(gè)共同點(diǎn),那就是它們大多都是一個(gè)LinearLayout
的父布局下面有一個(gè)ID為action_mode_bar_stub
的ViewStub
和一個(gè)ID為content
的FrameLayout
,這一點(diǎn)我們后面會(huì)提到.
經(jīng)過分析我們知道了layoutResource
代表的是一個(gè)布局,至于這個(gè)布局是誰的布局,我們看了第4部分的代碼就清楚了.
.....
mDecor.startChanging();
// 標(biāo)注開始
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
// 標(biāo)注結(jié)束
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
ProgressBar progress = getCircularProgressBar(false);
if (progress != null) {
progress.setIndeterminate(true);
}
}
// Remaining setup -- of background and title -- that only applies
// to top-level windows.
if (getContainer() == null) {
Drawable drawable = mBackgroundDrawable;
if (mBackgroundResource != 0) {
drawable = getContext().getResources().getDrawable(mBackgroundResource);
}
mDecor.setWindowBackground(drawable);
drawable = null;
if (mFrameResource != 0) {
drawable = getContext().getResources().getDrawable(mFrameResource);
}
mDecor.setWindowFrame(drawable);
// System.out.println("Text=" + Integer.toHexString(mTextColor) +
// " Sel=" + Integer.toHexString(mTextSelectedColor) +
// " Title=" + Integer.toHexString(mTitleColor));
if (mTitleColor == 0) {
mTitleColor = mTextColor;
}
if (mTitle != null) {
setTitle(mTitle);
}
setTitleColor(mTitleColor);
}
mDecor.finishChanging();
return contentParent;
}
第4部分的代碼最主要的部分就是我在代碼中用注釋標(biāo)注出來的那3句代碼了.首先第1句代碼將第3部分中得到的layoutResource
布局inflate出來,第2句就將inflate出來的View添加到decor
(就是傳入的函數(shù)參數(shù)mDecor
)中,并設(shè)置寬高為MATCH_PARENT
.最后第3句通過findViewById(ID_ANDROID_CONTENT)
找到了ID為ID_ANDROID_CONTENT
的View并賦值的函數(shù)的返回值contentParent
.
這里有兩點(diǎn)需要注意.第一點(diǎn)就是這里的findViewById
是在PhoneWindow
的父類Window
類下的下的方法,它調(diào)用的是getDecorView().findViewById(int)
,最終它調(diào)用的就是mDecor.findViewById()
,也就是說contentParent
是在mDecor
里的child
,而它也是我們的mContentParent
.還記得我們的mContentParent
中的注釋嗎?
// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
private ViewGroup mContentParent;
這里我們終于知道了注釋是什么意思了.
第2點(diǎn)需要注意的就是findViewById(int)
的參數(shù)ID_ANDROID_CONTENT
.這是一個(gè)Window
的常量.
/**
* The ID that the main layout in the XML layout file should have.
*/
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
可以看出,這個(gè)就是我們上面提到的第3部分代碼中layoutResource
布局中ID為content
的FrameLayout
布局,也就是我們的activity_main.xml
布局的父布局.generateLayout()
剩下的代碼就是為了設(shè)置window的背景和標(biāo)題.
講了這么久,終于把generateLayout()
的代碼講完了,我們可以往回走了.先回到installDecor()
中.
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor(); //標(biāo)注1
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
}
if (mContentParent == null) { //標(biāo)注2
mContentParent = generateLayout(mDecor);
mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
if (mTitleView != null) {
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
View titleContainer = findViewById(com.android.internal.R.id.title_container);
if (titleContainer != null) {
titleContainer.setVisibility(View.GONE);
} else {
mTitleView.setVisibility(View.GONE);
}
if (mContentParent instanceof FrameLayout) {
((FrameLayout)mContentParent).setForeground(null);
}
} else {
mTitleView.setText(mTitle);
}
} else {
mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
if (mActionBar != null) {
if (mActionBar.getTitle() == null) {
mActionBar.setWindowTitle(mTitle);
}
final int localFeatures = getLocalFeatures();
if ((localFeatures & (1 << FEATURE_PROGRESS)) != 0) {
mActionBar.initProgress();
}
if ((localFeatures & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
mActionBar.initIndeterminateProgress();
}
// Post the panel invalidate for later; avoid application onCreateOptionsMenu
// being called in the middle of onCreate or similar.
mDecor.post(new Runnable() {
public void run() {
if (!isDestroyed()) {
invalidatePanelMenu(FEATURE_ACTION_BAR);
}
}
});
}
}
}
}
我們標(biāo)注2
的代碼也分析完了,mDecor
和mContentParent
就像前面猜測的一樣,它們通過generateLayout()
聯(lián)系了在一起.剩下的代碼可以簡單的看出還是一些window的設(shè)置,包括window的title
和ActionBar
.接著我們繼續(xù)往回走,回到setContentView()
的地方.
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();// 標(biāo)注1
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);// 標(biāo)注2
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
現(xiàn)在再看setContentView()
的代碼就感覺清晰了不少,標(biāo)注1
將mContentParent
初始化,標(biāo)注2
就將我們自己的布局文件(activity_main.xml)添加到mContentParent
中.到此我們把setContentView()
都分析清楚了.
總結(jié)
本篇文章篇幅有點(diǎn)長,但如果能認(rèn)真的看下來我相信一定會(huì)有所收獲的.下面我們來用一張圖來總結(jié)setContentView的關(guān)鍵的地方和基本的流程.
圖中的橫向的箭頭表示了相關(guān)的對(duì)應(yīng)關(guān)系,豎向的箭頭為基本的流程,希望能對(duì)大家有所幫助.