Dynamic themes and custom styles

android

Solution

Thanks to wingman for the hint. My situation involved colors, which are a bit more complicated, so I'll write up my solution here.

I have two themes (light and dark) which the user can choose from in the Settings screen. I have a `ListView` which can have two types of rows (plain and note), each with its own styling. Firstly each layout needs to point to a style:

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

(or `NoteItemText`) and we need to define the styles:

<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>

The text color can't be fixed because it depends on the selected theme. We must create a custom attribute and refer to it with a question mark, as above. We define the attribute in `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"/>

We can then define the various colors. Here we have two styles and two themes, so we need four color state lists, each in its own file under `res/color`. For example, here's `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>

The selected/focused/pressed colors are the same in all these files because they're over the highlight color. Be careful with the `state_window_focused` version. It didn't behave as advertised, and I had to set it to the default color (the last line above) in all cases. Now we need to create our themes and bind the attributes to one of the colors. These lines go into `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>

Finally we pick a theme at run-time, in an Activity's `onCreate()` method, before calling `super.onCreate()`:

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

Note that I don't take into account newer themes like Holo, so my app looks old on Honeycomb and later. I'll fix that at some point, but it wasn't a regression here.

A twist in my case is that some Activities have a larger title bar in order to fit some buttons. In principle I should have created four themes, a light and dark for a narrow title and a light and dark for a fat title. But instead I created a mix-in style:

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

and procedurally add it to whatever theme I'm using. This code goes right after the above `setTheme()` calls:

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

I didn't see this documented anywhere, and I don't know if it's legit, but the code of `Activity.getTheme()` implies that it should work fine, and it has worked in all my testing. This can help avoid the combinatorial explosion of themes that you can find in the standard Android theme list.

Problem

I've got an app with two themes (dark and light) that can be selected at runtime. This works. I also have a ListView with rows that can have one of three different layouts, each of which has a style (say, different colors). This also works. But I can't get these two features to work together. I really need six different styles, three for one theme (dark) and three for the other (light), but I can't figure out how to choose a style for a list item based on the current theme, or get that effect any other way by using XML files. My three layouts each point to a custom theme that sets the color, but that overrides whatever theme I've got set. Themes can only contain items that are "styleable", so I can't put my own custom items in there. There may be a way to do this programmatically, but I was hoping to do it declaratively. Any ideas?

Original source

Related problems