The Android styling system offers a powerful way to specify your app’s visual design, but it can be easy to misuse. Proper use of it can make themes and styles easier to maintain, make branding updates less scary and make it straightforward to support dark modes. This is the first in a series of articles where Chris Banes and I will set out to demystify Android styling so that you can make stylish apps without pulling your hair out.
In this first article, I’ll take a look at the building blocks of the styling system: themes and styles.
Both themes and styles use the same
syntax but serve very different purposes. You can think of both as key-value stores where the keys are attributes and the values are resources. Let’s take a look at each.
A style is a collection of view attribute values. You can think of a style as a
Mapview attribute, resource>. That is the keys are all view attributes i.e. attributes that a widget declares and you might set in a layout file. Styles are specific to a single type of widget because different widgets support different sets of attributes:
Styles are a collection of view attributes; specific to a single type of widget
As you can see, each of the keys in the style are things you could set in a layout:
Extracting them to a style makes it easy to reuse across multiple views and maintain.
Styles are used by individual views from a layout:
Views can only apply a single style — contrast this to other styling systems such as css on the web where components can set multiple css classes.
A style applied to a view only applies to that view, not to any of its children. For example, if you have a
ViewGroup with three buttons, setting the
InlineAction style on the
ViewGroup will not apply that style to the buttons. The values provided by the style are combined with those set directly in the layout (resolved using the styling precedence order).
A theme is a collection of named resources which can be referenced later by styles, layouts etc. They provide semantic names to Android resources so you can refer to them later e.g.
colorPrimary is a semantic name for a given color:
These named resources are known as theme attributes, so a theme is
Maptheme attribute, resource>. Theme attributes are different from view attributes because they’re not properties specific to an individual view type but semantically named pointers to values which are applicable more broadly in an app. A theme provides concrete values for these named resources. In the example above the
colorPrimary attribute specifies that the primary color for this theme is teal. By abstracting the resource with a theme, we can provide different concrete values (such as
colorPrimary=orange) in different themes.
Themes are a collection of named resources, useful broadly across an app
A theme is similar to an interface. Programming to an interface allows you to decouple the public contract from the implementation allowing you to provide different implementations. Themes play a similar role; by writing our layouts and styles against theme attributes, we can use them under different themes, providing different concrete resources.
Roughly equivalent pseudo-code:
Which allows you to vary the way that
MyView is rendered, without having to create variants of it:
You can specify a theme on components which have (or are) a
The power of themes really comes from how you use them; you can build more flexible widgets by referencing theme attributes. Different themes provide concrete values at a later time. For example, you might wish to set a background color on a section of your view hierarchy:
Rather than setting a static color (
#ffffff or a
@color resource) we can delegate to the theme by using the
?attr/themeAttributeName syntax. This syntax means: query the theme for the value of this semantic attribute. This level of indirection allows us to provide different behavior (e.g. providing a different background color in light and dark themes) without having to create multiple layouts or styles which are mostly identical but for a few color variations. It isolates the elements that are changing within the theme.
?attr/themeAttributeNamesyntax to query the theme for the value of this semantic attribute
Theme is accessed as a property of a
Context and can be obtained from any object which is or has a
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
ViewGroup applies to all the
Views within it (in contrast to styles which only apply to a single view).
This can be extremely useful, say if you want a dark themed section of an otherwise light screen. Read more about this behavior here.
Note that this behavior only applies at layout inflation time. While
Context offers a
setTheme method, or
Theme offers an
applyStyle method, these need to be called before inflation. Setting a new theme or applying a style after inflation will not update existing views.
Understanding the different responsibilities and the interaction of styles and themes, helps to keep your styling resources more manageable.
For example, say you have a blue theme for your app, but some Pro screens get a fancy purple look and you want to provide dark themes with tweaked colors. If you tried to achieve this using only styles, you would have to create 4 styles for the permutations of Pro/non-Pro and light/dark. As styles are specific to a type of view (
Switch etc) you’d need to create these permutations for each view type in your app.