您的位置:首页 > 移动开发 > Android开发

如何正确的使用 Android 中的 themes 和 styles

2015-06-23 10:26 579 查看

如何正确的使用 Android 中的 themes 和 styles

译自:How to avoid headaches with Android themes and styles

Android 确实是一个非常棒的开发平台。它拥有非常棒的框架(Activities, Intents…),简洁的概念(Java 和 XML 的关联,可缩放的9切图…),清晰的文档以及可以使设计自己的 app 成为一种乐趣的辅助工具。它非常容易学习,当你写出第一个 HelloWorld 程序之后不久你就会觉得自己拥有设置非常酷的 app 的能力。这时候你已经体会到酷的应用设计会让酷的功能更加吸引人,从人吸引大量酷的用户。

但是,如果你深入到设计的过程中,你会涉及到那些,正如 Android 文档中自己说的“文档编写不是很好”的部分:styles 和 themes。确实,

“然而,R.style 部分的文档并没有被很好的编档,它并没有完整的描述 styles,所以查看 styles 和 themes 部分的源码会让你更好的了解那些 style 属性的意义。”

(http://developer.android.com/guide/topics/ui/themes.html#Platformstyles)

在研究 styles 和 themes 的概念的时候,我们会发现一些像是 theme 属性/attribute(R.attr),styleable 特性/property(R.styleable),text appearance/文本样式,theme inheritance/继承 这些概念。如果你没有将所有这些的细节全部记在脑子里的话,它们会让你非常头疼。我最新在试图以 Holo 为模板设计自定义的 AlertDialogs,而最终这些概念就出现在了我的面前,让我意识到这不是一件容易的事情。我会尝试从头开始捋清这些东西。

这篇文章呈现的所有信息都是对以下内容的总结:

Android “styles and themes” API guide http://developer.android.com/guide/topics/ui/themes.html

Android R.style reference http://developer.android.com/reference/android/R.style.html

Android R.attr reference http://developer.android.com/reference/android/R.attr.html

Android R.styleable reference http://developer.android.com/reference/android/R.styleable.html

Android source for default themes.xml https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/res/res/values/themes.xml

Android source for default styles.xml https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/res/res/values/styles.xml

这篇文章的目标读者包括 Android 初级和高级开发者。你已经搞定 Android XML 文件(resources,layouts,values…),并试图理解什么是 R.attr 和 textAppearance 了吗?或者是你正需要进行设计并想知道最简单的自定义你的 App 的方法,使用自己的 theme 而不是标准的。那么,这篇文章非常适合你。

Android 中的 themes 和 styles 是什么?

概述

让我们从最基础的 theme 和 style 的比较开始。

比较项themesstyles
定义在一个资源文件内部,通常是 themes.xml。
使用
<style>
标签,以 theme. 为命名前缀。
在一个资源文件内部,通常是 styles.xml。
使用
<style>
标签。
使用在 Android Manifest 中,
使用
<application android:theme="@style/Theme.LE_Theme">
来定义整个 Application 的 theme
或者使用
<activity android:theme="@style/Theme.LE_Theme">
为特定的 activity 指定 theme。
在任何布局文件中
<AnyWidget style="@style/LE_style">

AnyWidget 可以是 Button,TextView,LinearLayout,ScrollView等等。
Android 中的默认值
(API level 19)
General themes
theme
theme.Black
theme.Light
theme.DeviceDefault
theme.DeviceDefault.Light
Theme.Holo
Theme.Holo.Light
Sub-themes
theme.NoTitleBar

theme.Black.NoTitleBar

theme.Light.NoTitleBar

theme.DeviceDefault.NoActionBar

theme.DeviceDefault.Light.NoActionBar

Theme.Holo.NoActionBar

Theme.Holo.Light.NoActionBar
theme-related widget styles
Widget.Button
Widget.Button.Small

Widget.DeviceDefault.Button
Widget.DeviceDefault.Button.Small

Widget.DeviceDefault.Light.Button
Widget.DeviceDefault.Light.Button.Small

Widget.Holo.Button
Widget.Holo.Button.Small

Widget.Holo.Light.Button
Widget.Holo.Light.Button.Small

theme-related property styles
TextAppearance.Small
TextAppearance.Small.Inverse

TextAppearance.DeviceDefault.Small
TextAppearance.DeviceDefault.Small.Inverse

TextAppearance.Holo.Small
TextAppearance.Holo.Small.Inverse

Standalone styles
MediaButton
MediaButton.Play
MediaButton.Next
我们可以从表中看出:

一个 theme 实际上就是一个 style,唯一的不同是他们在哪里被使用:你在 Android Manifest 中为 app 或者 activity 设置 theme,你在布局文件中为 widget 设置 style。

style 比 theme 更多(styles.xml 文件比 themes.xml 文件大)。这是因为一个 theme 的定义本质上就是一些对这个 theme 会使用的 style 的引用。这意味着 Theme.Holo 引用并使用:

Widget.Holo.Button


Widget.Holo.Button.Small


TextAppearance.Holo.Small


TextAppearance.Holo.Small.Inverse


theme 可以被分为 general themesub-theme。例如,当你为自己的应用设计一个 theme 的时候,你会去创建一个
Theme.LE_Theme
。但是可以想象的到,你的 app 有可能在某些地方会用到没有 ActionBar

的特殊 activity。所以你应该为这个 activity 再创建一个 sub-theme
Theme.LE_Theme.NoActionBar
。那么你的 Android Manifest 文件会像是这样:

<application android:theme="@style/Theme.LE_Theme">
<activity android:name="MainActivity" />
<activity android:name="SpecialActivity" android:theme="@style/Theme.LE_Theme.NoActionBar" />
</application>


你的所有 activity ,包括 MainActivity,都会使用那个在 application 下面定义的 theme,除了那些重写了应用 theme 的 activity,像 SpecialActivity。

style 可以被分割为3类:

theme-related widget style:这些 style 的名字已经明确的表明了这个 widget 被定义为什么样子。当你读到
Widget.Holo.Light.Button.Small
这个 style 的定义时,你就已经知道它定义了 Holo theme 中 light 版本的小 button。

theme-related property style:这些定义了那些不只是绑定到一个 widget 的特性。例如:
TextAppearance.Small
定义了 小文本 的大小和颜色。但是这个显示效果并没有指定给任何一个 widget。我们一会儿会看到 widget 是如何引用它需要的文本显示效果的。

standalone styles:这些是不常见的,这些 style 会一直有效,不论实用的 theme 是什么。在上面的表格中,MediaButton,你可以想象一个 MediaButton 在所有的 theme 中看起来总是一样的。

继承

style/theme 的其中一个有趣的特性是,你可以继承一个已经存在的 style/theme,但是方法是很微妙的。

方案1:使用显式的 parrent 属性

这是最常见的方法,而你可能已经学过了。如果你想要定义一个名叫 Child 的 style 继承一个名叫 Parent 的 style 的所有特性(property),你只需要这样写:

<style name="Child" parent="@style/Parent">


然后你可以在 Child 中定义一些特性(property)来重写 Parent 的特性,并且/或者添加一些新的特性。当然,你也可以继承 Android 的默认 style/theme。例如,你可以像这样定义自己应用的 theme:

<style name="Theme.LE_Theme" parent="@android:style/Theme.Holo">


方案2:使用隐式的 style 名字

有另一个方式来继承 style/theme,那就是隐式继承。与设置一个 parent 属性不同,隐式继承做的只是在命名的时候将 style/theme 想要继承的父 style/theme的名字加一个“.”作为前缀。例如:

<style name="Parent.Child">


但是使用这种方法需要注意:

父 style/theme 需要存在:如果Theme.LE_Theme不存在的话,Theme.LE_Theme.NoActionBar 会报错。

父 style/theme 不可以是 Android 的默认 style/theme。这意味着你不能创建
<style name="Theme.Holo.LE_Theme>
因为它的隐式父 theme 是 Theme.Holo,而 Theme.Holo 并不是你的 theme。但是你可以创建
<style name="Theme.Holo.LE_Theme parent="@android:style/Theme.Holo">
因为它是显式继承。

当你在 java 中引用一个 style/theme 的时候,所有的点“.”都要用下划线“_”代替。所以上面的 theme 在 java 中引用的写法就应该是 R.style.Theme_Holo_LE_Theme。

theme 中都指定了些什么?

现在我们知道了 theme 和 style 之间的区别,我们知道要在哪些地方使用它们,我们知道如何正常的或者通过继承定义和命名一个 theme。而且我们知道每一个 theme/style 都包含一些特性(property)或者属性(attribute)。但是这些属性都是什么呢?

从源码中学习

首先,让我们聚焦在 theme 上。

一个 theme 可以定义的所有属性的完整列表在这里。一共有超过218个属性。Android 源码允许我们允许我们查看 Android 默认 theme 的定义,所以我们把 HOLO 作为例子,它被定义在 themes.xml中(2014年6月的版本)。

<style name="Theme.Holo">
<!-- Text styles -->
<item name="textAppearance">@android:style/TextAppearance.Holo</item>
<item name="textAppearanceInverse">@android:style/TextAppearance.Holo.Inverse</item>
<item name="textAppearanceLarge">@android:style/TextAppearance.Holo.Large</item>
<item name="textAppearanceLargeInverse">@android:style/TextAppearance.Holo.Large.Inverse</item>

<item name="textColorPrimary">@android:color/primary_text_holo_dark</item>
<item name="textColorSecondary">@android:color/secondary_text_holo_dark</item>
<item name="textColorPrimaryInverse">@android:color/primary_text_holo_light</item>
<item name="textColorSecondaryInverse">@android:color/secondary_text_holo_light</item>

<item name="editTextColor">?android:attr/textColorPrimary</item>
<item name="editTextBackground">@android:drawable/edit_text_holo_dark</item>

<!-- Window attributes -->
<item name="windowFullscreen">false</item>
<item name="windowTitleStyle">@android:style/WindowTitle.Holo</item>
<item name="windowTitleSize">25dip</item>

<!-- Widget styles -->
<item name="listViewStyle">@android:style/Widget.Holo.ListView</item>
<item name="scrollViewStyle">@android:style/Widget.Holo.ScrollView</item>
<item name="textViewStyle">@android:style/Widget.Holo.TextView</item>
</style>


(这个文件来自于 Android 源码,因此它是内部的(internal),它的属性不使用 android: 前缀。但是当你定义自己的 theme 的时候必须使用。)

这只是整个 Holo theme 定义的一小段片段,但是所有重要的东西都在这里了:

让我们从最后开始。第22行到第24行是对 widget style 的引用。它很容易理解:那是你用来定义你的 theme 中所有的 ListView 将会使用某个 style ,所有的 TextView 将会使用另一个 style 等等。所有常见的控件(widget)都有类似的属性。

第17行到第19行是窗口(window)自身相关的属性,典型的情况是,当你开始设计一个特别的 activity、dialog、透明窗口等的时候,你就会碰到它们。

第3行到第14行定义了不同类型的文本的显示样式(appearance):尺寸(小/中/大),重要程度(primary/secondary/tertiary)以及颜色版本(正常/反转),你的 theme 中所有可能的文本显示样式都在这里被定义。

这里最有趣的是这些属性的值。当然,你可以硬编码那些 dimension 值、boolean 值等等,或者你也可以选择引用 drawable 或者是 color。但是,通常你会想要在你的 theme 中引用 style。两种方式可以实现:

直接引用一个 style

xml

<item name="scrollViewStyle">@android:style/Widget.Holo.ScrollView</item>


这非常的简单:你只是简单的说明了你的所有ScrollView都使用 Widget.Holo.ScrollView 这个 style。

使用相同的 style 作为另一个属性

xml

<item name="textColorPrimary">@android:color/primary_text_holo_dark</item>

<item name="editTextColor">?android:attr/textColorPrimary</item>


textColorPrimary 是直接引用,但是 editTextColor 只是简单的说明“使用这个 theme 中 textColorPrimary 属性所定义的 style”。

theme 属性

这个部分非常重要,或者说是非常核心。那些通过 ?android:attr/ 来定义的属性都是 theme 属性。它们各自指向当前 theme 中定义的一个属性。android: 前缀可能会产生误导,但是它仅仅代表着那个 theme 属性的名字是定义在 Android 中的,但是它在当前 theme 中锁定义的值将会被获取到

假设你有一个自定义的布局文件,你在其中这样定义了一个TextView:

<TextView android:textAppearance="?android:attr/textAppearanceLarge" />


如果你在一个使用 theme A 的 activity 中显示这个布局文件,那么定义在

<style name="A"><item name="android:textAppearance">@style/LE_STYLE_A</item></style>


中的 LE_STYLE_A style 将会被使用。

如果你在一个使用 theme B 的 activity 中显示同样的布局文件,那么定义在

<style name="B"><item name="android:textAppearance">@style/LE_STYLE_B</item></style>


中的 LE_STYLE_B style 将会被使用。

一个 style 中都指定了一些什么?或者说 theme 属性的力量

我们知道一个 theme 如何通过引用 style 来指定自己的控件(widget)/文本(text)等等的显示效果。现在让让我们看看另一边。这些 style 是怎么定义的?这里有一个 Holo style 的定义,来自于styles.xml(2014年6月的版本)。

<style name="TextAppearance.Holo" parent="TextAppearance">
</style>

<style name="TextAppearance">
<item name="android:textColor">?textColorPrimary</item>
<item name="android:textColorHighlight">?textColorHighlight</item>
<item name="android:textColorHint">?textColorHint</item>
<item name="android:textColorLink">?textColorLink</item>
<item name="android:textSize">16sp</item>
<item name="android:textStyle">normal</item>
</style>

<style name="Widget.Holo.TextView" parent="Widget.TextView">
</style>

<style name="Widget.TextView">
<item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
<item name="android:textSelectHandleLeft"
c0d7
>?android:attr/textSelectHandleLeft</item>
<item name="android:textSelectHandleRight">?android:attr/textSelectHandleRight</item>
<item name="android:textSelectHandle">?android:attr/textSelectHandle</item>
<item name="android:textEditPasteWindowLayout">?android:attr/textEditPasteWindowLayout</item>
<item name="android:textEditNoPasteWindowLayout">?android:attr/textEditNoPasteWindowLayout</item>
<item name="android:textEditSidePasteWindowLayout">?android:attr/textEditSidePasteWindowLayout</item>
<item name="android:textEditSideNoPasteWindowLayout">?android:attr/textEditSideNoPasteWindowLayout</item>
<item name="android:textEditSuggestionItemLayout">?android:attr/textEditSuggestionItemLayout</item>
<item name="android:textCursorDrawable">?android:attr/textCursorDrawable</item>
</style>


正如你所看到的,这些 Holo style 只是简单的使用它们父 style,并没有重写任何属性。而默认的 style 只是定义了对 theme 属性的引用(textColorPrimary、textAppearanceSmall…)。而这正是展现 theme 属性的力量的地方!你可以看到默认的TextView控件没有硬编码它的颜色,文字大小和其他属性。它完全依赖 theme 属性,这意味着每个 TextView 都会被展示它的 theme 所定制!

现在,让我们来实践一下。假设你想要创建一个自定义的 theme,在这个 theme 中你只改变一件事:把小文本的大小设为25sp 而不是默认的14sp(这是为你眼神不好的奶奶设计的),其他的全部基于 DeviceDefault theme。你要做的所有事情就是:

在 granny_themes.xml 中:

<style name="theme.Granny" parent="@android:style/theme.DeviceDefault">
<item name="android:textAppearanceSmall">@style/TextAppearance.Granny.Small</item>
</style>


在 granny_styles.xml 中:

<style name="TextAppearance.Granny.Small" parent="@android:style/TextAppearance.DeviceDefault.Small">
<item name="android:textSize">25sp</item>
</style>


在你的 Manifest 中设置上 theme.Granny,然后就完成了!所有使用“小文本”显示方式的控件(包括默认的 TextView 都会显示 25sp 。不需要对这些控件的 style 做任何改变。你也不需要在在布局文件中硬编码一个 text size。

同样,你也可以一同样的方式改变你的 ActionBar。是需要在你的 theme 中通过 android:actionBarstyle 引用你自定义的 style 即可。

总结

根据我的经验,需要大量的事件才能够理解什么是 文本样式(text appearance)、什么是 theme 属性、怎样继承 style以及更多的时间才能学会如何恰当的使用那些概念。然而,它的益处也是非常大的:你不必再花费时间去创建孤立的 style。相反,你可以一次性的深入定制你的 app。

作为最后的话,让我分享一些小技巧:

永远使你的自定义 style/theme 继承自 DeviceDefault theme 而不是 Holo。从 API level 14 之后,DeviceDefault 作为默认的 theme,并且使用 DeviceDefault 可以保证在 HTC、Samsung、Sony、Nexus 等设备中都有最好的显示效果。

使用 Android 命名惯例 Theme.LE_Theme.Light、Widget.LE_THEME.TextView 等等。这会让你的代码更加易读和易维护。

Eclipse 文本补全对于 theme 和 style 属性来说非常糟糕。想要知道你可以给 theme、TextView style 或者 TextAppearance style 设置什么属性,你需要查看 R.styleable 的文档或者直接查看你想要在 XML 文件中自定义的控件的文档(例如 ScrollView 的文档)。

以上就是全部内容。现在开始把玩尺寸(size)、颜色(color)以及其他的 style 来控制你的 app 的样子和感觉吧!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android styles themes