系列文章传送门 (持续更新中..) :html
自定义控件(二) 从源码分析事件分发机制android
自定义控件(四) 源码分析 layout 和 draw 流程app
每个Activity都包含一个Window对象,Window由它的惟一的子类PhoneWindow实现ide
PhoneWindow:将Decoriew设置为整个应用窗口的根View。它是Android中的最基本的窗口系 统,每一个Activity 均会建立一个PhoneWindow对象,是Activity和整个View系统交互的接口。源码分析
DecorView:顶层视图,将要显示的具体内容呈如今PhoneWindow上. 它并不会向用户呈现任何东西,它主要有以下几个功能,可能不全:布局
A. Dispatch ViewRoot分发来的key、touch、trackball等外部事件;post
B. DecorView有一个直接的子View,咱们称之为System Layout,这个View是从系统的Layout.xml中解析出的,它包含当前UI的风格,如是否带title、是否带process bar等。能够称这些属性为Window decorations。动画
C. 做为PhoneWindow与ViewRoot之间的桥梁,ViewRoot经过DecorView设置窗口属性。能够同 View view = getWindow().getDecorView() 获取它;ui
D. DecorView只有一个子元素为LinearLayout。表明整个Window界面,包含通知栏,标题栏,内容显示栏三块区域。DecorView里面TitleView:标题,能够设置requestWindowFeature(Window.FEATURE_NO_TITLE)取消掉. ContentView:是一个id为content的FrameLayout。咱们日常在Activity使用的setContentView就是设置在这里,也就是在FrameLayout上
你们都知道当咱们写Activity时会调用 setContentView() 方法来加载布局, 让咱们来看一下内部实现:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
复制代码
getWindow() :
public Window getWindow() {
return mWindow;
}
复制代码
###Window 能够看到返回了一个 mWindow , 它的类型是 Window 类, 而 Window 是一个抽象类, setContentView() 也是一个抽象方法, 因此咱们必需要找到它的实现子类
/**
* Abstract base class for a top-level window look and behavior policy. An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
* 顶级窗口视图和行为的抽象基类。它的实例做为一个顶级View被添加到Window Manager。
* 它提供了一套标准的UI策略,例如背景,标题区域等。当你须要用到Window的时候,应该使
* 用它的惟一实现子类PhoneWindow。
*/
public abstract class Window {
...
public abstract void setContentView(@LayoutRes int layoutResID);
...
}
复制代码
而在 attach() 中 证明了 PhoneWindow 的初始化
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,
Window window) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window);
}
复制代码
咱们继续看一下 PhoneWindow 这个类以及实现方法 setContentView()
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;
...
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) {
// 1. 初始化: 建立 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); // Activity 转场动画相关
} else {
// 2. 填充布局: 把 setContentView() 设置进来的布局, 加载到 mContentParent,也就是 DecorView 中 id = content 的 FrameLayout
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets(); // 让DecorView的内容区域延伸到systemUi下方,防止在扩展时被覆盖,达到全屏、沉浸等不一样体验效果。
// 3. 通知 Activity 布局改变
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged(); // 触发 Activity 的 onContentChanged() 方法
}
mContentParentExplicitlySet = true;
}
}
复制代码
能够看到当 mContentParent = null , 即当前内容布局尚未放置到窗口, 也就是第一次调用的时候, 会执行 installDecor(), 咱们继续去看下该方法
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
// 生成 DecorView
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
// 根据主题 theme 设置对应的 xml布局文件以及 Feature(包括style,layout,转场动画,
// 属性等)到 DecorView中。并将 mContentParent 和 DecorView 布局中的
// ID_ANDROID_CONTENT(com.android.internal.R.id.content)绑定
mContentParent = generateLayout(mDecor);
// 省略
...
}
复制代码
能够看到先调用 genaratDecor() 生成了 mDecorView
protected DecorView generateDecor(int featureId) {
...
return new DecorView(context, featureId, this, getAttributes());
}
复制代码
建立完了后执行了 generateLayout() , 在这个方法中会 根据主题 theme 设置对应的 xml布局文件以及 Feature(包括style,layout,转场动画,属性等)到 DecorView中, 并在 DecorView 的xml 布局中 findViewById() 获取内容布局的应用 contentView 并返回,即 mContentParent 就是 DecorView 中的内容布局。由此咱们能够知道为何要在setContentView 以前调用 requesetFeature 的缘由。
这个方法有点长,咱们大体看一下
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme. --> 获取当前的主题, 加载默认的资源和布局
/**
* 下面的代码: 根据 theme 设定, 找到对应的 Feature(包括 style, layout, 转场动画, 属性等)
* /
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())); } // 透明状态栏 if (a.getBoolean(R.styleable.Window_windowTranslucentStatus, false)) { setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS & (~getForcedWindowFlags())); } // 其它资源的加载 ... ... /** * 下面是添加布局到 DecorView. * 在前面咱们看到已经调用 new DecorView 来建立一个实例, 可是 DecorView 自己是一个 * 继承了 FrameLayout 的 ViewGroup, 建立完了后尚未内容因此还须要对它建立相应的布 * 局. 而下面的代码则是根据用户设置的 Feature 来建立相应的默认布局主题. * * 举个例子: * 若是我在setContentView以前调用了requestWindowFeature(Window.FEATURE_NO_TITLE), * 这里则会经过getLocalFeatures来获取你设置的feature,进而选择加载对应的布局,此时则是加载 * 没有标题栏的主题,对应的就是R.layout.screen_simple * / * // Inflate the window decor. int layoutResource; int features = getLocalFeatures(); if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) { layoutResource = R.layout.screen_swipe_dismiss; } (// 省略各类 else if 判断){ layoutResource = ...; }else { // Embedded, so no decoration is needed. layoutResource = R.layout.screen_simple; } mDecor.startChanging(); // 把相应的布局建立并添加到 DecorView 中 mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); // 从布局中获取 R.id.content ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); ... // 配置 DecorView 完成 mDecor.finishChanging(); return contentParent; } 复制代码
能够看到 在 else{} 中加载的是没有标题栏的主题,对应的就是R.layout.screen_simple,咱们看下里面的布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<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:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
复制代码
能够看到xml布局中根布局是 LinearLayout, 包含两个子元素, 由于能够 no_title , 因此第一个是 ViewStub, 第二个子元素 id : content , 则是对应以前代码中的 mContentParent, 也就是 generateLayout() 返回的对象, 即 setContentView() 设置的内容就是添加到这个 FrameLayout 中。
咱们继续回到 setContentView() . 在方法的最后经过 cb.onContentChanged() 来通知界面改变的。Callback 是 Window 的内部接口,里面声明了当界面更改触摸时调用的各类方法, 并在Activity 中实现了这个接口, 而且实现的方法是空的,因此咱们能够经过重写这个方法, 来监听布局内容的改变了
public void onContentChanged() {
}
复制代码
参考文章: Android窗口机制 Android View体系(六)从源码解析Activity的构成
若是以为对你有帮助, 请点个赞再走吧~