Android的Styles(樣式)和Themes(主題)非常類似Web開發裏的CSS,方便開發者將頁面內容和佈局呈現分開。Style和Theme在Android裏的定義方式是完全一樣的,兩者只是概念上的區別:Style作用在單個視圖或控件上,而Theme用於Activity或整個應用程序。由於作用範圍的不同,Theme也就需要比Style包含更多的定義屬性值的項目(item)。不過本文,我將Style和Theme都歸爲Style來稱呼。
Android的Style和Web的CSS相比,有一個缺陷就是隻能針對一個對象只能通過android:theme="@style/AppTheme"
或style="@style/MyStyle"
指定一個值。而CSS則可以通過class
屬性在DOM元素上定義多個樣式來達到組合的效果。不過Style也有CSS沒有的功能,那就是繼承(Inheritance)。(當然CSS通過LESS和SASS這些工具也獲得繼承的能力。)
Style繼承簡介
根據Android Developers官方文檔的介紹,定義Style的繼承有兩種方式:一是通過parent
標誌父Style;
1
2
3
|
<style
name="GreenText"
parent="@android:style/TextAppearance">
<item name="android:textColor">#00FF00</item>
</style>
|
另一種則是將父Style的名字作爲前綴,然後通過“.”連接新定義Style的名字:
1
2
3
|
<style
name="CodeFont.Red">
<item name="android:textColor">#FF0000</item>
</style>
|
第二種方式可以無限連接子Style來實踐多層繼承:
1
2
3
|
<style
name="CodeFont.Red.Big">
<item name="android:textSize">30sp</item>
</style>
|
相對第一種,Android對第二種方式做出的限制就是Style必須是由自己定義的,或者說父Style和子Style必須是定義在同一個程序內,不能是引用第三方或系統的Style。畢竟對於系統的Style的引用是需要加上android:
前綴作爲命名空間。
其次在使用Style時,對於第二種方式定義的Style,必須引用其完全的名字,也就是說必須要包含完整的前綴和名字:
1
2
3
|
<EditText
style="@style/CodeFont.Red.Big"
...
/>
|
Android對於第一種定義方式並沒用限制,所以所有以第二種方式定義的Style都可以轉用第一種:
1
2
3
|
<style
name="Big"
parent="CodeFont.Red">
<item name="android:textSize">30sp</item>
</style>
|
只要parent
中的名字對應上實際定義的Style名字即可。不過換成第一種後Style的名字如果太簡潔就容易衝突了。
兩種繼承方式混合的效果
前面說到Style的兩種繼承方式的效果是一致的,那假如將兩種方式混在一起定義一個Style又會是什麼樣的效果呢?下邊就用實際例子來分析一下。
首先定義一些實驗所需的自定義屬性(attr),(這樣可以減少系統屬性的干擾,因爲系統總是會爲它的屬性定義值,那樣可能無法分辨最後的效果是來自系統還是定義的值)
1
2
3
4
5
6
7
8
|
<?xml
version="1.0"
encoding="utf-8"?>
<resources>
<declare-styleable
name="CustomStyle">
<attr
name="customColor"
format="color"/>
<attr
name="customText"
format="string"/>
<attr
name="customSize"
format="dimension"/>
</declare-styleable>
</resources>
|
接着定義一個TextView的子類,並在其中獲取上邊自定義屬性的值並賦予TextView去呈現:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
import
android.util.TypedValue;
import android.widget.TextView;
/**
* @author Ider
*/
public
class StyledTextView
extends TextView
{
public
StyledTextView(Context
context)
{
this(context,
null);
}
public
StyledTextView(Context
context,
AttributeSet attrs)
{
this(context,
attrs,
0);
}
public
StyledTextView(Context
context,
AttributeSet attrs,
int defStyleAttr)
{
super(context,
attrs,
defStyleAttr);
final
TypedArray a
= context.getTheme()
.obtainStyledAttributes(attrs,
R.styleable.CustomStyle,
defStyleAttr,
0);
final
CharSequence text
= a.getText(R.styleable.CustomStyle_customText);
final
int color
= a.getColor(R.styleable.CustomStyle_customColor,
Color.RED);
final
float size
= a.getDimensionPixelSize(R.styleable.CustomStyle_customSize,
70);
a.recycle();
setText(text);
setTextColor(color);
setTextSize(TypedValue.COMPLEX_UNIT_PX,
size);
}
}
|
然後就是定義研究所需的Style
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<resources>
<style name="SuperStyleOne">
<item
name="customColor">@android:color/holo_orange_dark</item>
<item name="customText">Hello
World</item>
<item
name="customSize">30dp</item>
</style>
<style name="SuperStyleTwo">
<item
name="customText">www.iderzheng.com</item>
</style>
<style name="SuperStyleOne.SubOne">
<item
name="customColor">@android:color/holo_blue_dark</item>
</style>
<style name="SuperStyleOne.SubTwo"
parent="SuperStyleTwo">
</style>
<style
name="SuperStyleOne.SubThree"
parent="SuperStyleTwo">
<item name="customText">blog.iderzheng.com</item>
</style>
</resources>
|
上邊定義的Style裏,SuperStyleOne
將通過添加前綴的方式作用到子Style上,而SuperStyleTwo
則通過指定到parent
來其作用。可以看到SubTwo
和SubThree
混合了兩種方式。
最後在Activity的佈局視圖裏使用自定類並設定上不同的Style
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
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=".MainActivity">
<com.ider.trial.styles.StyledTextView
style="@style/SuperStyleOne"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<com.ider.trial.styles.StyledTextView
style="@style/SuperStyleOne.SubOne"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<com.ider.trial.styles.StyledTextView
style="@style/SuperStyleOne.SubTwo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<com.ider.trial.styles.StyledTextView
style="@style/SuperStyleOne.SubThree"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
|
運行之後得到效果如下:
第一個和第二個都是Style標準的使用方式,也看到它們正確地獲得了定義的屬性值,子Style也正確的繼承和覆蓋了父Style的屬性值。
對於第三個和第四個,它們呈現的顏色是代碼中使用的默認紅色(Color.RED
),字體的值也是源自代碼中的使用值,所以明顯比前兩者要小。這也就是說它們並沒用繼承下SuperStyleOne
中定義的字體大小和顏色。但是SuperStyleTwo
中定義的內容被第三個正確的顯示了出來,也說明SubTwo
成功繼承通過parent
指定的父Style的內容。而第四個呈現出來內容則說明覆蓋的效果也是正確的。
在做這個試驗之前,我一直以爲兩種方式會同時其作用,只是用parent指定比用前綴有高優先級。也就是說Android會先從當前Style定義中找某個屬性的值,如果沒有找到就轉到parent指定的父Style中找,還沒有則轉到前綴指定的父Style中找。但是通過上邊的結果表明:當使用parent
指定父Style後,前綴方式則不在其作用,只是作爲Style的名字。也就是說:Android的Style不支持多繼承。Style的繼承只能單線一層層下來。
反過來在看看系統定義的Style也更容易懂了,比如打開themes_holo.xml,會看到很多一樣的內容被”冗餘”地定義在Theme.Holo
和Theme.Holo.Light
兩個Style下。但因爲Theme.Holo.Light
用parent指定了其父Style是Theme.Light
,所以Theme.Holo.Light
並沒有從Theme.Holo繼承任何屬性值,也因此這樣的冗餘是必須的。
1
2
3
4
5
|
<style
name="Theme.Holo.Light"
parent="Theme.Light">
...
... ...
...
</style>
|
使用Theme.Holo.Light
作爲Style的名字只是爲了名字更加的清晰明瞭。
References: