Android Styles & Themes for developers

le 09/12/2016 par Pierre Degand
Tags: Software Engineering

For beginner Android developer or the more experienced ones who don't do much of the UI work, understanding the difference between styles and themes and how they should be used can be very difficult to understand.

With AppCompat being a must have in every app and as it's relying A LOT on themes and style, understanding all this can be very frustrating when it comes to customizing its default behaviors.

With this article, I'll try to explain what this is all about and how it can helps you into your app's UI code.

What is a style ?

As an Android developer you probably already wrote some UI code in your XML layouts like this one :

<android.support.v7.widget.Toolbar
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:background="@drawable/bg_gradient"
  app:title="My app title"/>

How does Android actually knows that this Toolbar should be drawn with our gradient background and with the proper title ?

If you look at the source code of Android, you'll notice that every single widget class have at least 3 constructors (or 4 if you are reading the Lollipop and more source code). Let's explain the purpose of each constructors.

Here are the constructors of a very cool widget, the Toolbar from appcompat-v7 :

public Toolbar(Context context) {
  this(context, null);
}

public Toolbar(Context context, @Nullable AttributeSet attrs) {
  this(context, attrs, R.attr.toolbarStyle);
}

public Toolbar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  ...
}

The first one is simply the constructor you use when you are creating views directly in Java code.

The second one, with only the AttributeSet is the constructor used by the LayoutInflater when your XML is inflated. This AttributeSet contains all the attributes we specified in our view when we wrote it in XML. The Toolbar will read this AttributeSet and look for some known attributes like title or background (among others attributes used by Toolbar and View) and then call the corresponding methods like setTitle() or setBackground() to properly configure the widget the way we wrote it in XML.

Let's say that in our XML, we instead wrote our Toolbar like this :

<android.support.v7.widget.Toolbar
  style="@style/DefaultToolbar"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  app:title="My app title"/>

with our res/values/styles.xml file containing the following :

<style name="DefaultToolbar"
  <item name="android:background">@drawable/bg_gradient</item>
  <item name="titleTextColor">@android:color/red</item>
</style>

The set of attributes given to the the constructor will contains each of the attributes specified in the view's XML and the attributes specified in the style. If a specific attribute is defined in both style and directly in the XML of the View, the one in the XML will be selected.

In other word, we can finally say that, in Android, a style is a set of attributes given to a View either directly in XML or through the style parameter.

Introducing Themes

As I said earlier, widgets typically have 3 constructors but we only covered the first two. Before going deep into the third constructor, let's talk about Themes.

Theme is basically a super Style applied to an entire Activity and all it's containing Views.

This means that if I declare the following Theme :

<style name="CustomTheme" parent="@style/Theme.Appcompat">
  <item name="android:text">Default text.</item>
</style>

and I apply this theme to my Activity in the AndroidManifest.xml file, every single views inflated in this Activity will at least receive in its AttributeSet the attribute text with the value specified in the Theme.

So, if we declare in our Activity's view XML the following TextView :

<TextView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"/>

The TextView will be displayed with the text "Default text." even though we never wrote it in the Activity's XML.

Of course, if you specify a theme attribute like we just did but in your Views's XML or style you override the same attribute, the overriden value will be used.

Theme's Style-Attributes

Even though a Theme is mainly a big Style applied everywhere, it can also store some references, or style-attributes, to some others resources. Theses references can the be used by other styles or views to have a specific behavior defined by the theme.

For example, in AppCompat themes you could find the colorAccent style-attribute. Let's explain how I could define our own style-attribute like colorAccent.

First, the attribute is defined in an <attr/> block (usually this block is written in res/values/attrs.xml).

<?xml version="1.0" encoding="UTF-8"?>
<resources>
  <attr name="favoriteColor" format="color"/>
</resources>

This tells Android "OK, I'm defining a new style-attribute called favoriteColor, it will reference a color and any theme can specify the value of this attribute."

Now, in our App theme, I can specify this style-attribute like any other attributes:

