In previous articles in this series on Android styling, we’ve looked at the difference between styles and themes, talked about the benefits of using themes and theme attributes and highlighted some common attributes to use.
Today we’ll focus on actually using themes, how they are applied to your app and the implications for how you build them.
In a previous article we stated:
Themeis accessed as a property of a
Contextand can be obtained from any object which is or has a context e.g.
ViewGroup. These objects exist in a tree, where an
ViewGroups which contain
Views etc. Specifying a theme at any level of this tree cascades to descendent nodes e.g. setting a theme on a
ViewGroupapplies to all the
Views within it (in contrast to styles which only apply to a single view).
Setting a theme at any level in this tree doesn’t replace the theme currently in effect, it overlays it. Consider the following
Button which species a theme, but who’s parent also specifies a theme:
If an attribute is specified in both themes, then the most local “wins” i.e. those in
Bar will be applied to the button. Any attributes specified in theme
Foo but not specified in theme
Bar will also be applied to the button.
This might seem like a contrived example but this technique is extremely useful for styling subsections of an app with a different appearance such as a dark toolbar on an otherwise light screen, or this screen (from the Owl sample app) which has a largely pink theme but the bottom section showing related content has a blue theme:
This can be achieved by setting a theme on the root of the blue section and it cascades to all views within it.
As themes overlay any themes higher in the tree, it’s important to consider what your theme specifies to ensure that it doesn’t accidentally replace an attribute that you want to keep. For example, you may want to change the background color of a view (usually controlled by
colorSurface) but nothing else i.e. you want to retain the rest of the current theme. For this we can use a technique known as theme overlays.
These are themes which are designed to, well, overlay another theme. They are as narrowly scoped as possible i.e. they only define (or inherit) as few attributes as possible. In fact, theme overlays often (but not always) have no parent e.g.:
Theme overlays are narrowly scoped themes, defining as few attributes as possible, designed to overlay another theme
By convention, we name these beginning with “ThemeOverlay”. There are a number of handy theme overlays provided by MDC (and AppCompat) that you can use to flip the color of a subsection of your app from light to dark:
By definition, theme overlays don’t specify a number of things and shouldn’t be used in isolation e.g. as the theme of your activity. In fact you can think of 2 “types” of theme you can use in your app:
- “Full” themes. These specify everything you’d need for a screen. They inherit from another “full” theme like
Theme.MaterialComponentsand should be used to theme an
- Theme overlays. Only ever intended to be applied over a full theme, i.e. should not be used in isolation as likely won’t specify important and necessary things.
There’s always a theme in effect, even if you don’t specify one anywhere in your app you’ll inherit a default theme. As such the example above was a simplification and you should never use a full theme within a
View and instead use theme overlays:
These overlays won’t exist in isolation but will themselves be overlaid on the theme of the enclosing
Using Themes has a run-time cost; each time that you declare an
android:theme, you’re creating a new
ContextThemeWrapper, which allocates new
Resources instances. It also introduces more levels of styling indirection to be resolved. Be wary of using themes too liberally, especially in repeated situations such as
RecyclerView item layouts or profile to monitor their impact.
We said that a
Theme is associated with a
Context — this means that if you’re using a
Context to retrieve a resource in code, then be careful that you use the right
Context. For example, somewhere in your code you may retrieve a
someView.background = AppCompatResources.getDrawable(requireContext(), R.drawable.foo)
If the drawable references a theme attribute (which all drawables can do from API 21+, and
VectorDrawables can do from API 14+ via Jetpack) then you should ensure you use the right
Context to load the
Drawable. If you don’t you might be frustrated when trying to apply a theme to a sub-hierarchy and wondering why your
Drawable isn’t respecting it. For example if you use a
Context to load the
Drawable, this won’t respect themes applied lower down in the tree. Instead use the
Context closest to where the resource will be used:
someView.background = AppCompatResources.getDrawable(someView.context, R.drawable.foo)
We’ve talked about themes and contexts existing in a tree:
View etc. It can be tempting to extend this mental model to include the
Application class, after all you can specify a theme on the
tag in your manifest. Don’t be fooled by this!!
Context does not retain any theme information, the theme that you can set in your manifest is merely used as a fallback for any
Activity which doesn’t explicitly set a theme. As such you should never use the
Context to load resources which might vary by theme (like drawables or colors) or to resolve theme attributes.
Never use the
Contextto load themable resources
This is also why we specify a “full” theme for an
Activity and structure these to extend from any application wide theme — an
’s theme isn’t overlaid over the
Hopefully this post has explained how themes overlay ancestors in a tree and how this behavior can be useful when styling our apps. Use the
android:theme tag to theme sections of your layout and use theme overlays to only adjust the attributes you need. Be mindful of using the right theme and context to load resources and be wary of the application context!