Style在Android中的繼承關係
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;
Source code |
- <style name = "GreenText" parent = "@android:style/TextAppearance" >
- <item name = "android:textColor" > #00FF00 </item >
- </style >
另一種則是將父Style的名字作為字首,然後通過“.”連線新定義Style的名字:
Source code |
- <style name = "CodeFont.Red" >
- <item name = "android:textColor" > #FF0000 </item >
- </style >
第二種方式可以無限連線子Style來實踐多層繼承:
Source code |
- <style name = "CodeFont.Red.Big" >
- <item name = "android:textSize" > 30sp </item >
- </style >
相對第一種,Android對第二種方式做出的限制就是Style必須是由自己定義的,或者說父Style和子Style必須是定義在同一個程式內,不能是引用第三方或系統的Style。畢竟對於系統的Style的引用是需要加上 android:
字首作為名稱空間。
其次在使用Style時,對於第二種方式定義的Style,必須引用其完全的名字,也就是說必須要包含完整的字首和名字:
Source code |
- <EditText
- style = "@style/CodeFont.Red.Big"
- ... />
Android對於第一種定義方式並沒用限制,所以所有以第二種方式定義的Style都可以轉用第一種:
Source code |
- <style name = "Big" parent = "CodeFont.Red" >
- <item name = "android:textSize" > 30sp </item >
- </style >
只要 parent
中的名字對應上實際定義的Style名字即可。不過換成第一種後Style的名字如果太簡潔就容易衝突了。
兩種繼承方式混合的效果
前面說到Style的兩種繼承方式的效果是一致的,那假如將兩種方式混在一起定義一個Style又會是什麼樣的效果呢?下邊就用實際例子來分析一下。
首先定義一些實驗所需的自定義屬性(attr),(這樣可以減少系統屬性的干擾,因為系統總是會為它的屬性定義值,那樣可能無法分辨最後的效果是來自系統還是定義的值)
Source code |
- <?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去呈現:
Source code |
- package com.ider.trial.styles ;
- import android.content.Context ;
- import android.content.res.TypedArray ;
- import android.graphics.Color ;
- import android.util.AttributeSet ;
- 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
Source code |
- <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
Source code |
- <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
指定父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繼承任何屬性值,也因此這樣的冗餘是必須的。
Source code |
- <style name = "Theme.Holo.Light" parent = "Theme.Light" >
- ... ... ... ...
- </style >
使用 Theme.Holo.Light
作為Style的名字只是為了名字更加的清晰明瞭。