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