<style name="AppTheme" parent="@style/Theme.Appcompat">
  <item name="favoriteColor">#FF00FF</item>
</style>

And finally, this attribute can be used anywhere as long as this Theme is applied in the Activity. For example, in the xml of an Activity with the AppTheme, I could do

<TextView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:textColor="?attr/favoriteColor">

Note the notation with ?attr/, it means that the value is a style-attribute defined in the theme. If this attribute is not found in the Activity's theme, the app will crash.

The real power of the style-attributes is that they can refer to anything ! A color, a drawable, a dimen, an integer and even ... another style !

Default Styles

Let's jump all the way to the beginning at the different constructors of the widgets because I actually never explained the third one.

As you probably noticed, the second constructor is only calling the third one with a fixed parameter : R.attr.toolbarStyle.

This 3rd parameter is actually ... a reference to a style-attribute referencing a style resource that supplies default values for the view !

This attribute toolbarStyle is the Default Style of the Toolbar !

If you read the source code of AppCompat, you will see in any Theme.AppCompat (or its parent themes) the following reference :

<style name="Base.V7.Theme.AppCompat.Light" parent="Platform.AppCompat.Light">
  <item name="toolbarStyle">@style/Widget.AppCompat.Toolbar</item>
</style>

And you can see the values of this default style on AppCompat source code.

When constructing a View, the default style is used with the AttributeSet to resolve the final attributes of the view. Every attributes specified in the default style that are not present in the AttributSet will be retrieved from the default style.

If an attribute from the AttributeSet overrides on defined in the default style, the value from the AttributeSet is used.

Here is a couple of default styles from AppCompat with default value of some common widgets :

  • TextView: ?android:attr/textViewStyle with default value in Material theme Widget.Material.Light.TextView
  • EditText: ?attr/editTextStyle with default value Widget.AppCompat.EditText
  • Button: ?attr/buttonStyle with default value Widget.AppCompat.Button
  • Toolbar: ?attr/toolbarStyle with default value Widget.AppCompat.Toolbar
  • Checkbox: ?attr/checkboxStyle with default value Widget.AppCompat.CompoundButton.CheckBox

Customizing AppCompat

Now that you understand how all of theses themes and styles are working, you are now ready to customize every widgets of your applications.

You want a custom Toolbar all over your app without having to copy/paste 10 lines of XML in every Activity's view ?

You can just define a custom style that extends from the default Toolbar style and apply your new custom style to you application theme :

<style name="SuperCustomToolbar" parent="Widget.AppCompat.Toolbar">
  <item name="titleTextColor">#FF00FF</item>
  <item name="subtitleTextColor">#00FF00</item>
  <item name="background">#222222</item>
</style>

<style name="AppTheme" parent="Theme.AppCompat.Light">
  <item name="toolbarStyle">@style/SuperCustomToolbar</item>
</style>

In your AndroidManifest.xml, apply this theme to your application

<application
 ...
 theme="@style/AppTheme">
  <activity android:name=".MainActivity"/>
</application>

And in your activity_main.xml layout file, simply written

<android.support.v7.widget.Toolbar
  android:layout_width="match_parent"
  android:layout_height="wrap_content"/>

And VOILA ! Your Toolbar will be styled with your custom style.

On a final note, I can only advise you to read the source code of Android and AppCompat to find what are the default style-attribute for every widget and to which style theses attributes refer to.

You should probably have the source code of the support libraries available directly in Android Studio (cmd+click on a class name to go to the source) or you can browse it directly in Github here.

Always look for the constructors of the different widgets, this will help you find the default syle-attribute then browse the AppCompat's theme you are depending on to find the exact definition of the default style.

Here is a quick how-to (finding the default toolbarStyle value) :

//giphy.com/embed/3oz8xuz9yvNHfIZHtm

via GIPHY

If you want to read more about themes and style, you can read the official documentation or you can watch this talk from Google I/O 2016: Android themes & styles demystified

Have fun customizing your AppCompat themes !