DecorView
開發中,通常都是在onCreate()中調用setContentView(R.layout.custom_layout)來實現想要的頁面布局。頁面都是依附在窗口之上的,而DecorView即是窗口最頂層的視圖。Android frameworks中,與窗口視圖處理相關的類,主要是Window及其實現類PhoneWindow
public class PhoneWindow extends Window implements MenuBuilder.Callback { //... //窗口頂層View private DecorView mDecor; //所有自定義View的根View, id="@android:id/content" private ViewGroup mContentParent; //... }
DecorView其實是PhoneWindow中的一個內部類,本質上也是一個View,其只是擴展了FrameLayout的實現
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker
添加至窗口流程
- Activity中調用setContentView(R.layout.custom_layout), 具體實現為PhoneWindow中的同名方法
public void setContentView(int layoutResID) { //getWindow()獲取的即是PhoneWindow對象 getWindow().setContentView(layoutResID); }
看一下window類
public abstract class Window { //... //指定Activity窗口的風格類型 public static final int FEATURE_NO_TITLE = 1; public static final int FEATURE_INDETERMINATE_PROGRESS = 5; //設置布局文件 public abstract void setContentView(int layoutResID); public abstract void setContentView(View view); //請求指定Activity窗口的風格類型 public boolean requestFeature(int featureId) { final int flag = 1<<featureId; mFeatures |= flag; mLocalFeatures |= mContainer != null ? (flag&~mContainer.mFeatures) : flag; return (mFeatures&flag) != 0; } //... }
- PhoneWindow執行setContentView(int layoutResource)
PhoneWindow該類繼承於Window類,是Window類的具體實現,即我們可以通過該類具體去繪制窗口。並且,該類內部包含了一個DecorView對象,該DectorView對象是所有應用窗口(Activity界面)的根View。 簡而言之,PhoneWindow類是把一個FrameLayout類即DecorView對象進行一定的包裝,將它作為應用窗口的根View,並提供一組通用的窗口操作接口。
public void setContentView(int layoutResID) { //初始,mContentParent為空 if (mContentParent == null) { installDecor(); } else { mContentParent.removeAllViews(); } //inflate自定義layout, 並將mContentParent作為其根視圖 mLayoutInflater.inflate(layoutResID, mContentParent); //... }
該方法根據首先判斷是否已經由setContentView()了獲取mContentParent即View對象, 即是否是第一次調用該PhoneWindow對象setContentView()方法。如果是第一次調用,則調用installDecor()方法,否則,移除該mContentParent內所有的所有子View。最后將我們的資源文件通過LayoutInflater對象轉換為View樹,並且添加至mContentParent視圖中(在應用程序里,我們可以多次調用setContentView()來顯示我們的界面。)。
- PhoneWindow.installDecor()
private void installDecor() { if (mDecor == null) { //new一個DecorView mDecor = generateDecor(); mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); } } if (mContentParent == null) { //這一步會設置窗口的修飾文件,並將id為ID_ANDROID_CONTENT的view find出來作為返回值賦值給mContentParent mContentParent = generateLayout(mDecor); //... }
- PhoneWindow.generateLayout(DecorView decor)
protected ViewGroup generateLayout(DecorView decor) { //1,獲取<Application android:theme=""/>, <Activity/>節點指定的themes或者代碼requestWindowFeature()中指定的Features, 並設置 TypedArray a = getWindowStyle(); //... //2,獲取窗口Features, 設置相應的修飾布局文件,這些xml文件位於frameworks/base/core/res/res/layout下 int layoutResource; int features = getLocalFeatures(); 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; } removeFeature(FEATURE_ACTION_BAR); } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0 && (features & (1 << FEATURE_ACTION_BAR)) == 0) { layoutResource = com.android.internal.R.layout.screen_progress; //... mDecor.startChanging(); //3, 將上面選定的布局文件inflate為View樹,添加到decorView中 View in = mLayoutInflater.inflate(layoutResource, null); decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); //將窗口修飾布局文件中id="@android:id/content"的View賦值給mContentParent, 后續自定義的view/layout都將是其子View ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); if (contentParent == null) { throw new RuntimeException("Window couldn't find content container view"); } //... }
該方法會做如下事情:
根據窗口的風格修飾類型為該窗口選擇不同的窗口布局文件(根視圖)。這些窗口修飾布局文件指定一個用來存放Activity自定義布局文件的ViewGroup視圖,一般為FrameLayout 其id 為: android:id="@android:id/content"。
例如窗口修飾類型包括FullScreen(全屏)、NoTitleBar(不含標題欄)等。選定窗口修飾類型有兩種:
①指定requestFeature()指定窗口修飾符,PhoneWindow對象調用getLocalFeature()方法獲取值;
②為我們的Activity配置相應屬性,即android:theme=“”,PhoneWindow對象調用getWindowStyle()方法獲取值。
舉例如下,隱藏標題欄有如下方法:
requestWindowFeature(Window.FEATURE_NO_TITLE);
或者為Activity配置xml屬性:
android:theme="@android:style/Theme.NoTitleBar"
因此,在Activity中必須在setContentView之前調用requestFeature()方法。
確定好窗口風格之后,選定該風格對應的布局文件,這些布局文件位於 frameworks/base/core/res/layout/ ,
典型的窗口布局文件有:
R.layout.dialog_titile_icons R.layout.screen_title_icons R.layout.screen_progress R.layout.dialog_custom_title R.layout.dialog_title R.layout.screen_title // 最常用的Activity窗口修飾布局文件 R.layout.screen_simple //全屏的Activity窗口布局文件
- 最后頁面中設置的自定義layout會被添加到mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
整個過程主要是如何把Activity的布局文件添加至窗口里,上面的過程可以概括為:
- 創建一個DecorView對象,該對象將作為整個應用窗口的根視圖
- 創建不同的窗口修飾布局文件,並且獲取Activity的布局文件該存放的地方,由該窗口修飾布局文件內id為content的FrameLayout指定 。
- 將Activity的布局文件添加至id為content的FrameLayout內。
最后,當AMS(ActivityManagerService)准備resume一個Activity時,會回調該Activity的handleResumeActivity()方法,該方法會調用Activity的makeVisible方法 ,顯示我們剛才創建的mDecor 視圖族。
//系統resume一個Activity時,調用此方法 final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) { ActivityRecord r = performResumeActivity(token, clearHide); //... if (r.activity.mVisibleFromClient) { r.activity.makeVisible(); } }
void makeVisible() { if (!mWindowAdded) { ViewManager wm = getWindowManager(); // 獲取WindowManager對象 wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded = true; } mDecor.setVisibility(View.VISIBLE); //使其處於顯示狀況 }
布局層次結構
干貨
@Override protected void onCreate(Bundle savedInstanceState) { //設置窗口無標題欄 requestWindowFeature(Window.FEATURE_NO_TITLE); super.onCreate(savedInstanceState); setContentView(R.layout.activity_decor); }
activity_decor.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".DecorActivity"> <TextView android:text="@string/hello_world" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:text="@string/hello_world" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true"/> </RelativeLayout>
onCreate()中設置的Window.FEATURE_NO_TITLE對應的窗口修飾布局文件為screen_simple.xml, 源碼如下
<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" /> <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>
源碼中id為"@android:id/content"的FrameLayout就是內容區域,在整個流程中,其會賦值給PhoneWindow類中的屬性mContentParent, 運行應用后,使用SDK提供的hierarchyviewer工具查看頁面的ViewTree結構,可以看到結構如下:
我是天王蓋地虎的分割線
參考:http://www.cnblogs.com/yogin/p/4061050.html