我有一个可以在运行时选择的具有两个主题(深色和浅色)的应用程序。这行得通。我还有一个 ListView,其中的行可以具有三种不同布局中的一种,每种布局都有一种样式(例如,不同的颜色)。这也有效。但是我不能让这两个功能一起工作。我真的需要六种不同的样式,三个用于一个主题(深色),三个用于另一个(浅色),但我不知道如何根据当前主题为列表项选择样式,或者获得任何效果通过使用 XML 文件的其他方式。我的三个布局每个都指向一个设置颜色的自定义主题,但这会覆盖我设置的任何主题。主题只能包含“可样式化”的项目,所以我不能把我自己的自定义项目放在那里。可能有办法以编程方式执行此操作,但我希望以声明方式进行。有任何想法吗?
2 回答
感谢僚机的提示。我的情况涉及颜色,这有点复杂,所以我会在这里写下我的解决方案。
我有两个主题(浅色和深色),用户可以在“设置”屏幕中进行选择。我有一个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>
最后,我们在运行时在 Activity 的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 主题列表中找到的主题组合爆炸式增长。
很久以前,Lawrence Kesteloot 在 2012 年发布了他的解决方案。现在是六年后,一个 Android 的新手,并尝试解决类似的问题:
如何通过只交换一个主题来交换应用程序的整体风格?
这是对劳伦斯问题如何组织两个可交换主题的概括。
我想出了一个基于劳伦斯的解决方案,并更进一步。
(并不是说它是完美的解决方案,而是一种改进。)
Lawrence 发现了用户定义属性的力量来实现这一目标。他根据当前选择的主题使用它们来处理颜色。
虽然这是有效的,但它仍然需要为每个属性定义属性。它不能很好地扩展。那么为什么不将属性捆绑到样式和主题中并使用相同的机制呢?
这会产生一个主主题,即定义子主题和样式。
资源/值/attrs.xml
<resources>
...
<attr name="mainViewTheme" format="string"/>
<attr name="asideViewTheme" format="string"/>
...
</resources>
在定义属性来设置主题时,它没有特殊的格式。格式字符串可以做到这一点。
res/values/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>
资源/布局/main.xml
<TextView
android:theme="?mainViewTheme"
...
通过交换主主题,所有风格都得到了调整。它仍然需要定义一些主题属性,然后完成一项强大的工作。不再需要为每个属性设置属性。