上次的两篇文章,我们讨论了创建自定义View的基本流程.对View
有了基本的了解后,有好奇心的同学可能会对View
的基本原理充满好奇(其实我也非常好奇View
在Android系统下是怎样实现的),所以我就本着好奇心看了很多关于View
的实现原理和View
的基本工作流程的文章,也看了一些源码,对View
有了更加深入的理解.在这我就跟大家分享一下我对View
的理解,希望能对大家的学习有所帮助.若有什么错误的地方还希望大家帮我指出.
从setContentView
开始
写过Android应用的都知道,我们最开始接触View
是在布局文件activity_main.xml
中.在我们的MainActivity
中onCreate()
方法中,通过setContentView(R.layout.activity_main)
设置我们的Activity布局.View的所有工作都是从这里开始的,为了一探究竟,我们就进到setContentView()
中去,看看它究竟是怎样实现的.下面就是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为使用兼容库的应用所提供的一个Activity基类,这里为了研究方便,减少其他无关代码的干扰,我们使用Activity
类进行讨论.
我们可以看到这里最终调用的是getWindow().setContentView(layoutID)
进行布局设置.
// Window 类下的setContentView()
public abstract void setContentView(View view);
可以看到这是一个抽象方法,需要由子类来实现.这就需要我们找到Window
的实现类了.我们再来看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
成员对象.但我们还是没找到Window
的实现类啊,先别急,我们在Activity
类里搜索mWindow
关键字,看看它是在哪里被实例化的.经过搜索后,发现了在mWindow
是在Activity
类下的attach()
函数里被实例化的.下面是相关的代码:
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在这里被实例化
mWindow = new PhoneWindow(this);
........
}
由于attach()
的代码比较长,我只贴出了我们关心的代码,就是上面那一句.这里很容易就看出了mWindow
就是一个PhoneWindow
的一个实例.这样我们就可以查看mWindow
中的setContentView()
的实现了.
一切都在PhoneWindow
PhoneWindow
并不是Android SDK内的类,我们在Android Studio中无法看到其代码.遇到这种情况肯定要到Internet上搜索一下,果然我们可以到一些在线的Android源码网站上查看它的源代码,我们可以找到PhoneWindow的源码地址.然后经过网页内的搜索找到了setContentView(int)
的代码.
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();// 标注1
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);// 标注2
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
我在代码中标注了两个地方,我们先来看标注2
.这里可以清除的看到Android将我们传进来的xml布局文件进行了inflate
并将它添加到mContentParent
这个容器中.我们找到mContentParent
的声明,发现它是一个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;
这里的注释说了,这个mContentParent
是存放窗口内容的一个View
,它要不就是mDecor
自己,要不就是一个存放窗口内容的mDecor
的child
.而在这行代码的上面正好是mDecor
的声明,它是一个DecorView
对象,是window的顶层view,里面有window decor
.
看到这里是不是有点晕?不要紧,先接着看,我马上让你的头脑清晰起来.我们先转到DecorView
这个PhoneWindow
的内部类的声明:
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
......
}
这里可以看到DecorView
继承了FrameLayout
,而我们知道FrameLayout
继承了ViewGroup
.所以说这里的DecorView
也是一个ViewGroup
对象,看到这里,上面关于mContentParent
和mDecor
的注释也就清楚是什么意思了.mContentParent
就是mDecor
,或者就是mDecor
里的一个子View,而且里面的内容就是顶层窗口的内容,就是我们activity_main.xml
布局的父布局(可以说是Activity的根布局).
知道了mCOntentParent
和mDecor
是什么之后,我就在想:既然mContentParent
与mDecor
有关系,那究竟它俩是怎样联系起来的呢? 抱着这个问题先回到setContentView()
的代码中,为了方便,我在这重新将代码再贴一遍.
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();// 标注1
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);// 标注2
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
我们来看标注1
的地方,这里是当mContentParent
为空的时候才会执行,我猜这个installDecor()
函数是为了对mDecor
进行初始化设置并对mContentParent
进行赋值的,这里就是mContentParent
和mDecor
产生联系的地方.为了看一下我的猜测是否正确,我们找到installDecor()
的代码.
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor(); //标注1
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
}
if (mContentParent == null) { //标注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);
}
}
});
}
}
}
}
这里的代码比较长,但这并不影响我们的阅读,因为在函数的前面几行,我看到了关键的部分,这些关键的部分我都在上面的代码标注了起来.我们先来看标注1
.
if (mDecor == null) {
mDecor = generateDecor(); //标注1
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
}
这里对mDecor
使用了generateDecor()
进行赋值,可以从函数名看出这是一个创建DecorView
对象的函数,我们来看一下是否如此.
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
果然如此,这里直接new
了一个DecorView
对象并返回.但是参数列表里的-1
是什么呢?我们来到DecorView
的构造函数.
/** 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;
}
可以看到,第二个构造参数是一个featureID
,而且将此赋值到了mFeatureId
.这个mFeatureId
是DecorView的feature ID
,-1表示这个DecorView是application
的DecorView
.这里的mFeatureId
应该是用来标注DecorView
的类型的,或者是用来设置窗口的具体样式的.我也搞不太清楚,在Internet上搜了很久也没找到答案,如果有同学知道答案希望能分享一下.虽然在这里遇到了一点问题,但这并不会对我们的后续分析有太大的影响.
我们清楚了generateDecor()
是用于初始化mDecor
对象,我们接着分析标注1
后面的代码,这里
- mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
- mDecor.setIsRootNamespace(true);
两句代码可以看得出是对mDecor
进行的一些初始化的操作.然后我们来看标注2
的代码.
if (mContentParent == null) { //标注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);
}
}
这里同样的也调用了generateLayout(mDecor)
函数为mContentParent
赋值,注意这里将初始化好的mDecor
作为参数传入.按照惯例,转到generateLayout()
的代码.由于代码有点长,为了方便分析,我就分开几部分别贴出来.首先我们先看第一部分的代码:
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的主题样式属性并进行相应设置,就是处理我们在AndroidManifest.xml
或在style.xml
对theme进行的设置.我上次在View的构造函数分析时讲到了样式属性的获取,这里的也是一样的方法.我们可以找到Window
的getWindowStyle()
函数的代码看看.
/**
* 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
的资源属性.相关的内容可以参考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);
}
....
第二部分的代码就是设置WindowManager.LayoutParams
窗口布局参数,然后对窗口的mBackgroundResource
和mFrameResource
进行赋值.这部分的代码也是对window进行样式的设置.我们接着看第三部分的代码.
// 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
两个局部变量,这两个变量在后面会有特别的作用.第3部分的代码就是根据feature
的值来对layoutResource
进行赋值.layoutResource
的值可能会是下列值的其中一个:
- 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
- 或是一个dialog的布局
这些资源id所对应的布局文件同样可以在Android的Github网站上找到.可以从这些布局文件的名字看出这些都是window screen的布局.看过这些文件的同学都会发现它们都有一个共同点,那就是它们大多都是一个LinearLayout
的父布局下面有一个ID为action_mode_bar_stub
的ViewStub
和一个ID为content
的FrameLayout
,这一点我们后面会提到.
经过分析我们知道了layoutResource
代表的是一个布局,至于这个布局是谁的布局,我们看了第4部分的代码就清楚了.
.....
mDecor.startChanging();
// 标注开始
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");
}
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部分的代码最主要的部分就是我在代码中用注释标注出来的那3句代码了.首先第1句代码将第3部分中得到的layoutResource
布局inflate出来,第2句就将inflate出来的View添加到decor
(就是传入的函数参数mDecor
)中,并设置宽高为MATCH_PARENT
.最后第3句通过findViewById(ID_ANDROID_CONTENT)
找到了ID为ID_ANDROID_CONTENT
的View并赋值的函数的返回值contentParent
.
这里有两点需要注意.第一点就是这里的findViewById
是在PhoneWindow
的父类Window
类下的下的方法,它调用的是getDecorView().findViewById(int)
,最终它调用的就是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点需要注意的就是findViewById(int)
的参数ID_ANDROID_CONTENT
.这是一个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;
可以看出,这个就是我们上面提到的第3部分代码中layoutResource
布局中ID为content
的FrameLayout
布局,也就是我们的activity_main.xml
布局的父布局.generateLayout()
剩下的代码就是为了设置window的背景和标题.
讲了这么久,终于把generateLayout()
的代码讲完了,我们可以往回走了.先回到installDecor()
中.
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor(); //标注1
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
}
if (mContentParent == null) { //标注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);
}
}
});
}
}
}
}
我们标注2
的代码也分析完了,mDecor
和mContentParent
就像前面猜测的一样,它们通过generateLayout()
联系了在一起.剩下的代码可以简单的看出还是一些window的设置,包括window的title
和ActionBar
.接着我们继续往回走,回到setContentView()
的地方.
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();// 标注1
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);// 标注2
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
现在再看setContentView()
的代码就感觉清晰了不少,标注1
将mContentParent
初始化,标注2
就将我们自己的布局文件(activity_main.xml)添加到mContentParent
中.到此我们把setContentView()
都分析清楚了.
总结
本篇文章篇幅有点长,但如果能认真的看下来我相信一定会有所收获的.下面我们来用一张图来总结setContentView的关键的地方和基本的流程.
图中的横向的箭头表示了相关的对应关系,竖向的箭头为基本的流程,希望能对大家有所帮助.