Android窗口机制(一)——Window,PhoneWindow,DecorView理解
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则是空的,也就是我们可以通过重写该方法来监听布局内容的改变了。
总结
- Window是一个抽象类,PhoneWindow则是Window的唯一实现类,提供了各种窗口操作的方法,比如设置背景标题ContentView等等;
- 每一个Activity上面都有一个Window,可以通过getWindow获取;
- DecorView,顶级视图,继承与FramentLayout,setContentView则是添加在它里面的@id/content里;
- setContentView里面创建了DecorView,根据Theme,Feature添加了对应的布局文件,当setContentView设置显示后会回调Activity的onContentChanged方法;
更多相关文章
- 【Android】 dialog 设置maxHeight 最大高度
- android获取versionName和versionCode
- android 基础知识
- android 调用webservice
- 为ListActivity 添加Button
- 三步搞定:Vue.js调用Android原生操作
- Android(安卓)--- 图片处理的方法
- 浅谈Java中Collections.sort对List排序的两种方法
- Python list sort方法的具体使用