android布局界面theme,Andriod中Style/Theme原理以及Activity界面文件选取过程浅析

通过对前面的一篇博文的学习,我们掌握了Activity组件布局文件地创建过程以及

其顶层控件DecorView,今天我们继续庖丁解牛---深入到其中的generateLayout()方法,步步为营掌握一下内容:

1、Activity中Theme(主题)的系统定义以及使用之处;

2、如何根据设置的Feature(属性)选择合适的布局文件。

另外,对于下文中Theme和Style的概念进行一个简要说明:

都是由

我们看看Android另外一个Theme.NoTitleBar属性定义,默认继承了"Theme"集合。

true

其实xml文件中声明的任何元素(包括属性),必须通过代码去获取他们的值,然后进行适当地逻辑运算。那么

系统是在什么地方去解析这些Window属性,并且选择合适地布局文件?

二、Theme主题的解析以及布局文件的选取

如果对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=(1Notethatsomeflagsmustbesetbeforethewindowdecorationis

*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()方法。文件路径:frameworksbasecorejavaandroidappActivityThread.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的资源文件

定义如下: 文件路径 frameworksbasecoreresreslayoutscreen_action_bar.xml

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

具体分析依旧见于generateLayout @ PhoneWindow.java , 4.0 的源码咯。直接设置为gone状态不知可行否?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值