Android -- DecorView


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

添加至窗口流程

1

  • 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的布局文件添加至窗口里,上面的過程可以概括為:

  1. 創建一個DecorView對象,該對象將作為整個應用窗口的根視圖
  2. 創建不同的窗口修飾布局文件,並且獲取Activity的布局文件該存放的地方,由該窗口修飾布局文件內id為content的FrameLayout指定 。
  3. 將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); //使其處於顯示狀況  
}

布局層次結構

2

干貨

@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結構,可以看到結構如下:

3

我是天王蓋地虎的分割線

 

 

參考:http://www.cnblogs.com/yogin/p/4061050.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM