Visualising Theme Overlays

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!).