Android Styles & Themes for developers
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.TextViewEditText
:?attr/editTextStyle
with default valueWidget.AppCompat.EditText
Button
:?attr/buttonStyle
with default valueWidget.AppCompat.Button
Toolbar
:?attr/toolbarStyle
with default valueWidget.AppCompat.Toolbar
Checkbox
:?attr/checkboxStyle
with default valueWidget.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
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 !