前言
這三個概念貫穿Android框架的方方面面,是Android程序設(shè)計中很重要的一環(huán),理解它們,并能學(xué)以致用,不但可以讓你的代碼變得簡潔明了,還可以讓你的應(yīng)用更加靈活。但目前網(wǎng)上資料對這塊介紹的知識點往往比較散,不是很系統(tǒng)全面,在此總結(jié)此文一篇。
參考資料:https://juejin.im/entry/5767b3c12e958a00698a8eba
概念說明
Attr:屬性,風(fēng)格樣式的最小單元;
Style:風(fēng)格,它是一系列Attr的集合用以定義一個View的樣式,比如height、width、padding等;
Theme:主題,它與Style作用一樣,不同于Style作用于個一個單獨View,而它是作用于Activity上或是整個應(yīng)用。
Attr的定義
我們先舉一個框架中的源碼例子,用來介紹下Android中是如何定義一個Attr的,比如以下創(chuàng)建一個簡單的TextView布局
TextView
其中l(wèi)ayout_width對應(yīng)到框架中的attr信息如下:
<declare-styleable name="ViewGroup_Layout">
<attr name="layout_width" format="dimension">
<enum name="fill_parent" value="-1" />
<enum name="match_parent" value="-1" />
<enum name="wrap_content" value="-2" />
</attr>
...
</declare-styleable>
從上可以看到layout_width可以使用三個枚舉值,并且其中fill_parent和match_parent的value值都為-1。做過Android開發(fā)的童鞋肯定知道,從2.2開始Android框架就推薦用match_parent代替fill_parent,而以上代碼正實現(xiàn)了兼容,因為它們對應(yīng)的值都為-1。
以上的textStyle的屬性信息在源碼中如下:
<attr name="textStyle">
<flag name="normal" value="0" />
<flag name="bold" value="1" />
<flag name="italic" value="2" />
</attr>
它也對應(yīng)了三個值,但這里卻使用了flag標(biāo)簽。細(xì)心的童鞋可能已經(jīng)明白了flag與enum的差別,flag表示這幾個值可以做或運算,比如上面的textStyle,你可以疊加使用,如用bold|italic表示既加粗也變成斜體,而enum只能讓你選擇其中一個值。
看完上例后,我們來試著自己自定義一個自己的屬性,在values目錄下創(chuàng)建一個attrs.xml文件,在<resources>元素里面首先申明一個自己的<declare-styleable>表示一個屬性組,再在里面加上屬性就行。如下我們定義一個DogStyle的屬性組,其中有三個屬性一個是dogSex,一個是dogName,dogName的格式我們設(shè)置為string,最后一個是dogColor,這樣一個屬于我們自己的屬性就定義成功了。
DogStyle attr的format根據(jù)字面意思也挺容易理解的,這里我解釋下reference的用法。它用在一些可以設(shè)置引用值的情況,比如@drawable/myImage、@color/myColor等。當(dāng)然format也可以進(jìn)行或運算,一般我們定義color類型的屬性時,也一般會把format寫成format="reference|color"
TIPS:format即使用錯,只要你自定義的View中獲取對應(yīng)類型值也是可以的,只是在布局中寫代碼時,IDE就不會根據(jù)你定義的format給出相應(yīng)的提示了,所以最好在自定義View時還是仔細(xì)斟酌下類型。
Style的使用
如下我們在styles.xml中定義一個雪納瑞風(fēng)格
<style name="SchnauzerStyle">
<item name="dogName">雪納瑞</item>
<item name="dogColor">@drawable/schnauzer</item>
<item name="dogSex">boy</item>
</style>
下面我們看下如何讓一個Style作用在一個View上的。
首先我們自定義了一個View命名為DogView,然后創(chuàng)建一個布局文件中加入該DogView視圖,并讓該View使用SchnauzerStyle風(fēng)格。代碼如下:
<cn.hadcn.test.DogView
style="@style/SchnauzerStyle"
android:layout_height="wrap_content"
android:layout_width="wrap_content"/>
移步到DogView的Java代碼中,我們可以通過theme的obtainStyledAttributes方法來獲得我們剛剛定義的幾個Attr屬性在Style中的內(nèi)容,如下我們舉一個獲得dogName的例子:
final Resources.Theme theme = context.getTheme();
TypedArray dogArray = theme.obtainStyledAttributes(attrs, R.styleable.DogStyle, defStyleAttr, defStyleRes);
String name = dogArray.getString(R.styleable.DogStyle_dogName);
Log.e("dog", "name = " + name);
dogArray.recycle();
以上obtainStyledAttributes有四個入?yún)?,前兩個比較容易理解,后兩個用作指定默認(rèn)的Style,表示如果attrs中沒有你想獲得的屬性,但如果你指定了默認(rèn)Style,它會去從該默認(rèn)的Style里面找你想要的屬性。defStyleAttr和defStyleRes功能一樣,指定的資源形式不同,前者表示一個默認(rèn)的指向一個style風(fēng)格的attr屬性,而后者你可以直接傳入一個style風(fēng)格的id。注意以上定義的Style只能在這個DogView中被使用,如果你想在其他View使用,就需要再在需要使用的View中增加這個Style。這就是先前我們說的Style只能作用于一個View。
Theme的使用
Theme與Style使用同一個元素標(biāo)簽區(qū)別在于所包含的屬性不同,并且使用的地方也不一樣。Theme你需要設(shè)置到AndroidManifest.xml的<application>或者<activity>標(biāo)簽下,設(shè)置后,被設(shè)置的Activity或整個應(yīng)用下所有的View都可以使用《style》里面的屬性了。
比如在上例中,我們直接把SchnauzerStyle設(shè)置到<activity>標(biāo)簽中,并把布局文件中DogView元素的style="@style/SchnauzerStyle"欄位刪除,以此來測試下,這個Activity下的所有View是不是可以直接使用theme中聲明的這些屬性。
<activity
android:name=".MainActivity"
android:theme="@style/SchnauzerStyle">
...
以上理論上是可行的,不過運行后,程序卻出現(xiàn)奔潰,出現(xiàn)以下錯誤提示:
java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.
有些同學(xué)一眼可能就看出,因為在這里Activity或Application的需要很多屬性才能工作的,而此處我們只給它傳一個SchnauzerStyle,這當(dāng)然不行,所以我們需要對這個Style做下處理,讓SchnauzerStyle繼承一個系統(tǒng)主題,如下:
<style name="SchnauzerStyle" parent="Theme.AppCompat">
<item name="dogName">雪納瑞</item>
<item name="dogColor">@drawable/schnauzer</item>
<item name="dogSex">boy</item>
</style>
這樣一個雪納瑞主題就誕生了,而在這個Activity下的所有View都可以用雪納瑞的信息了。Application中定義theme的原理一樣,這里就不多說了。
TIPS:框架使用Attr的順序是:View中的Style會優(yōu)先于Activity中的Theme,Activity中的Theme會優(yōu)先于Application中的Theme,所以說你可以定義整個應(yīng)用的總體風(fēng)格,但局部風(fēng)格你也可以做出自己的調(diào)整。
Attr的獲得方法
有些情況下,我們可能需要使用theme中的屬性值,比如下面我們想讓一個TextView直接顯示dogName這個屬性的內(nèi)容,并且使用系統(tǒng)字體的顏色,則可以如下做:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:textColorSecondary"
android:text="?attr/dogName"/>
獲得一個Attr的方法,不同于普通資源使用@符號獲得的方式,而是需要使用?符號來獲得屬性,整體的表達(dá)方式如下:
?[<package_name>:][<resource_type>/]<resource_name>
如果是本應(yīng)用中的attr使用,則可以省去<package_name>部分。
此處的textColor使用當(dāng)前主題的android:textColorSecondary屬性內(nèi)容。因為資源工具知道此處是一個屬性,所以省去了attr (完整寫法:?android:attr/textColorSecondary)。