Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Andriod中Style/Theme原理以及Activity界面文件選取過程淺析

Andriod中Style/Theme原理以及Activity界面文件選取過程淺析

編輯:關於Android編程

通過對前面的一篇博文<從setContentView()談起>的學習,我們掌握了Activity組件布局文件地創建過程以及

其頂層控件DecorView,今天我們繼續庖丁解牛---深入到其中的generateLayout()方法,步步為營掌握一下內容:

1、Activity中Theme(主題)的系統定義以及使用之處;

2、如何根據設置的Feature(屬性)選擇合適的布局文件。

另外,對於下文中Theme和Style的概念進行一個簡要說明:

都是由 我們看看Android另外一個Theme.NoTitleBar屬性定義,默認繼承了"Theme"集合。

  true

 

其實xml文件中聲明的任何元素(包括屬性),必須通過代碼去獲取他們的值,然後進行適當地邏輯運算。那麼

系統是在什麼地方去解析這些Window屬性,並且選擇合適地布局文件?

二、Theme主題的解析以及布局文件的選取

如果對setContentView()調用過程不太熟悉的朋友,可以先看看前面一篇博文<從setContentView()談起>。

今天我們深入到其中generateLayout()方法,該方法地主要作用就是解析這些Window屬性,然後選擇合適地

布局文件作為我們地Activity或者Window界面地承載布局文件,即DecorView的直接子View。

 

在進行具體分析之前,Android還提供了另外兩種簡單API讓我們制定界面的風格,如下兩個方法:

1、requestFeature() 設定個該界面的風格Feature,例如,FEATURE_NO_TITLE(沒有標題) 、

FEATURE_PROGRESS(標題欄帶進度條)。必須在setContentView()前調用,否則會報異常。

FEATURE屬性定義在Window.java類

2、getWindow().setFlags(),為當前的WindowManager.LayoutParams添加一些Flag。

Flag標記定義在WindowManager.LayoutParams.java類。


通過這兩種方法隱藏狀態欄和標題欄的例子為:

 

@Override publicvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); //hidetitlebarofapplication //mustbebeforesettingthelayout requestWindowFeature(Window.FEATURE_NO_TITLE); //hidestatusbarofAndroid //couldalsobedonelater getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(R.layout.main); } 完整例子,可見於博文

源碼分析:

這兩個方法是在Window.java類實現的,如下:

