记录view是如何添加至界面显示出来。
先记录几个相关类:
1)Window:是一个抽象类,提供了绘制窗口的一组通用API。
2)PhoneWindow:是Window的具体继承实现类。而且该类内部包含了一个DecorView对象,该DectorView对象是所有应用窗口(Activity界面)的根View。
3)DecorView:是PhoneWindow的内部类,是FrameLayout的子类,是对FrameLayout进行功能的修饰(所以叫DecorXXX),是所有应用窗口的根View 。
在activity的初始化过程中,会调用activity的attach方法,在该方法中创建一个PhoneWindow实例,将其作为Activity的成员变量mWindow。之后会调用onCreate方法,只要在onCreate方法里面,调用setContentView()将布局文件(layout)id传递进去。setContentView()做了什么事呢?
Activity的源码中提供了三个重载的setContentView方法(api 23):
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
首先通过getWindow()得到Window实例mWindow,mWindow是PhoneWindow对象,在attach中初始化了。然后调用PhoneWindow的setContentView方法。
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
该方法传入layout的id,该资源代表了activity中的content内容。首先判断mContentParent是否为null,也就是第一次调用);如果是第一次调用,则调用installDecor()方法,否则判断是否设置FEATURE_CONTENT_TRANSITIONS Window属性(默认false),如果没有设置,就移除该mContentParent内所有的所有子View;接着通过mLayoutInflater.inflate(layoutResID, mContentParent);将我们的资源文件通过LayoutInflater对象转换为View树,并且添加至mContentParent视图中(其中mLayoutInflater是在PhoneWindow的构造函数中得到实例对象的LayoutInflater.from(context);)。
其中,mContentParent就是content的parent,是个viewgroup,包裹layout文件。
接下来,通过getCallBack()拿到了一个CallBack对象,事实上获取到的这个CallBack就是Activity自己,因为Activity实现了CallBack接口。
这个Callback是一个回调,当PhoneWindow接收到系统分发给它的触摸、IO、菜单等相关的事件时,可以回调相应的Activity进行处理。在这里,Callback只回调了onContentChanged,而onContentChanged在Activity中是空实现。
installDecor()的代码如下:
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
//...
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
if (mTitleView != null) {
//根据FEATURE_NO_TITLE隐藏,或者设置mTitleView的值
//...
} else {
mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
if (mActionBar != null) {
//设置ActionBar标题、图标神马的;根据FEATURE初始化Actionbar的一些显示
//...
}
}
}
}
省略例部分代码。主要做了这些事:
调用generateDecor()得到mDecor,mDecor是DecorView对象。在得到mDecor以后设置其焦点的获取方式为:当其子孙都不需要时,自己才获取。
然后调用generateLayout(mDecor);把mDecor做为参数传入,获取到mContentParent。
接下里通过findViewById进行获取控件,这里的findViewById的代码是这样的:
public View findViewById(int id) {
return getDecorView().findViewById(id);
}
getDecorView返回的就是mDecor,即DecorView。
generateDecor()代码:
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
public DecorView(Context context, int featureId) {
super(context);
mFeatureId = featureId;
}
该方法初始化一个DecorView对象。
generateLayout(mDecor)代码:
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
//...Window_windowIsFloating,Window_windowNoTitle,Window_windowActionBar...
//首先通过WindowStyle中设置的各种属性,对Window进行requestFeature或者setFlags
if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
}
//...
if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) {
setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
}
//...根据当前sdk的版本确定是否需要menukey
WindowManager.LayoutParams params = getAttributes();
//通过a中设置的属性,设置 params.softInputMode 软键盘的模式;
//如果当前是浮动Activity,在params中设置FLAG_DIM_BEHIND并记录dimAmount的值。
//以及在params.windowAnimations记录WindowAnimationStyle
// 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) {
layoutResource = com.android.internal.R.layout.screen_action_bar;
} else {
layoutResource = com.android.internal.R.layout.screen_title;
}
// System.out.println("Title!");
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = com.android.internal.R.layout.screen_simple_overlay_action_mode;
} else {
// Embedded, so no decoration is needed.
layoutResource = com.android.internal.R.layout.screen_simple;
// System.out.println("Simple!");
}
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
//...
return contentParent;
}
}
首先getWindowStyle在当前的Window的theme中获取我们的Window中定义的属性。参考:\frameworks\base\core\res\res\values\attrs.xml
<!-- The set of attributes that describe a Windows's theme. -->
<declare-styleable name="Window">
<attr name="windowBackground" />
<attr name="windowContentOverlay" />
<attr name="windowFrame" />
<attr name="windowNoTitle" />
<attr name="windowFullscreen" />
<attr name="windowOverscan" />
<attr name="windowIsFloating" />
<attr name="windowIsTranslucent" />
<attr name="windowShowWallpaper" />
<attr name="windowAnimationStyle" />
<attr name="windowSoftInputMode" />
<attr name="windowDisablePreview" />
<attr name="windowNoDisplay" />
<attr name="textColor" />
<attr name="backgroundDimEnabled" />
<attr name="backgroundDimAmount" />
然后就根据这些属性的值,对Window各种requestFeature,setFlags等等。接下来,通过对features和mIsFloating的判断,为layoutResource进行赋值,值可以为R.layout.screen_custom_title;R.layout.screen_action_bar;等等。至于features,特性,除了theme中设置的,我们也可以在Activity的onCreate的setContentView之前进行requestFeature,也解释了,为什么需要在setContentView前调用requestFeature设置全屏等。
得到了layoutResource以后,通过LayoutInflater把布局转化成view,然后加入到decor中,即传入的DecorView中。之后通过mDecor.findViewById传入R.id.content,返回mDecor(布局)中的id为ID_ANDROID_CONTENT(R.id.content)的View,一般是FrameLayout。
mDecor是一个FrameLayout,会根据theme去选择系统中的布局文件,将布局文件通过inflate转化为view,加入到mDecor中;
当generateLayout(mDecor)执行完后,installDecor()也就执行完了。然后执行mLayoutInflater.inflate(layoutResID, mContentParent);这里的layoutResID就是activity的布局文件id,将他放入到mContentParent(id为content的FrameLayout对象)ViewGroup中。中。
这样 ,activity的setContentView()就执行完了。
总结就是:
1.创建一个DecorView的对象mDecor,该mDecor对象将作为整个应用窗口的根视图。
2.根据theme中的属性值,选择合适的布局,通过infalter.inflater放入到mDecor中。
在这些布局中,一般会包含ActionBar,Title,和一个id为content的FrameLayout,这就是Activity布局文件该存放的地方。
3.将Activity的布局文件添加至id为content的FrameLayout内。
(PhoneWindow中还有一个成员变量叫mContentRoot,代表content的root,content的根节点。一般情况下,mContentParent是放在mContentRoot中。)
最后总结activity上的视图层次:最下面是Activity,上一层是PhoneWindow,PhoneWindow里面是DecorView,DecorView一般上面是一个ActionBar,下面是id是content的FrameLayout,这个FrameLayout就是放activity布局文件的地方。