动态主题和自定义样式

时间:2012-10-29 04:58:30

标签: android

我有一个可以在运行时选择的具有两个主题(黑暗和浅色)的应用程序。这有效。我还有一个ListView,其行可以有三种不同的布局之一,每种布局都有一种样式(比方说,颜色不同)。这也有效。但我不能让这两个功能一起工作。我真的需要六种不同的风格,三种为一种主题(黑暗),三种为另一种(浅色),但我无法弄清楚如何根据当前主题为列表项选择样式,或者获得任何效果通过使用XML文件的其他方式。我的三个布局都指向一个自定义主题设置颜色,但这会覆盖我设置的任何主题。主题只能包含“可设置”的项目,因此我无法将自己的自定义项目放在那里。可能有一种方法可以以编程方式执行此操作,但我希望以声明方式执行此操作。有什么想法吗?

2 个答案:

答案 0 :(得分:7)

感谢wingman the hint。我的情况涉及颜色,这有点复杂,所以我会在这里写出我的解决方案。

我有两个主题(浅色和深色),用户可以在“设置”屏幕中选择。我有一个ListView,它可以有两种类型的行(普通和注释),每种行都有自己的样式。首先,每个布局都需要指向一种风格:

<TextView style="@style/PlainItemText" ... />

(或NoteItemText)我们需要定义样式:

<style name="PlainItemText">
    <item name="android:textSize">@dimen/list_item_font_size</item>
    <item name="android:textStyle">bold</item>
    <item name="android:textColor">?plainTextColor</item>
</style>

文本颜色无法修复,因为它取决于所选主题。我们必须创建一个自定义属性并使用问号引用它,如上所述。我们在res/values/attrs.xml中定义了属性:

<!-- Attributes we use to set the text color of the various list items. -->
<attr name="plainTextColor" format="reference|color"/>
<attr name="noteTextColor" format="reference|color"/>

然后我们可以定义各种颜色。这里我们有两个样式和两个主题,因此我们需要四个颜色状态列表,每个列表都位于res/color下的自己的文件中。例如,这里是res/color/plain_text_color_dark.xml

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_window_focused="false" android:color="@android:color/white"/>

    <item android:state_selected="true" android:color="@android:color/black"/>
    <item android:state_focused="true" android:color="@android:color/black"/>
    <item android:state_pressed="true" android:color="@android:color/black"/>

    <item android:color="@android:color/white"/>
</selector>

所有这些文件中所选/聚焦/按下的颜色相同,因为它们超出了高亮颜色。小心state_window_focused版本。它没有像宣传的那样,我必须在所有情况下将其设置为默认颜色(上面的最后一行)。现在我们需要创建主题并将属性绑定到其中一种颜色。这些行进入res/values/themes.xml

 <style name="Theme.Dark" parent="android:Theme">
     <item name="plainTextColor">@color/plain_text_color_dark</item>
     <item name="noteTextColor">@color/note_text_color_dark</item>
 </style>
 <style name="Theme.Light" parent="android:Theme.Light">
     <item name="plainTextColor">@color/plain_text_color_light</item>
     <item name="noteTextColor">@color/note_text_color_light</item>
 </style>

最后,我们在运行时选择一个主题,在活动的onCreate()方法之前调用super.onCreate()

if (isDarkTheme) {
    activity.setTheme(R.style.Theme_Dark);
} else {
    activity.setTheme(R.style.Theme_Light);
}

请注意,我没有考虑像Holo这样的新主题,因此我的应用程序在Honeycomb及更高版本上看起来很旧。我会在某个时候解决这个问题,但这不是回归。


在我的情况下,一个扭曲是一些活动有一个更大的标题栏,以适应一些按钮。原则上我应该创造四个主题,一个是明暗的标题,一个是明暗的标题。但相反,我创造了一种混合风格:

<!-- Mix-in style for activities. -->
<style name="ButtonTitleBar">
    <item name="android:windowTitleSize">44dp</item> 
</style>

并在程序上将其添加到我正在使用的任何主题中。此代码在上述setTheme()调用之后立即执行:

if (buttonTitleBar) {
    // Mix in this other style.
    Resources.Theme theme = activity.getTheme();
    theme.applyStyle(R.style.ButtonTitleBar, true);
}

我没有在任何地方看到这个记录,我不知道它是否合法,但Activity.getTheme()的代码暗示它应该可以正常工作,并且它在我的所有测试中都有效。这有助于避免您在标准Android主题列表中找到的主题组合爆炸。

答案 1 :(得分:1)

很久以前,Lawrence Kesteloot在2012年发布了他的解决方案。现在已经六年了,这是Android的一个新手,并试图解决类似的问题:

如何通过交换一个主题来交换整个应用程序的风格?

这是劳伦斯问题如何组织两个可交换主题的概括。

我找到了一个基于劳伦斯的解决方案,并且更进了一步。

(并不是说它是完美的解决方案,而是一种改进。)

Lawrence找出了用户定义属性的力量来实现这一目标。他根据当前选择的主题使用它们来处理颜色。

虽然这是有效的,但它仍然需要为每个属性定义属性。它不能很好地扩展。那么为什么不将属性捆绑到样式和主题中并使用相同的机制呢?

这会产生一个主题,即定义子主题和样式。

<强> RES /值/ attrs.xml

<resources>
     ...
    <attr name="mainViewTheme" format="string"/>
    <attr name="asideViewTheme" format="string"/>
     ...
</resources>

定义设置主题的属性时,没有特殊的格式。格式字符串就可以了。

<强> RES /值/ styles.xml

<style name="MasterTheme">
     ...
    <item name="mainViewTheme">@style/MainViewTheme</item>
    <item name="asideViewTheme">@style/AsideViewTheme</item>
     ...
</style>

<style name="MainTextTheme">
     ...
</style>

<style name="MainViewTheme">
     ...
</style>

<强> RES /布局/ main.xml中

<TextView
    android:theme="?mainViewTheme"
    ...

通过交换主主题,可以调整所有样式。它仍然需要定义少数几个主题属性,然后才能完成一项强大的工作。不再需要为每个属性设置属性。