publicclassWindow{ /**Flagforthe"optionspanel"feature.Thisisenabledbydefault.*/ publicstaticfinalintFEATURE_OPTIONS_PANEL=0; /**Flagforthe"notitle"feature,turningoffthetitleatthetop *ofthescreen.*/ publicstaticfinalintFEATURE_NO_TITLE=1; /**Flagfortheprogressindicatorfeature*/ publicstaticfinalintFEATURE_PROGRESS=2; /**Flagforhavinganiconontheleftsideofthetitlebar*/ publicstaticfinalintFEATURE_LEFT_ICON=3; /**Flagforhavinganiconontherightsideofthetitlebar*/ publicstaticfinalintFEATURE_RIGHT_ICON=4; /**Flagforindeterminateprogress*/ publicstaticfinalintFEATURE_INDETERMINATE_PROGRESS=5; /**Flagforthecontextmenu.Thisisenabledbydefault.*/ publicstaticfinalintFEATURE_CONTEXT_MENU=6;//菜單 /**Flagforcustomtitle.Youcannotcombinethisfeaturewithothertitlefeatures.*/ publicstaticfinalintFEATURE_CUSTOM_TITLE=7;   //默認的FEATURESFEATURE_OPTIONS_PANEL&FEATURE_CONTEXT_MENU protectedstaticfinalintDEFAULT_FEATURES=(1<Notethatsomeflagsmustbesetbeforethewindowdecorationis

 

*created. *Thesewillbesetforyoubasedonthe{@linkandroid.R.attr#windowIsFloating} *attribute. *@paramflagsThenewwindowflags(seeWindowManager.LayoutParams). *@parammaskWhichofthewindowflagbitstomodify. */ //mask代表對應為的掩碼,設置對應位時,需要先清空對應位的掩碼,然後在進行或操作。類似的函數可以見於View.java類的setFlags()方法 publicvoidsetFlags(intflags,intmask){ finalWindowManager.LayoutParamsattrs=getAttributes();//當前的WindowManager.LayoutParams屬性 //將設置的flags添加至attrs屬性中 attrs.flags=(attrs.flags&~mask)|(flags&mask); mForcedWindowFlags|=mask; if(mCallback!=null){//Activity和Dialog默認實現了Window.Callback接口 mCallback.onWindowAttributesChanged(attrs);//回調onWindowAttributesChanged()方法 } } ... }

 

其實也挺簡單的,主要是邏輯運算符的操作。
mFeatures 代表了當前Window的Feature值.
flags 保存在當前WindowManager.LayoutParams.flag屬性中。

 

接下來具體分析generateLayout()方法.

如果當前界面的DecorView對象為空(一般由setContentView()或者addContentView()調用),則會創建一個

DecorView對象以及對應的裝載xml布局的mContentParent對象。

Step 1、創建DecorView對象

privatevoidinstallDecor(){ if(mDecor==null){ mDecor=generateDecor();//創建一個DecorView對象 mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);//設置焦點捕獲動作 }   if(mContentParent==null){//mContentParent作為我們自定義布局的Parent. mContentParent=generateLayout(mDecor);//創建mContentParent。 ... } }
Step 2、創建mContentParent對象

 

 

protectedViewGroupgenerateLayout(DecorViewdecor){ //Applydatafromcurrenttheme. TypedArraya=getWindowStyle();//獲得當前的Theme屬性對應的TypedArray對象.   //接下來都是對Attribute值的獲取...,後續繼續分析 //是否是Dialog樣式的界面,android:windowIsFloating屬性 mIsFloating=a.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating,false); ... }

 

首先獲取系統自定義的Style對應的TypeArray對象,然後獲取對應的屬性值。我們繼續分析getWindowStyle()

方法。

2.1、

/** *Returnthe{@linkandroid.R.styleable#Window}attributesfromthis *window'stheme. */ publicfinalTypedArraygetWindowStyle(){ synchronized(this){ if(mWindowStyle==null){ //調用Context類的相應方法,返回對應的TypedArray對象,參數為自定義屬性集合 mWindowStyle=mContext.obtainStyledAttributes(com.android.internal.R.styleable.Window); } returnmWindowStyle; } } 調用Context類對應地obtainStyledAttributes()方法,參數傳遞的是 Window對應的自定義屬性集合。

2.2、

/** *RetrievestyledattributeinformationinthisContext'stheme.See *{@linkResources.Theme#obtainStyledAttributes(int[])} *formoreinformation. * *@seeResources.Theme#obtainStyledAttributes(int[]) */ publicfinalTypedArrayobtainStyledAttributes( int[]attrs){ //首先獲取當前Theme對應的TypedArray對象 returngetTheme().obtainStyledAttributes(attrs); } 由於Activity繼承至ContextThemeWapprer類,ContextThemeWapprer重寫了getTheme()方法。

2.3

 

@Override publicResources.ThemegetTheme(){ if(mTheme!=null){//第一次訪問時,mTheme對象為null returnmTheme; } //Theme資源是否已經指定,沒有選取默認Theme if(mThemeResource==0){ mThemeResource=com.android.internal.R.style.Theme; } initializeTheme();//初始化Theme資源 returnmTheme; } 首先,判斷是否是第一次調用該方法,即是否創建了mTheme對象;

 

其次,判斷是否設置了該Theme所對應的資源ID,如果沒有,則選取默認的theme style

即com.android.internal.R.style.Theme 。

最後,初始化對應資源。

 

/** *Calledby{@link#setTheme}and{@link#getTheme}toapplyatheme *resourcetothecurrentThemeobject.Canoverridetochangethe *default(simple)behavior.Thismethodwillnotbecalledinmultiple *threadssimultaneously. * *@paramthemeTheThemeobjectbeingmodified. *@paramresidThethemestyleresourcebeingappliedtotheme. *@paramfirstSettotrueifthisisthefirsttimeastyleisbeing *appliedtotheme. */ protectedvoidonApplyThemeResource(Resources.Themetheme,intresid,booleanfirst){ theme.applyStyle(resid,true); }   privatevoidinitializeTheme(){ finalbooleanfirst=mTheme==null;//是否是第一次調用 if(first){ mTheme=getResources().newTheme(); Resources.Themetheme=mBase.getTheme();//調用ContextImpl類的getTheme(),獲取默認的Theme if(theme!=null){ mTheme.setTo(theme);//將theme配置應用到mTheme屬性中 } } onApplyThemeResource(mTheme,mThemeResource,first); }

 

如果沒有手動設置mThemeResource,則選取系統中為我們提供的默認Theme。當然我們也可以手動設置Theme

Resource ,如開篇所述。

方法一:Activity中調用setTheme()方法,該方法會實現在ContextThemeWrapper.java類中。

 

@Override publicvoidsetTheme(intresid){ mThemeResource=resid;//設置mThemeResource initializeTheme(); }

 

方法二:在AndroidManifest文件中,為Activity節點配置android:theme屬性. 當通過startActivity()啟動一個

Activity時,會調用setTheme()方法。文件路徑:frameworks\base\core\java\android\app\ActivityThread.java

 

privatefinalActivityperformLaunchActivity(ActivityClientRecordr,IntentcustomIntent){ ... Activityactivity=null; try{ //創建Activity實例 java.lang.ClassLoadercl=r.packageInfo.getClassLoader(); activity=mInstrumentation.newActivity( cl,component.getClassName(),r.intent); } ... try{ ... if(activity!=null){ //創建相應的信息. ContextImplappContext=newContextImpl(); appContext.init(r.packageInfo,r.token,this); appContext.setOuterContext(activity); CharSequencetitle=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); ... //activityInfo相關信息是由ActivityManagerService通過IPC調用而來 //可以參考AndroidSDK的ActivityInfo類API。 inttheme=r.activityInfo.getThemeResource(); if(theme!=0){ activity.setTheme(theme);//調用setTheme()方法,參見方法1 } ... } } ... returnactivity; }
總結: 如果沒有為設置Theme Resource ,則會選取默認的Theme Style,否則選用我們設置的Theme。

 

因為mTheme對象是相對統一的,只不過每次都通過apply一個新的Style ID,感覺Android 框架會為每個

應用程序的資源形成一個統一的資源庫,應用程序定義的所有Style都存在在該資源庫中,可以通過通過Style

ID值顯示獲取對應值集合。但由於對系統獲取資源的過程不了解,目前還不清楚Android中是如何根據資源ID

獲取對應的資源甚至一組資源的。但可喜的是,老羅目前正在研究這塊,希望能在老羅的文章中找到答案。

具體可見 <

Android資源管理框架(Asset Manager)簡要介紹和學習計劃

>

另外,Dialog的構造函數也有一定啟發性,創建了一個指定Theme 的ContextThemeWapper對象,然後通過它創

建對應的Window對象。 具體過程可以自行研究下。

publicDialog(Contextcontext,inttheme){ //創建一個ContextThemeWrapper對象,指定ThemeID mContext=newContextThemeWrapper( context,theme==0?com.android.internal.R.style.Theme_Dialog:theme); mWindowManager=(WindowManager)context.getSystemService(Context.WINDOW_SERVICE); //傳遞該ContextThemeWrapper對象,構造指定的ID. Windoww=PolicyManager.makeNewWindow(mContext); ... }

PS : Android 4.0 之後默認的屬性為Theme_Holo,呵呵,Holo倒挺有意思的。調用相關函數時,會判斷SDK

版本,然後選取相應地Theme。相關函數如下: @Resources.java

 

/**@hide*/ publicstaticintselectDefaultTheme(intcurTheme,inttargetSdkVersion){ returnselectSystemTheme(curTheme,targetSdkVersion, com.android.internal.R.style.Theme, com.android.internal.R.style.Theme_Holo, com.android.internal.R.style.Theme_DeviceDefault); } /**@hide*/ publicstaticintselectSystemTheme(intcurTheme,inttargetSdkVersion, intorig,intholo,intdeviceDefault){ //是否設置了Theme if(curTheme!=0){ returncurTheme; } //判斷版本號,HONEYCOMB代表Android3.0,ICE_CREAM_SANDWICH代表Android4.0 if(targetSdkVersion

 

 

Step 3、通過前面的分析,我們獲取了Window屬性對應的TypeArray對象,接下來就是獲取對應的屬性值。

如下代碼所示:

 

  protectedViewGroupgenerateLayout(DecorViewdecor){ //Applydatafromcurrenttheme. TypedArraya=getWindowStyle();//獲得當前的Theme屬性對應的TypedArray對象.   //是否是Dialog樣式的界面,android:windowIsFloating屬性 mIsFloating=a.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating,false); intflagsToUpdate=(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR) &(~getForcedWindowFlags()); //如果是Dialog樣式,則設置當前的WindowManager.LayoutParams的width和height值,代表該界面的大小由布局文件大小指定。 //因為默認的WindowManager.LayoutParams的width和height是MATCH_PARENT,即與屏幕大小一致. if(mIsFloating){ setLayout(WRAP_CONTENT,WRAP_CONTENT); setFlags(0,flagsToUpdate);//取消FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR位標記 }else{ setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR,flagsToUpdate); } //是否是沒有標題欄,android:windowNoTitle屬性 if(a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle,false)){ requestFeature(FEATURE_NO_TITLE);//添加FEATURE_NO_TITLE } //是否是全屏,android:windowFullscreen屬性 if(a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen,false)){ setFlags(FLAG_FULLSCREEN,FLAG_FULLSCREEN&(~getForcedWindowFlags())); } //是否是顯示牆紙,android:windowShowWallpaper屬性 if(a.getBoolean(com.android.internal.R.styleable.Window_windowShowWallpaper,false)){ setFlags(FLAG_SHOW_WALLPAPER,FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags())); } WindowManager.LayoutParamsparams=getAttributes();//當前的WindowManager.LayoutParams對象 if(!hasSoftInputMode()){//是否已經設置了softInputMode模式,可顯示通過#setSoftInputMode()方法設定 params.softInputMode=a.getInt( com.android.internal.R.styleable.Window_windowSoftInputMode,//android:windowSoftInputMode params.softInputMode);//可以由WindowManager.LayoutParams指定 } //是否是某個Activity的子Activity,一般不是,getContainer()返回null. if(getContainer()==null){ if(mBackgroundDrawable==null){//獲得了指定的背景圖片. if(mBackgroundResource==0){//獲得了指定的背景圖片資源 mBackgroundResource=a.getResourceId(//背景圖片id,android:windowBackground com.android.internal.R.styleable.Window_windowBackground,0); } } ... } //Inflatethewindowdecor. intlayoutResource; intfeatures=getLocalFeatures();//等同於mFeatures,由requestFeature()設定. if((features&((1<

 

依次取出對應的屬性值,然後根據這些值調用不同的函數,例如:requestFeature(),以及為

WindowMamager.LayoutParams設置Flag標記(這個掩碼實現按位操作倒挺麻煩的,部分理解,有知道的朋友

可以給我指點下。)例如:如果是對話框窗口,則不設置FLAG_LAYOUT_IN_SCREEN |FLAG_LAYOUT_INSET_DECOR

位標記,因為該標記代表對應的是Activity窗口。

1、 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR 代表的是典型的Activity窗口
2、 FLAG_LAYOUT_IN_SCREEN 代表的是典型的全屏窗口

3、 其他則代表其他對話框窗口。

最後,根據相應的Feature值,加載不同的布局文件。

 

PS: 前些日子在論壇中看到有網友說取消/隱藏ActionBar,Android 4.0中對一個支持ActionBar的資源文件

定義如下: 文件路徑 frameworks\base\core\res\res\layout\screen_action_bar.xml

 

    android:fitsSystemWindows="true">   android:layout_height="wrap_content" style="?android:attr/actionBarStyle">  

具體分析依舊見於generateLayout @ PhoneWindow.java , 4.0 的源碼咯。直接設置為gone狀態不知可行否?

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved