09 November 2019
A theme is a style resource that’s associated with a Context
. It’s usually used to define theme attributes (though can be used to define view attributes), for example, colorPrimary
.
ContextThemeWrapper
is an implementation of ContextWrapper
which lets us specify a theme. Since ContextThemeWrapper
must have a “base” context (which will have its own theme), we can use this class to create theme overlays. Activity
extends ContextThemeWrapper
which is why an activity theme will take precedence over the application (base) theme.
When there are multiple view overlays, how do views resolve the attributes? We’ll use the classic game, Connect 4 (also known as Four in a Row, 4 Gewinnt, Puissance 4…) to help us understand.
Here, each overlay is represented by a different set of coloured counters, and each column represents a specific attribute that can be set in a theme:
The red layer (on the bottom row) represents the application (base) theme. It hasn’t defined a value for fontFamily
.
We can specify a theme for our activity using the android:theme
attribute in the Android manifest. Since activity extends from ContextThemeWrapper
, we’re able to keep a keep a reference to our activity theme as well as the parent theme (via mBase
field in the ContextWrapper
class, which ContextThemeWrapper
extends); the activity theme doesn’t overwrite the base theme.
We can do the same by specifying android:theme
on a view in XML too. Here, the colorPrimary
value is set in our theme overlay (in the view), our activity theme, and the base theme:
Each attribute is resolved in isolation and is resolved using the outermost context first. So, if we’re looking for colorPrimary
we look only at that column, and we’ll start from the top (the latest overlay) working our way down. In this case, we’ll use the value defined by the view’s theme overlay, since that’s the latest theme overlay where colorPrimary
was defined.
The diagrams don’t show this clearly (sorry!), but theme overlays will be checked for each attribute, even if it doesn’t contain a definition for that value—it’ll only stop when it finds a value (or it’s exhausted all the themes). I.e. when we’re trying to resolve colorOnBackground
the green and yellow layers would be checked first, before finding the value in the red (base theme) layer.
Please let me know if you found this post helpful, or if you have any comments or questions (or corrections!).