Window
public abstract class Window {
public <T extends View> T findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}
public abstract void setContentView(@LayoutRes int layoutResID);
public abstract void onConfigurationChanged(Configuration newConfig);
}
一个顶级窗口查看和行为的一个抽象基类。这个类的实例作为一个顶级View添加到Window Manager。它提供了一套标准的UI方法,比如添加背景,标题等等。当你需要用到Window的时候,你应该使用它的唯一实现类PhoneWindow。可以看到,Window是一个抽象基类,它提供了一系列窗口的方法,比如设置背景,标题等等,而它的唯一实现类则是PhoneWindow。
PhoneWindow
public class PhoneWindow extends Window implements MenuBuilder.Callback {
//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.
ViewGroup mContentParent;
}
可以看到,在PhoneWindow里面,出现了成员变量DecorView,而DecorView是继承与FrameLayout。
DecorView
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
private PhoneWindow mWindow;
void setWindow(PhoneWindow phoneWindow) {
mWindow = phoneWindow;
Context context = getContext();
if (context instanceof DecorContext) {
DecorContext decorContext = (DecorContext) context;
decorContext.setPhoneWindow(mWindow);
}
}
}
既然是FrameLayout,就可以加载布局文件,也就是说,我们那些标题栏,内容栏,顶级上看是加载在DecorView上的,而DecorView则是由PhoneWindow负责添加。
从setContentView源码流程分析
从Activity里面的setContentView,就是我们平常把布局内容显示到界面上的一个方法。
public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
//mWindow = new PhoneWindow(),后续将介绍
public Window getWindow() {
return mWindow;
}
这里的mWindow.setContentView(),实际上调用到的是它的实现类方法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) {
//创建DecorView,并添加到mContentParent上
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 {
//将要加载的资源添加到mContentParent上
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
//回调通知表示完成界面加载
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
如果当前内容还未放置到窗口,此时mContentParent==null,也就是第一次调用的时候,调用那个installDecor方法,而后将我们的资源文件通过LayoutInflater对象转换为View树,并且添加至mContentParent视图中。
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
//调用该方法创建new一个DecorView
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
//一开始DecorView未加载到mContentParent,所以此时mContentParent=null
if (mContentParent == null) {
//该方法将mDecorView添加到Window上绑定布局
mContentParent = generateLayout(mDecor);
}
protected DecorView generateDecor(int featureId) {
return new DecorView(context, featureId, this, getAttributes());
}
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
//根据当前设置的主题来加载默认布局
TypedArray a = getWindowStyle();
//如果你在theme中设置了window_windowNoTitle,则这里会调用到,其他方法同理,
//这里是根据你在theme中的设置去设置的
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
//是否有设置全屏
if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
}
...//省略其他加载资源
/*添加布局到DecorView,前面说到,DecorView是继承与FrameLayout,
它本身也是一个ViewGroup,而我们前面创建它的时候,只是调用了new DecorView,
此时里面并无什么东西。而下面的步奏则是根据用户设置的Feature来创建相应的默认布局主题。
举个例子,如果我在setContentView之前调用了requestWindowFeature(Window.FEATURE_NO_TITLE),
这里则会通过getLocalFeatures来获取你设置的feature,进而选择加载对应的布局,
此时则是加载没有标题栏的主题,对应的就是R.layout.screen_simple
*/
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} ... //省略其他判断方法
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
//选择对应布局创建添加到DecorView中
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
//@android:id/content添加到contentParent中
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
return contentParent;
}
首先generateLayout会根据当前用户设置的主题去设置对应的Feature,接着,根据对应的Feature来选择加载对应的布局文件,(Window.FEATURE_NO_TITLE)接下来通过getLocalFeatures来获取你设置的feature,进而选择加载对应的布局,这也就是为什么我们要在setContentView之前调用requesetFeature的原因。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:fitsSystemWindows="true">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize"
style="?android:attr/windowTitleBackgroundStyle">
<TextView android:id="@android:id/title"
style="?android:attr/windowTitleStyle"
android:background="@null"
android:fadingEdge="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />
FrameLayout>
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
LinearLayout>
DecorView只有一个子元素为LinearLayout。代表整个Window界面,包含通知栏,标题栏,内容显示栏三块区域。注意FrameLayout里面的id,@android:id/content ,我们setContentView的内容就是添加到这个FrameLayout中。
generateLayout的返回是contentParent,而它的获取则是ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
正是id为content的FrameLayout,接着就是将你setContentView的内容添加到mContentParent中。
mLayoutInflater.inflate(layoutResID, mContentParent);
或者
mContentParent.addView(view, params);
最后调用Callback来通知界面发生改变。Callback是Window里面的一个接口,里面声明了当界面更改触摸时调用的各种方法。
public interface Window.Callback {
public boolean dispatchKeyEvent(KeyEvent event);
public boolean dispatchKeyShortcutEvent(KeyEvent event);
public boolean dispatchTouchEvent(MotionEvent event);
public boolean onMenuOpened(int featureId, Menu menu);
public void onContentChanged();
public void onAttachedToWindow();
public void onDetachedFromWindow();
...
}
public void setCallback(Callback callback) {
mCallback = callback;
}
public final Callback getCallback() {
return mCallback;
}
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback { ... }
可以看到Activity里面实现了Window.Callback接口而里面onContentChanged则是空的,也就是我们可以通过重写该方法来监听布局内容的改变了。
总结