1.1 問(wèn)題
你要讓自己的應(yīng)用程序在所有用戶可能運(yùn)行的Android版本上創(chuàng)建一致的外觀和體驗(yàn)朝蜘,同時(shí)減少維護(hù)這些自定義元素所需的代碼量喊崖。
1.2 解決方案
(API Level1)
可以將定義應(yīng)用程序外觀常見(jiàn)屬性抽象化到XML樣式中兵志。樣式是視圖自定義屬性的集合,如文本大小或背景色,這些屬性應(yīng)該應(yīng)用于應(yīng)用程序內(nèi)的多個(gè)視圖。將這些屬性抽象化到樣式中蹋盆,就可以在單個(gè)位置定義公共的元素,使得代碼更易于更新和維護(hù)硝全。
Android還支持將多個(gè)樣式共同分組到稱為“主題”的全局元素中栖雾。主題被應(yīng)用于整個(gè)上下文(如Activity或應(yīng)用程序),并且定義了應(yīng)適用于該上下文中所有視圖的樣式伟众。在應(yīng)用程序中啟動(dòng)的每一個(gè)Activity都應(yīng)用了一個(gè)主題析藕,即使你沒(méi)有定義任何主題。在此情況下凳厢,改為應(yīng)用默認(rèn)的系統(tǒng)主題账胧。
1.3 實(shí)現(xiàn)機(jī)制
為研究樣式的概念,接下來(lái)創(chuàng)建如圖所示的Activity布局先紫。
從圖中可以看到治泥,此視圖中一些元素的外觀需要定制,使其不同于通過(guò)所應(yīng)用的默認(rèn)系統(tǒng)主題樣式化的常見(jiàn)外觀遮精。一種方法是直接在Activity布局中定義適用于全部視圖的所有屬性居夹。如果這樣做的話,則使用的代碼如下所示:
res/layout/activity_styled.xml
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="22sp"
android:textStyle="bold"
android:text="Select One"/>
<RadioGroup
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioButton
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:minHeight="@dimen/buttonHeight"
android:button="@null"
android:background="@drawable/background_radio"
android:gravity="center"
android:text="One"/>
<RadioButton
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:minHeight="@dimen/buttonHeight"
android:button="@null"
android:background="@drawable/background_radio"
android:gravity="center"
android:text="Two"/>
<RadioButton
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:minHeight="@dimen/buttonHeight"
android:button="@null"
android:background="@drawable/background_radio"
android:gravity="center"
android:text="Three"/>
</RadioGroup>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="22sp"
android:textStyle="bold"
android:text="Select All"/>
<TableRow>
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="@dimen/buttonHeight"
android:minWidth="@dimen/checkboxWidth"
android:button="@null"
android:gravity="center"
android:textStyle="italic"
android:textColor="@color/text_checkbox"
android:text="One"/>
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="@dimen/buttonHeight"
android:minWidth="@dimen/checkboxWidth"
android:button="@null"
android:gravity="center"
android:textStyle="italic"
android:textColor="@color/text_checkbox"
android:text="Two"/>
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="@dimen/buttonHeight"
android:minWidth="@dimen/checkboxWidth"
android:button="@null"
android:gravity="center"
android:textStyle="italic"
android:textColor="@color/text_checkbox"
android:text="Three"/>
</TableRow>
<TableRow>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="@dimen/buttonWidth"
android:background="@drawable/background_button"
android:textColor="@color/accentPink"
android:text="@android:string/ok"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="@dimen/buttonWidth"
android:background="@drawable/background_button"
android:textColor="@color/accentPink"
android:text="@android:string/cancel"/>
</TableRow>
</TableLayout>
在此代碼中本冲,我們突出強(qiáng)調(diào)了每個(gè)視圖中與其他相同類型視圖共用的屬性准脂。這些屬性使按鈕、文本標(biāo)題和可選中的元素具有相同的外觀檬洞。其中有很多重復(fù)出現(xiàn)的代碼意狠,我們可以通過(guò)樣式進(jìn)行簡(jiǎn)化。
首先疮胖,我們需要?jiǎng)?chuàng)建新的資源文件环戈,并且使用<style>標(biāo)記定義每個(gè)屬性組闷板。以下代碼顯示了完整的抽象化代碼:
res/values/styles.xml
<resources>
<!-- 小部件的樣式 -->
<style name="LabelText" parent="android:TextAppearance.Large">
<item name="android:textStyle">bold</item>
</style>
<style name="FormButton" parent="android:Widget.Button">
<item name="android:minWidth">@dimen/buttonWidth</item>
<item name="android:background">@drawable/background_button</item>
<item name="android:textColor">@color/accentPink</item>
</style>
<style name="FormRadioButton" parent="android:Widget.CompoundButton.RadioButton">
<item name="android:minHeight">@dimen/buttonHeight</item>
<item name="android:button">@null</item>
<item name="android:background">@drawable/background_radio</item>
<item name="android:gravity">center</item>
</style>
<style name="FormCheckBox" parent="android:Widget.CompoundButton.CheckBox">
<item name="android:minHeight">@dimen/buttonHeight</item>
<item name="android:minWidth">@dimen/checkboxWidth</item>
<item name="android:button">@null</item>
<item name="android:gravity">center</item>
<item name="android:textStyle">italic</item>
<item name="android:textColor">@color/text_checkbox</item>
</style>
</resources>
<style>組將需要應(yīng)用于每個(gè)視圖類型的公共屬性分組在一起。視圖僅可以接受單個(gè)樣式定義院塞,因此必須在一個(gè)組中聚集用于此視圖的所有屬性遮晚。然而,樣式支持繼承性拦止,這就使我們可以級(jí)聯(lián)每個(gè)樣式的定義县遣,之后再將它們應(yīng)用于視圖。
請(qǐng)注意每個(gè)樣式如何聲明父樣式汹族,父樣式是我們應(yīng)繼承的基礎(chǔ)框架樣式萧求。父樣式不是必需的,但因?yàn)槊總€(gè)視圖上存在的單一樣式規(guī)則顶瞒,使用自定義版本覆蓋默認(rèn)樣式可替代主題的默認(rèn)值夸政。如果沒(méi)有繼承基礎(chǔ)父樣式,則必須定義視圖需要的所有屬性榴徐。通過(guò)框架的基礎(chǔ)樣式擴(kuò)展小部件的樣式守问,可確保我們只需要添加希望定制的、默認(rèn)主題外觀之外的屬性坑资。
顯式或隱式的父樣式聲明:
樣式繼承采用兩種形式之一耗帕。如前所示,樣式可以顯式聲明其父樣式:
<style name="BaseStyle" />
<style name="NewStyle" parent="BaseStyle" />
NewStyle是BaseStyle的擴(kuò)展袱贮,包括在父樣式中定義的所有屬性仿便。樣式還支持隱式父樣式聲明語(yǔ)法,如下所示:
<style name="BaseStyle" />
<style name="BaseStyle.Extended"/>
BaseStyle.Extended以相同的方式從Base Style繼承其屬性攒巍。此版本的功能與顯式示例相同探越,只是更加簡(jiǎn)潔。兩種方式不應(yīng)混用窑业,如果混用钦幔,就無(wú)法實(shí)現(xiàn)在單個(gè)樣式中采用多個(gè)父樣式。最終常柄,人們始終優(yōu)先選擇顯式父樣式聲明鲤氢,而代碼的可讀性就會(huì)降低。
我們可以對(duì)原始布局文件應(yīng)用新的樣式西潘,得到的簡(jiǎn)化版本如下所示:
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/LabelText"
android:text="Select One"/>
<RadioGroup
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioButton
style="@style/FormRadioButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="One"/>
<RadioButton
style="@style/FormRadioButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Two"/>
<RadioButton
style="@style/FormRadioButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Three"/>
</RadioGroup>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/LabelText"
android:text="Select All"/>
<TableRow>
<CheckBox
style="@style/FormCheckBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="One"/>
<CheckBox
style="@style/FormCheckBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Two"/>
<CheckBox
style="@style/FormCheckBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Three"/>
</TableRow>
<TableRow>
<Button
style="@style/FormButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@android:string/ok"/>
<Button
style="@style/FormButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@android:string/cancel"/>
</TableRow>
</TableLayout>
通過(guò)對(duì)每個(gè)視圖應(yīng)用樣式屬性卷玉,就可以避免重復(fù)的顯式屬性引用,而改用對(duì)每個(gè)元素只引用一次喷市。此行為的一個(gè)例外情況是TextView頭部相种,它接受特殊的android:textAppearance屬性。此屬性獲取一個(gè)樣式引用品姓,并且僅應(yīng)用于文本格式化屬性(大小寝并、樣式箫措、顏色等)。使用TextView時(shí)衬潦,仍然可以同時(shí)應(yīng)用單獨(dú)的樣式屬性斤蔓。這樣,TextView實(shí)例在對(duì)單個(gè)視圖使用多種樣式的框架中就可以得到支持镀岛。
主題
在Android中弦牡,主題(Theme)就是一種應(yīng)用到整個(gè)應(yīng)用程序或某個(gè)Activity的外觀風(fēng)格。使用主題有兩個(gè)選擇漂羊,使用系統(tǒng)主題或者自定義主題驾锰。無(wú)論采用哪種方法,都要在AndroidManifest.xml文件中設(shè)置主題走越,代碼如下所示:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
……>
<!--通過(guò)application標(biāo)簽來(lái)設(shè)置全局主題-->
<application android:theme="APPLICATION_THEME_NAME"
……>
<!--通過(guò)application標(biāo)簽來(lái)設(shè)置單個(gè)主題-->
<activity android:name=".Activity"
android:theme="ACTIVITY_THEME_NAME"
……>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
……
</intent-filter>
</activity>
</application>
</manifest>
(1)系統(tǒng)主題
Android框架中打包的styles.xml和themes.xml文件中包含了一些主題選項(xiàng)椭豫,其中是一些有用的自定義屬性。完整的清單可查閱SDK文檔中的R.style,下面是幾個(gè)常用實(shí)例:
- Theme.Light:標(biāo)準(zhǔn)主題的變體买喧,該主題的背景和用戶元素使用相反的顏色主題捻悯。它是Android3.0以前版本的應(yīng)用程序默認(rèn)推薦使用的基礎(chǔ)主題匆赃。
- Theme.NoTitleBar.Fullscreen:移除標(biāo)題欄和狀態(tài)欄淤毛,全屏顯示(去掉屏幕上所有的組件)。
- Theme.Dialog:讓Activity看起來(lái)像對(duì)話框的有用主題算柳。
- Theme.Holo.Light:(API Level11)使用逆配色方案的主題并默認(rèn)擁有一個(gè)ActionBar低淡。這是Android3.0上應(yīng)用程序默認(rèn)推薦的基礎(chǔ)主題。
- Theme.Holo.Light.DarkActionBar:(API Level14)使用逆配色方案的主題瞬项,但ActionBar是黑色實(shí)線的蔗蹋。這是Android4.0上應(yīng)用程序默認(rèn)推薦的基礎(chǔ)主題。
- Theme.Material.Light:(API Level21)通過(guò)小型的原色調(diào)色板控制的簡(jiǎn)化顏色方案主題囱淋,此主題還支持使用提供的原色對(duì)標(biāo)準(zhǔn)小部件著色猪杭。這是Android5.0上應(yīng)用程序默認(rèn)推薦的基礎(chǔ)主題。
注意:
使用AppCompat庫(kù)時(shí)妥衣,應(yīng)改為使用這些主題的每個(gè)主題的其他版本(例如皂吮,Theme.AppCompat.Light.DarkActionBar)。
以下代碼通過(guò)設(shè)置AndroidManifest.xml文件中的android:theme屬性税手,將一個(gè)系統(tǒng)主題應(yīng)用到整個(gè)應(yīng)用程序中蜂筹。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
……>
<!--通過(guò)application標(biāo)簽來(lái)設(shè)置全局主題-->
<application android:theme="Theme.NoTitleBar"
……>
……
</application>
</manifest>
(2)自定義主題
有時(shí)候系統(tǒng)提供的主題還不能滿足要求。畢竟芦倒,系統(tǒng)提供的主題并不能自定義窗口中的所有元素艺挪。定義自定義主題能方便地解決這個(gè)問(wèn)題。
找到項(xiàng)目目錄res/values下的styles.xml文件兵扬,如果沒(méi)有就創(chuàng)建一個(gè)麻裳。記住口蝠,主題就是應(yīng)用范圍更廣的風(fēng)格樣式,所以兩者是在同一個(gè)地方定義的掂器。與窗口自定義有關(guān)的主題元素可以在SDK的R.attr引用中找到亚皂,下面是常用的一些元素:
- android:windowNotitle:控件是否要移除默認(rèn)的標(biāo)題欄;設(shè)為true以移除標(biāo)題欄国瓮。
- android:windowFullscreen:控件是否移除系統(tǒng)狀態(tài)欄灭必;設(shè)為true以移除狀態(tài)欄并全屏顯示。
- android:windowBackground:將某個(gè)顏色或Drawable資源設(shè)為背景乃摹。
- android:windowContentOverlay:窗口內(nèi)容的前景之上放置的Drawable資源禁漓。默認(rèn)情況下,就是狀態(tài)欄下的陰影孵睬;可以用任何資源代替默認(rèn)的狀態(tài)欄播歼,或者設(shè)為null(XML中為@null)以將其移除。
此外掰读,Material主題接受一系列顏色屬性秘狞,這些屬性用于對(duì)應(yīng)用程序界面小部件著色:
- android:colorPrimary:用于對(duì)主要的界面元素著色,如ActionBar和滾動(dòng)邊界發(fā)光特效蹈集。同樣也影響最近對(duì)標(biāo)題欄顏色的操作烁试。
- android:colorPrimaryDark:對(duì)系統(tǒng)控件著色,如狀態(tài)欄的背景拢肆。
- android:colorAccent:應(yīng)用于擁有焦點(diǎn)或已激活控件的默認(rèn)顏色减响。
- android:colorControlNormal:重寫沒(méi)有焦點(diǎn)或未激活控件的顏色。
- android:colorControlActivated:重寫擁有焦點(diǎn)或已激活控件的顏色郭怪。如果同時(shí)定義了強(qiáng)調(diào)色支示,則替換了該顏色。
- android:colorControlHighlight:重寫正在按下的控件的顏色鄙才。
以下代碼就是一個(gè)styles.xml文件示例颂鸿,其中創(chuàng)建了一個(gè)自定義主題,以便為應(yīng)用程序界面提供品牌特有的顏色攒庵。
res/values/styles.xml
<resources>
<style name="BaseAppTheme" parent="@style/Theme.AppCompat.Light.DarkActionBar">
<!-- Action bar 的背景色 -->
<item name="colorPrimary">@color/primaryBlue</item>
<!-- 狀態(tài)欄的著色 -->
<item name="colorPrimaryDark">@color/primaryDarkBlue</item>
<!-- 應(yīng)用于所有擁有焦點(diǎn)/已激活控件的默認(rèn)顏色-->
<item name="colorAccent">@color/accentPink</item>
<!-- 未選擇控件的顏色 -->
<item name="colorControlNormal">@color/controlNormalGreen</item>
<!-- 已激活控件的顏色嘴纺,重寫強(qiáng)調(diào)色 -->
<item name="colorControlActivated">@color/controlActivatedGreen</item>
</style>
</resources>
注意,主題也可以從父主題繼承屬性叙甸,所以不需要從頭創(chuàng)建整個(gè)主題颖医。在這個(gè)示例中,我們選擇了繼承Android默認(rèn)的系統(tǒng)主題裆蒸,只自定義我們要修改的屬性熔萧。所有平臺(tái)主題都定義在Android包的res/values/theme.xml文件中。關(guān)于樣式和主題的更多細(xì)節(jié)可查閱SDK文檔。
以下代碼展示了如何在AndroidManifest.xml中對(duì)單個(gè)Activity示例應(yīng)用這些主題:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
……>
<!--通過(guò)application標(biāo)簽來(lái)設(shè)置全局主題-->
<application
……>
<!--通過(guò)activity標(biāo)簽來(lái)設(shè)置單獨(dú)的主題-->
<activity android:name=".ActivityOne"
android:theme = "@style/AppTheme"
……>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Demo下載地址:
1.1 樣式化常見(jiàn)組件