經過對前面的一篇博文<從setContentView()談起>的學習,咱們掌握了Activity組件佈局文件地建立過程以及java
其頂層控件DecorView,今天咱們繼續庖丁解牛---深刻到其中的generateLayout()方法,步步爲營掌握一下內容:android
一、Activity中Theme(主題)的系統定義以及使用之處;app
二、如何根據設置的Feature(屬性)選擇合適的佈局文件。框架
另外,對於下文中Theme和Style的概念進行一個簡要說明:ide
都是由<style />節點進行定義的。但應用在<application />和<activity />則爲theme,應用在<View />則爲style.函數
1、關於Theme主題的使用方法以及原理分析
一般來講,能夠直接使用系統定義好的Style/Theme,畢竟,系統爲咱們提供了豐富地選擇。固然,你也能夠oop
自定義Theme,前提是該Theme必須繼承與某個已經存在地Theme,不然編譯器會提示錯誤的。源碼分析
一、 應用Theme屬性兩種方式
①、在AndroidManifest.xml文件中在<application/>或者<activity />節點設置android:theme屬性.佈局
②、直接在代碼中調用方法setTheme()設置該Activity的主題,必須得在第一次調用setContentView()前設置,學習
不然,也是沒有效果的(具體緣由可見後面分析)。
二、原理分析
Android的全部系統資源定義位置存放在 frameworks\base\core\res\ 路徑下,編譯時會造成apk文件,即
framework-res.apk,全部應用程序共享。
實際上任何Style/Theme也是一組自定義屬性集合,其內置在Android系統資源中,以下所示:
文件路徑:frameworks\base\core\res\res\values\attrs.xml
- <!-- The set of attributes that describe a Windows's theme. -->
- <declare-styleable name="Window">
- <!-- 常見的Window屬性 -->
-
- <attr name="windowBackground" />
- <attr name="windowFrame" />
- <attr name="windowNoTitle" />
- <attr name="windowFullscreen" />
- <attr name="windowIsFloating" />
- <attr name="windowIsTranslucent" />
- <attr name="windowSoftInputMode" />
-
- <!-- more 更多不常見地Window屬性-->
- ...
-
- </declare-styleable>
特殊的是若是某個自定義屬性若是沒有指名 format屬性,那麼該屬性必須在當前已經定義,即該屬性只是一個
別名。
大部分Android屬性定義在 name = "Theme"的屬性集合下(僅列出Window attrs):
文件路徑:frameworks\base\core\res\res\values\attrs.xml
- <!-- These are the standard attributes that make up a complete theme. -->
- <declare-styleable name="Theme">
- <!-- Drawable to use as the overall window background. There are a
- few special considerations you should use when settings this
- drawable:
- -->
- <attr name="windowBackground" format="reference" />
- <!-- Drawable to use as a frame around the window. -->
- <attr name="windowFrame" format="reference" />
- <!-- Flag indicating whether there should be no title on this window. -->
- <attr name="windowNoTitle" format="boolean" />
- <!-- Flag indicating whether this window should fill the entire screen. -->
- <attr name="windowFullscreen" format="boolean" />
- <!-- Flag indicating whether this is a floating window. -->
- <attr name="windowIsFloating" format="boolean" />
- <!-- Flag indicating whether this is a translucent window. -->
- <attr name="windowIsTranslucent" format="boolean" />
- <!-- Flag indicating that this window's background should be the
- user's current wallpaper. -->
- <attr name="windowShowWallpaper" format="boolean" />
- <!-- This Drawable is overlaid over the foreground of the Window's content area, usually
- to place a shadow below the title. -->
- <!-- This Drawable is overlaid over the foreground of the Window's content area, usually
- to place a shadow below the title. -->
- <attr name="windowContentOverlay" format="reference" />
- <!--more -->
- </declare-styleable>
屬性定義如上,Android系統中這些屬性定義了不少Style/Theme ,常見的有以下 :
- android:theme="Theme"
- android:theme="Theme.Light"
- android:theme="Theme.Light.NoTitleBar"
- android:theme="Theme.Light.NoTitleBar.Fullscreen"
- android:theme="Theme.Black"
名稱爲"Theme"屬性(系統默認的Theme)的定義爲(僅copy部分關於Window屬性的定義) :
文件位於:frameworks\base\core\res\res\values\themes.xml
- <style name="Theme">
- <!-- Window attributes -->
- <item name="windowBackground">@android:drawable/screen_background_dark</item>
- <item name="windowFrame">@null</item>
- <item name="windowNoTitle">false</item>
- <item name="windowFullscreen">false</item>
- <item name="windowIsFloating">false</item>
- <item name="windowTitleSize">25dip</item>
- <item name="windowTitleBackgroundStyle">@android:style/WindowTitleBackground</item>
- <item name="android:windowAnimationStyle">@android:style/Animation.Activity</item>
- <item name="android:windowSoftInputMode">stateUnspecified|adjustUnspecified</item>
- lt;/style>
該Theme做爲一個超元素集,全部其餘的Style/Theme則繼承了它。例如:咱們關於自定義的Theme必須顯示從
一個父Theme繼承,以下:
- <!--自定義Theme 必須制定parent屬性-->
- <style name="CustomTheme" parent="@android:style/Theme" >
- <item name="android:windowNoTitle">true</item>
- <item name="android:windowFrame">@drawable/icon</item>
- <item name="android:windowBackground">?android:windowFrame</item>
- </style>
咱們看看Android另一個Theme.NoTitleBar屬性定義,默認繼承了"Theme"集合。
- <!-- Variant of the default (dark) theme with no title bar -->
- <style name="Theme.NoTitleBar">
- <item name="android:windowNoTitle">true</item>
- </style>
其實xml文件中聲明的任何元素(包括屬性),必須經過代碼去獲取他們的值,而後進行適當地邏輯運算。那麼
系統是在什麼地方去解析這些Window屬性,而且選擇合適地佈局文件?
2、Theme主題的解析以及佈局文件的選取
若是對setContentView()調用過程不太熟悉的朋友,能夠先看看前面一篇博文<從setContentView()談起>。
今天咱們深刻到其中generateLayout()方法,該方法地主要做用就是解析這些Window屬性,而後選擇合適地
佈局文件做爲咱們地Activity或者Window界面地承載佈局文件,即DecorView的直接子View。
在進行具體分析以前,Android還提供了另外兩種簡單API讓咱們制定界面的風格,以下兩個方法:
一、requestFeature() 設定個該界面的風格Feature,例如,FEATURE_NO_TITLE(沒有標題) 、
FEATURE_PROGRESS(標題欄帶進度條) 。必須在setContentView()前調用,不然會報異常。
FEATURE屬性定義在Window.java類
二、getWindow().setFlags(),爲當前的WindowManager.LayoutParams添加一些Flag。
Flag標記定義在WindowManager.LayoutParams.java類。
經過這兩種方法隱藏狀態欄和標題欄的例子爲:
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
-
- requestWindowFeature(Window.FEATURE_NO_TITLE);
-
-
- getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
- WindowManager.LayoutParams.FLAG_FULLSCREEN);
- setContentView(R.layout.main);
- }
完整例子,可見於博文
<Android隱藏狀態欄和標題欄,至關於全屏效果>
源碼分析:
這兩個方法是在Window.java類實現的,以下:
- public class Window {
-
- public static final int FEATURE_OPTIONS_PANEL = 0;
-
- public static final int FEATURE_NO_TITLE = 1;
-
- public static final int FEATURE_PROGRESS = 2;
-
- public static final int FEATURE_LEFT_ICON = 3;
-
- public static final int FEATURE_RIGHT_ICON = 4;
-
- public static final int FEATURE_INDETERMINATE_PROGRESS = 5;
-
- public static final int FEATURE_CONTEXT_MENU = 6;
-
- public static final int FEATURE_CUSTOM_TITLE = 7;
-
-
- protected static final int DEFAULT_FEATURES = (1 << FEATURE_OPTIONS_PANEL) |
- (1 << FEATURE_CONTEXT_MENU);
-
- private int mFeatures = DEFAULT_FEATURES;
-
-
- public boolean requestFeature(int featureId) {
- final int flag = 1<<featureId;
- mFeatures |= flag;
-
- mLocalFeatures |= mContainer != null ? (flag&~mContainer.mFeatures) : flag;
- return (mFeatures&flag) != 0;
- }
-
- public void addFlags(int flags) {
- setFlags(flags, flags);
- }
-
-
-
- public void setFlags(int flags, int mask) {
- final WindowManager.LayoutParams attrs = getAttributes();
-
- attrs.flags = (attrs.flags&~mask) | (flags&mask);
- mForcedWindowFlags |= mask;
- if (mCallback != null) {
- mCallback.onWindowAttributesChanged(attrs);
- }
- }
- ...
- }
其實也挺簡單的,主要是邏輯運算符的操做。
mFeatures 表明了當前Window的Feature值.
flags 保存在當前WindowManager.LayoutParams.flag屬性中。
接下來具體分析generateLayout()方法.
若是當前界面的DecorView對象爲空(通常由setContentView()或者addContentView()調用),則會建立一個
DecorView對象以及對應的裝載xml佈局的mContentParent對象。
Step 1、建立DecorView對象
- private void installDecor() {
- if (mDecor == null) {
- mDecor = generateDecor();
- mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
- }
-
- if (mContentParent == null) {
- mContentParent = generateLayout(mDecor);
- ...
- }
- }
Step 2、建立mContentParent對象
- protected ViewGroup generateLayout(DecorView decor) {
-
- TypedArray a = getWindowStyle();
-
-
-
- mIsFloating = a.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false);
- ...
- }
首先獲取系統自定義的Style對應的TypeArray對象,而後獲取對應的屬性值。咱們繼續分析getWindowStyle()
方法。
2.一、
- public final TypedArray getWindowStyle() {
- synchronized (this) {
- if (mWindowStyle == null) {
-
- mWindowStyle = mContext.obtainStyledAttributes(com.android.internal.R.styleable.Window);
- }
- return mWindowStyle;
- }
- }
調用Context類對應地obtainStyledAttributes()方法,參數傳遞的是 Window對應的自定義屬性集合。
2.二、
- public final TypedArray obtainStyledAttributes(
- int[] attrs) {
-
- return getTheme().obtainStyledAttributes(attrs);
- }
因爲Activity繼承至ContextThemeWapprer類,ContextThemeWapprer重寫了getTheme()方法。
2.3
- @Override
- public Resources.Theme getTheme() {
- if (mTheme != null) {
- return mTheme;
- }
-
- if (mThemeResource == 0) {
- mThemeResource = com.android.internal.R.style.Theme;
- }
- initializeTheme();
- return mTheme;
- }
首先,判斷是不是第一次調用該方法,便是否建立了mTheme對象;
其次,判斷是否設置了該Theme所對應的資源ID,若是沒有,則選取默認的theme style
即com.android.internal.R.style.Theme 。
最後,初始化對應資源。
- protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) {
- theme.applyStyle(resid, true);
- }
-
- private void initializeTheme() {
- final boolean first = mTheme == null;
- if (first) {
- mTheme = getResources().newTheme();
- Resources.Theme theme = mBase.getTheme();
- if (theme != null) {
- mTheme.setTo(theme);
- }
- }
- onApplyThemeResource(mTheme, mThemeResource, first);
- }
若是沒有手動設置mThemeResource,則選取系統中爲咱們提供的默認Theme。固然咱們也能夠手動設置Theme
Resource ,如開篇所述。
方法一: Activity中調用setTheme()方法,該方法會實如今ContextThemeWrapper.java類中。
- @Override
- public void setTheme(int resid) {
- mThemeResource = resid;
- initializeTheme();
- }
方法二:在AndroidManifest文件中,爲Activity節點配置android:theme屬性. 當經過startActivity()啓動一個
Activity時,會調用setTheme()方法。文件路徑:frameworks\base\core\java\android\app\ActivityThread.java
- private final Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
- ...
- Activity activity = null;
- try {
-
- java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
- activity = mInstrumentation.newActivity(
- cl, component.getClassName(), r.intent);
- }
- ...
- try {
- ...
- if (activity != null) {
-
- ContextImpl appContext = new ContextImpl();
- appContext.init(r.packageInfo, r.token, this);
- appContext.setOuterContext(activity);
- CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
- ...
- activity.attach(appContext, this, getInstrumentation(), r.token,
- r.ident, app, r.intent, r.activityInfo, title, r.parent,
- r.embeddedID, r.lastNonConfigurationInstance,
- r.lastNonConfigurationChildInstances, config);
- ...
-
-
- int theme = r.activityInfo.getThemeResource();
- if (theme != 0) {
- activity.setTheme(theme);
- }
- ...
- }
- }
- ...
- return activity;
- }
總結: 若是沒有爲設置Theme Resource ,則會選取默認的Theme Style,不然選用咱們設置的Theme。
由於mTheme對象是相對統一的,只不過每次都經過apply一個新的Style ID,感受Android 框架會爲每一個
應用程序的資源造成一個統一的資源庫,應用程序定義的全部Style都存在在該資源庫中,能夠經過經過Style
ID值顯示獲取對應值集合。 但因爲對系統獲取資源的過程不瞭解,目前還不清楚Android中是如何根據資源ID
獲取對應的資源甚至一組資源的。但可喜的是,老羅目前正在研究這塊,但願能在老羅的文章中找到答案。
具體可見 <
>
另外,Dialog的構造函數也有必定啓發性,建立了一個指定Theme 的ContextThemeWapper對象,而後經過它創
建對應的Window對象。 具體過程能夠自行研究下。
- public Dialog(Context context, int theme) {
-
- mContext = new ContextThemeWrapper(
- context, theme == 0 ? com.android.internal.R.style.Theme_Dialog : theme);
- mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
-
- Window w = PolicyManager.makeNewWindow(mContext);
- ...
- }
PS : Android 4.0 以後默認的屬性爲Theme_Holo,呵呵,Holo倒挺有意思的。調用相關函數時,會判斷SDK
版本,而後選取相應地Theme。相關函數以下: @ Resources.java
- public static int selectDefaultTheme(int curTheme, int targetSdkVersion) {
- return selectSystemTheme(curTheme, targetSdkVersion,
- com.android.internal.R.style.Theme,
- com.android.internal.R.style.Theme_Holo,
- com.android.internal.R.style.Theme_DeviceDefault);
- }
- public static int selectSystemTheme(int curTheme, int targetSdkVersion,
- int orig, int holo, int deviceDefault) {
-
- if (curTheme != 0) {
- return curTheme;
- }
-
- if (targetSdkVersion < Build.VERSION_CODES.HONEYCOMB) {
- return orig;
- }
- if (targetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
- return holo;
- }
- return deviceDefault;
- }
Step 3、經過前面的分析,咱們獲取了Window屬性對應的TypeArray對象,接下來就是獲取對應的屬性值。
以下代碼所示:
-
- protected ViewGroup generateLayout(DecorView decor) {
-
- TypedArray a = getWindowStyle();
-
-
- mIsFloating = a.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false);
- int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
- & (~getForcedWindowFlags());
-
-
- if (mIsFloating) {
- setLayout(WRAP_CONTENT, WRAP_CONTENT);
- setFlags(0, flagsToUpdate);
- } else {
- setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
- }
-
- if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) {
- requestFeature(FEATURE_NO_TITLE);
- }
-
- if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) {
- setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN&(~getForcedWindowFlags()));
- }
-
- if (a.getBoolean(com.android.internal.R.styleable.Window_windowShowWallpaper, false)) {
- setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags()));
- }
- WindowManager.LayoutParams params = getAttributes();
- if (!hasSoftInputMode()) {
- params.softInputMode = a.getInt(
- com.android.internal.R.styleable.Window_windowSoftInputMode,
- params.softInputMode);
- }
-
- if (getContainer() == null) {
- if (mBackgroundDrawable == null) {
- if (mBackgroundResource == 0) {
- mBackgroundResource = a.getResourceId(
- com.android.internal.R.styleable.Window_windowBackground, 0);
- }
- }
- ...
- }
-
- int layoutResource;
- int features = getLocalFeatures();
- if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
-
- if (mIsFloating) {
- layoutResource = com.android.internal.R.layout.dialog_title_icons;
- } else {
- layoutResource = com.android.internal.R.layout.screen_title_icons;
- }
- }
- else if {
- else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
-
-
-
- if (mIsFloating) {
- layoutResource = com.android.internal.R.layout.dialog_title;
- } else {
- layoutResource = com.android.internal.R.layout.screen_title;
- }
-
- } else {
- layoutResource = com.android.internal.R.layout.screen_simple;
- }
- View in = mLayoutInflater.inflate(layoutResource, null);
- decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
- ...
- if (getContainer() == null) {
- Drawable drawable = mBackgroundDrawable;
- if (mBackgroundResource != 0) {
- drawable = getContext().getResources().getDrawable(mBackgroundResource);
- }
- mDecor.setWindowBackground(drawable);
- drawable = null;
-
- if (mFrameResource != 0) {
- drawable = getContext().getResources().getDrawable(mFrameResource);
- }
- mDecor.setWindowFrame(drawable);
- }
- return contentParent;
- }
依次取出對應的屬性值,而後根據這些值調用不一樣的函數,例如:requestFeature(),以及爲
WindowMamager.LayoutParams設置Flag標記(這個掩碼實現按位操做倒挺麻煩的,部分理解,有知道的朋友
能夠給我指點下。)例如:若是是對話框窗口,則不設置FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR
位標記,由於該標記表明對應的是Activity窗口。
一、 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR 表明的是典型的Activity窗口
二、 FLAG_LAYOUT_IN_SCREEN 表明的是典型的全屏窗口
三、 其餘則表明其餘對話框窗口。
最後,根據相應的Feature值,加載不一樣的佈局文件。
PS : 前些日子在論壇中看到有網友說取消/隱藏ActionBar,Android 4.0中對一個支持ActionBar的資源文件
定義以下: 文件路徑 frameworks\base\core\res\res\layout\screen_action_bar.xml
- <!--
- This is an optimized layout for a screen with the Action Bar enabled.
- -->
-
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:fitsSystemWindows="true">
- <!-- Action Bar 對應的 View文件 -->
- <com.android.internal.widget.ActionBarContainer android:id="@+id/action_bar_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="?android:attr/actionBarStyle">
- <com.android.internal.widget.ActionBarView
- android:id="@+id/action_bar"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="?android:attr/actionBarStyle" />
- <com.android.internal.widget.ActionBarContextView
- android:id="@+id/action_context_bar"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:visibility="gone"
- style="?android:attr/actionModeStyle" />
- </com.android.internal.widget.ActionBarContainer>
- <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" />
- <com.android.internal.widget.ActionBarContainer android:id="@+id/split_action_bar"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="?android:attr/actionBarSplitStyle"
- android:visibility="gone"
- android:gravity="center"/>
- </LinearLayout>
具體分析依舊見於generateLayout @ PhoneWindow.java , 4.0 的源碼咯。直接設置爲gone狀態不知可行否?
轉載請註明出處:http://blog.csdn.net/qinjuning