In the previous posts in this series on Android styling, we looked at the difference between themes and styles and why it’s a good idea to factor out things that you wish to vary by theme and common theme attributes to use:
This enables us to create fewer layouts or styles, isolating changes within a theme. In practice, you largely want to vary colors by theme and as such you should always* refer to colors via theme attributes.
Always* refer to colors via theme attributes
That means you can consider code like this to be a smell:
1 | <!-- Copyright 2019 Google LLC. |
prefer_theme_attrs_color_res.xml hosted with ❤ by GitHub
Instead you should refer to a theme attribute, allowing you to vary the color by theme, for example, providing a different value in dark theme:
1 | <!-- Copyright 2019 Google LLC. |
prefer_theme_attrs_color_attr.xml hosted with ❤ by GitHub
Even if you’re not currently supporting alternate themes (what–no dark theme??), I’d recommend following this approach as it’ll make adopting theming much easier.
Qualified Colors?
You can vary colors by providing alternate values in different configurations (e.g. @color/foo
defined in both res/**values**/colors.xml
and an alternate value set in res/**values-night**/colors.xml
) but I’d recommend using theme attributes instead.
Varying at the color layer forces you to give semantic names to colors i.e. you likely wouldn’t name a color @color/white
and provide a dark variant in the -night
configuration — that would be pretty confusing. Instead you might be tempted to use a semantic name, like @color/background
. The problem with this is that it combines both the declaration of the color and providing the value. As such it gives no indication that this can or will vary by theme.
Varying @colors
can also encourage you to create more colors. If a different situation calls for a new semantically named color with the same value (i.e. not a background but should be the same color), then you still need to create a new entry in your colors file.
By using a theme attribute we separate the declaration of semantic colors from providing their values and make call-sites clearer that the color will vary by theme (as they use the ?attr/
syntax). Keeping your color declarations to literally named values encourages you to define a palette of colors used by your app and vary them at the theme level, keeping your colors file small and maintainable.
Define a palette of colors used by your app and vary them at the theme level
The added benefit of this approach is that layouts/styles referring to these colors become more reusable. Because themes can be overlaid or varied, the indirection means you don’t need to create alternate layouts or styles just to vary some colors — you can use the same layouts with a different theme.
Always?
I placed an asterix on “always* refer to colors via theme attributes” because there may be occasions where you explicitly don’t want to vary a color by theme. For example, the Material Design guidelines call out occasions where you may wish to use the same brand color in both light and dark themes.
In these rare cases, it’s perfectly valid to refer directly to a color resource:
1 | <!-- Copyright 2019 Google LLC. |
prefer_theme_attrs_constant_color_exception.xml hosted with ❤ by GitHub
State of the art
Another situation where you might not refer directly to a theme attribute in your layouts/styles is when using ColorStateList
s.
1 |
|
prefer_theme_attrs_csl_exception.xml hosted with ❤ by GitHub
This might be valid if primary_20
is a ColorStateList
which itself refers to theme attributes for the color values (see below). While commonly used to provide different colors in different states (pressed, disabled etc) ColorStateList
s have another capability that can be useful for theming. They let you specify an alpha value to be applied to a color:
1 | <!-- Copyright 2019 Google LLC. |
prefer_theme_attrs_csl.xml hosted with ❤ by GitHub
This kind of single-item-ColorStateList
(i.e. only supplying a single, default color, not different colors per state) helps reduce the number of color resources that you need to maintain. That is rather than defining a new color resource that manually sets an alpha value on your primary color (per configuration!) instead this alters whatever colorPrimary
is in the current theme. If your primary color changes you only need to update it in a single place, not hunting down all instances of where it has been tweaked.
While useful, there are some caveats to this technique to be aware of.
\1. If the specified color also has an alpha value, then the alphas are combined e.g. applying 50% alpha to 50% opaque white would yield 25% white:
1 |
|
prefer_theme_attrs_csl_combined_alpha.xml hosted with ❤ by GitHub
For this reason, it’s preferable to specify theme colors as fully opaque and use ColorStateList
s to modify their alphas.
\2. The alpha component was only added in API 23 so if your min sdk is lower than this, be sure to use AppCompatResources.getColorStateList
which backports this behavior (and always use the android:alpha
namespace, never the app:alpha
namespace).
\3. Often we use a shorthand to set a color as a drawable e.g.
1 | <!-- Copyright 2019 Google LLC. |
prefer_theme_attrs_view_background_color.xml hosted with ❤ by GitHub
A View
’s background is a drawable, this shorthand coerces the given color to a ColorDrawable
. However there is no way to convert a ColorStateList
to a Drawable
(before API 29 when ColorStateListDrawable
was introduced to solve this issue). We can however work around this restriction:
1 | <!-- Copyright 2019 Google LLC. |
prefer_theme_attrs_view_background_tint.xml hosted with ❤ by GitHub
Be sure that your background tint supports the states your view needs e.g. if it needs to change when disabled.
Enforcement
So you’re convinced that you should be using theme attributes and ColorStateList
s, but how do you enforce this across your codebase or team? You can try to be vigilant during code reviews but this doesn’t scale well. A better approach is to rely on tooling to catch this. This article outlines how to add a lint check to look for literal color uses and could be extended to cover all advice in this article:
Be Indirect
Using theme attributes and ColorStateList
s to factor colors out to your theme makes your layouts and styles more flexible, promoting reuse and keeping your codebase lean and maintainable.
Join us in the next post where we’ll look more at using themes and how they interact:
https://medium.com/androiddevelopers/android-styling-prefer-theme-attributes-412caa748774