1. 程式人生 > >Style在Android中的繼承關係

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
  1. <style name = "GreenText" parent = "@android:style/TextAppearance" >
  2. <item name = "android:textColor" > #00FF00 </item >
  3. </style >

另一種則是將父Style的名字作為字首,然後通過“.”連線新定義Style的名字:

Source code
  1. <style name = "CodeFont.Red" >
  2. <item name = "android:textColor" > #FF0000 </item >
  3. </style >

第二種方式可以無限連線子Style來實踐多層繼承:

Source code
  1. <style name = "CodeFont.Red.Big" >
  2. <item name = "android:textSize" > 30sp </item >
  3. </style >

相對第一種,Android對第二種方式做出的限制就是Style必須是由自己定義的,或者說父Style和子Style必須是定義在同一個程式內,不能是引用第三方或系統的Style。畢竟對於系統的Style的引用是需要加上 android: 字首作為名稱空間。

其次在使用Style時,對於第二種方式定義的Style,必須引用其完全的名字,也就是說必須要包含完整的字首和名字:

Source code
  1. <EditText
  2. style = "@style/CodeFont.Red.Big"
  3. ... />

Android對於第一種定義方式並沒用限制,所以所有以第二種方式定義的Style都可以轉用第一種:

Source code
  1. <style name = "Big" parent = "CodeFont.Red" >
  2. <item name = "android:textSize" > 30sp </item >
  3. </style >

只要 parent 中的名字對應上實際定義的Style名字即可。不過換成第一種後Style的名字如果太簡潔就容易衝突了。

兩種繼承方式混合的效果

前面說到Style的兩種繼承方式的效果是一致的,那假如將兩種方式混在一起定義一個Style又會是什麼樣的效果呢?下邊就用實際例子來分析一下。

首先定義一些實驗所需的自定義屬性(attr),(這樣可以減少系統屬性的干擾,因為系統總是會為它的屬性定義值,那樣可能無法分辨最後的效果是來自系統還是定義的值)

Source code
  1. <?xml version = "1.0" encoding = "utf-8" ?>
  2. <resources >
  3. <declare-styleable name = "CustomStyle" >
  4. <attr name = "customColor" format = "color" />
  5. <attr name = "customText" format = "string" />
  6. <attr name = "customSize" format = "dimension" />
  7. </declare-styleable >
  8. </resources >

接著定義一個TextView的子類,並在其中獲取上邊自定義屬性的值並賦予TextView去呈現:

Source code
  1. package com.ider.trial.styles ;
  2. import android.content.Context ;
  3. import android.content.res.TypedArray ;
  4. import android.graphics.Color ;
  5. import android.util.AttributeSet ;
  6. import android.util.TypedValue ;
  7. import android.widget.TextView ;
  8. /**
  9.  * @author Ider
  10.  */
  11. public class StyledTextView extends TextView {
  12. public StyledTextView ( Context context ) {
  13. this ( context, null ) ;
  14. }
  15. public StyledTextView ( Context context, AttributeSet attrs ) {
  16. this ( context, attrs, 0 ) ;
  17. }
  18. public StyledTextView ( Context context, AttributeSet attrs, int defStyleAttr ) {
  19. super ( context, attrs, defStyleAttr ) ;
  20. final TypedArray a = context. getTheme ( )
  21. . obtainStyledAttributes ( attrs, R. styleable . CustomStyle , defStyleAttr, 0 ) ;
  22. final CharSequence text = a. getText ( R. styleable . CustomStyle_customText ) ;
  23. final int color = a. getColor ( R. styleable . CustomStyle_customColor , Color . RED ) ;
  24. final float size = a. getDimensionPixelSize ( R. styleable . CustomStyle_customSize , 70 ) ;
  25. a. recycle ( ) ;
  26. setText ( text ) ;
  27. setTextColor ( color ) ;
  28. setTextSize ( TypedValue. COMPLEX_UNIT_PX , size ) ;
  29. }
  30. }

然後就是定義研究所需的Style

Source code
  1. <resources >
  2. <style name = "SuperStyleOne" >
  3. <item name = "customColor" > @android:color/holo_orange_dark </item >
  4. <item name = "customText" > Hello World </item >
  5. <item name = "customSize" > 30dp </item >
  6. </style >
  7. <style name = "SuperStyleTwo" >
  8. <item name = "customText" > www.iderzheng.com </item >
  9. </style >
  10. <style name = "SuperStyleOne.SubOne" >
  11. <item name = "customColor" > @android:color/holo_blue_dark </item >
  12. </style >
  13. <style name = "SuperStyleOne.SubTwo" parent = "SuperStyleTwo" >
  14. </style >
  15. <style name = "SuperStyleOne.SubThree" parent = "SuperStyleTwo" >
  16. <item name = "customText" > blog.iderzheng.com </item >
  17. </style >
  18. </resources >

上邊定義的Style裡, SuperStyleOne 將通過新增字首的方式作用到子Style上,而 SuperStyleTwo 則通過指定到 parent 來其作用。可以看到 SubTwo 和 SubThree 混合了兩種方式。

最後在Activity的佈局視圖裡使用自定類並設定上不同的Style

Source code
  1. <LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android"
  2. xmlns:tools = "http://schemas.android.com/tools"
  3. android:orientation = "vertical"
  4. android:layout_width = "match_parent"
  5. android:layout_height = "match_parent"
  6. android:paddingLeft = "@dimen/activity_horizontal_margin"
  7. android:paddingRight = "@dimen/activity_horizontal_margin"
  8. android:paddingTop = "@dimen/activity_vertical_margin"
  9. android:paddingBottom = "@dimen/activity_vertical_margin"
  10. tools:context = ".MainActivity" >
  11. <com.ider.trial.styles.StyledTextView
  12. style = "@style/SuperStyleOne"
  13. android:layout_width = "wrap_content"
  14. android:layout_height = "wrap_content" />
  15. <com.ider.trial.styles.StyledTextView
  16. style = "@style/SuperStyleOne.SubOne"
  17. android:layout_width = "wrap_content"
  18. android:layout_height = "wrap_content" />
  19. <com.ider.trial.styles.StyledTextView
  20. style = "@style/SuperStyleOne.SubTwo"
  21. android:layout_width = "wrap_content"
  22. android:layout_height = "wrap_content" />
  23. <com.ider.trial.styles.StyledTextView
  24. style = "@style/SuperStyleOne.SubThree"
  25. android:layout_width = "wrap_content"
  26. android:layout_height = "wrap_content" />
  27. </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繼承任何屬性值,也因此這樣的冗餘是必須的。

Source code
  1. <style name = "Theme.Holo.Light" parent = "Theme.Light" >
  2. ... ... ... ...
  3. </style >

使用 Theme.Holo.Light 作為Style的名字只是為了名字更加的清晰明瞭。