As Android developers, we tend to focus primarily on the functionality of our apps. Functionality alone, however, is rarely enough. On Google Play, which hosts well over a million apps today, looks are just as important as functionality, if not more. If you find that hard to believe, I suggest you take a quick look at the apps in the Top Charts section of Google Play.
There are two approaches to modifying the look of Android apps. The first approach involves directly modifying the properties of views in layout XML files. This approach is feasible only if you are working on a simple app that has a small number of views and activities. The second approach involves creating and using custom styles and themes. If you are familiar with web development, the first approach is akin to using inline CSS styles, and the second approach is akin to using style sheets.
In this tutorial, you'll learn how to create custom styles and themes for your Android apps. You'll also learn how styles and themes are different from each other and how to use them properly.
1. Creating Styles
Styles are obviously applied to UI components. Therefore, let's start by creating a new empty activity and adding two TextView
elements to its layout XML file.
<TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="50dp" android:layout_marginTop="50dp" android:text="@string/brand_name" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /><TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="50dp" android:layout_marginTop="50dp" android:text="@string/brand_tagline" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView" />
As you can see in the above code, properties such as layout_width
, layout_height
, and layout_marginStart
etc. are explicitly included in the definition of each view and they have the exact same value. Lets say you wanted to change the layout_marginStart
value for these TextView
elements. You will have to make changes in two different places for it to work. The situation would get worse if there were more such elements.
One way to avoid the duplication and make the styling of these Views easier is to define a separate style that holds the values of all these common attributes. You can do so by right clicking on the TextView
and selecting Refactor > Extract > Style.
You will now see a dialog where you can give a name to the style and also select the attributes that should be included in it. Let the name be LeftAlignedView and select all the attributes except layout_marginTop
.
When you press OK, you'll see that the code for the first view has changed.
<TextView android:id="@+id/textView" android:layout_marginTop="50dp" android:text="@string/brand_name" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" style="@style/LeftAlignedView" />
The TextView
now has a style
attribute that points to the LeftAlignedView
style. You can take a look at the definition of the style by opening res/values/styles.xml.
<style name="LeftAlignedView"><item name="android:layout_width">wrap_content</item><item name="android:layout_height">wrap_content</item><item name="android:layout_marginStart">50dp</item></style>
Once a style has been created, you can apply it to any view. For example, here's how you would apply LeftAlignedView
to the second TextView
:
<TextView android:id="@+id/textView2" android:layout_marginTop="50dp" android:text="@string/brand_tagline" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView" style="@style/LeftAlignedView" />
Now, lets say you want to change the layout_marginStart
value for both the TextView
elements to 20dp. You could simply make the change in a single place and the layout would update accordingly as shown below:
2. Extending Styles
Android allows you to create styles that use other styles as a foundation. In other words, it allows you to extend existing styles. There are two different syntaxes you can follow while extending a style.
The first syntax is often called the implicit syntax and uses a dot notation. For example, here's how you create two derived styles, called PrimaryText and SecondaryText, using LeftAlignedView
as the parent style:
<style name="LeftAlignedView.PrimaryText"><item name="android:textColor">?attr/colorPrimary</item></style><style name="LeftAlignedView.SecondaryText"><item name="android:textColor">?attr/colorSecondaryVariant</item></style>
As you might have guessed, both LeftAlignedView.PrimaryText
and LeftAlignedView.SecondaryText
have all the properties of LeftAlignedText
. In addition to those, they have the android:textColor
property.
The second syntax for creating a derived style is usually referred to as the explicit syntax. It involves using a parent
attribute whose value is set to the name of the parent style. Here's a code snippet that defines a style called PrimaryText and SecondaryText.
<style name="PrimaryText" parent="LeftAlignedView"><item name="android:textColor">?attr/colorPrimary</item></style><style name="SecondaryText" parent="LeftAlignedView"><item name="android:textColor">?attr/colorSecondaryVariant</item></style>A
You should note that we have not supplied any hardcoded colors or direct resource references when specifying the value of textColor
attribute. This makes it easier to switch the styling of the whole app at once by switching themes. We will learn how to do that in the next section.
Applying derived styles is no different from applying normal ones.
<TextView android:id="@+id/textView" android:text="@string/brand_name" style="@style/PrimaryText" /><TextView android:id="@+id/textView2" android:text="@string/brand_tagline" style="@style/LeftAlignedView.SecondaryText" />
Most developers use the implicit syntax while extending their own styles, and the explicit syntax while extending platform styles.
You can also use a lot of other predefined styles with your views. For example, you can use the textAppearance
attribute and set its value to @style/TextAppearance.AppCompat.Display2
in order to apply a bunch of predefined typography styles. Take a look at the source file to see the rules applied when different styles are used.
<TextView android:id="@+id/textView" style="@style/PrimaryText" android:text="@string/brand_name" android:textAppearance="@style/TextAppearance.AppCompat.Display2" />
3. Creating Themes
All this while, we've only been applying styles to views that are inside an activity. Android allows you to apply styles to entire activities and applications too. When a style is applied to an activity or application, it becomes a theme.
You can find the theme file for your app by navigating to app > res > values > themes > themes.xml. Here are the default contents of the themes.xml file:
<resources xmlns:tools="https://schemas.android.com/tools"><!-- Base application theme. --><style name="Theme.StyleDemo" parent="Theme.MaterialComponents.DayNight.DarkActionBar"><!-- Primary brand color. --><item name="colorPrimary">@color/purple_500</item><item name="colorPrimaryVariant">@color/purple_700</item><item name="colorOnPrimary">@color/white</item><!-- Secondary brand color. --><item name="colorSecondary">@color/teal_200</item><item name="colorSecondaryVariant">@color/teal_700</item><item name="colorOnSecondary">@color/black</item><!-- Status bar color. --><item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item><!-- Customize your theme here. --></style></resources>
All apps created using the latest version of Android Studio use a theme called Theme.MaterialComponents.DayNight.DarkActionBar
. This theme along with many others is part of the Material Design Components library. There are other possible values that you can specify for the parent theme like Theme.MaterialComponents.Light.DarkActionBar
etc.
The theme file also contains a list of items which specify values for different attributes such as colorPrimary
, colorSecondary
, colorOnPrimary
etc. They determine the primary and secondary brand colors for your app. The attributes which include the word "On" determine the color of text and icons drawn on different surfaces. For example, the value of colorPrimary
attribute in the above theme is purple_500 which translates to #6200EE and the colorOnPrimary
has the value white which translates to #FFFFFF. This means that a regular button created using this theme will have a purple background and white text.
We can define another theme that uses a different color scheme by using the following code:
<style name="Theme.StyleRedBlue" parent="Theme.MaterialComponents.DayNight.DarkActionBar"><!-- Primary brand color. --><item name="colorPrimary">@color/red_500</item><item name="colorPrimaryVariant">@color/red_700</item><item name="colorOnPrimary">@color/white</item><!-- Secondary brand color. --><item name="colorSecondary">@color/blue_200</item><item name="colorSecondaryVariant">@color/blue_700</item><item name="colorOnSecondary">@color/black</item><!-- Status bar color. --><item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item><!-- Customize your theme here. --></style>
The color resources are defined in the another file called colors.xml. You can find it by navigating to res > values > colors.xml. Here are its contents:
<?xml version="1.0" encoding="utf-8"?><resources><color name="purple_200">#FFBB86FC</color><color name="purple_500">#FF6200EE</color><color name="purple_700">#FF3700B3</color><color name="teal_200">#FF03DAC5</color><color name="teal_700">#FF018786</color><color name="red_200">#FFEF9A9A</color><color name="red_500">#FFF44336</color><color name="red_700">#FFD32F2F</color><color name="blue_200">#FF90CAF9</color><color name="blue_700">#FF1976D2</color><color name="black">#FF000000</color><color name="white">#FFFFFFFF</color></resources>
You don't have to necessarily specify a value for all the color attributes in your file. The value for any color attributes that are missing from your theme file will be inherited from the parent theme. Your theme file could also have the following content:
<resources xmlns:tools="http://schemas.android.com/tools"><!-- Base application theme. --><style name="Theme.BasicTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar"></style></resources>
4. Applying Themes
Before we apply the theme we created, let us add a few commonly used views to the activity. Doing so will make it easier for us to notice the effects of the theme.
The following code creates some very large, large and normal sized TextView
elements. It also adds EditText
, Button
and CheckBox
elements to the UI.
<TextView android:id="@+id/textView" android:layout_marginTop="20dp" android:text="@string/brand_name" android:textAppearance="@style/TextAppearance.AppCompat.Display2" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" style="@style/LeftAlignedView.PrimaryText" /><TextView android:id="@+id/textView2" android:text="@string/brand_tagline" android:textAppearance="@style/TextAppearance.AppCompat.Large" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView" style="@style/LeftAlignedView.SecondaryText" /><TextView android:id="@+id/textView3" android:layout_marginTop="50dp" android:text="Your Email" android:textAppearance="@style/TextAppearance.AppCompat.Medium" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView2" style="@style/LeftAlignedView" /><EditText android:id="@+id/editTextEmailAddress" android:layout_marginTop="10dp" android:ems="10" android:inputType="textEmailAddress" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView3" style="@style/LeftAlignedView" /><TextView android:id="@+id/textView4" android:layout_marginTop="50dp" android:text="Your Password" android:textAppearance="@style/TextAppearance.AppCompat.Medium" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/editTextEmailAddress" style="@style/LeftAlignedView" /><EditText android:id="@+id/editTextPassword" android:layout_marginTop="10dp" android:ems="10" android:inputType="textPassword" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView4" style="@style/LeftAlignedView" /><CheckBox android:id="@+id/checkBox" android:layout_marginTop="20dp" android:text="Remember Me" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/editTextPassword" style="@style/LeftAlignedView" /><Button android:id="@+id/button" android:layout_marginTop="50dp" android:textSize="20sp" android:text="Log In" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/checkBox" style="@style/LeftAlignedView"/><Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="50dp" android:layout_marginEnd="50dp" android:text="Forgot Password?" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/checkBox" style="?attr/materialButtonOutlinedStyle" /><Button android:id="@+id/button3" android:layout_marginTop="10dp" android:text="Sign Up" android:textSize="20sp" app:backgroundTint="?attr/colorSecondaryVariant" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/button" style="@style/LeftAlignedView" />
With all the newly added views, the layout will look like this:
As you can see, the activity currently uses the default theme where the primary color shades are derived from purple and secondary color shades are derived from teal.
To apply Theme.StyleRedBlue
, the theme we created in the previous section, to your activity, open your project's manifest file and add an android:theme
attribute to the activity's definition. Set its value to @style/Theme.StyleRedBlue
.
<activity android:name=".MainActivity" android:theme="@style/Theme.StyleRedBlue"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity>
Similarly, you can apply the theme to your entire application by setting the value of the android:theme
attribute of the application's definition to @style/Theme.StyleRedBlue
.
If you take a look at your activity now, it should look very different.
How are Styles and Themes Different?
The concept of styles and themes can be a bit confusing for beginners because they seem to do very similar things and yet serve different purposes. In this section, I will provide a simple explanation to help clear up all your doubts.
Both themes and styles use the same <style>
syntax for their key-value pairs.
A style contains a collection of attribute values for specific views. This means key-value pairs that you define for a style could also be set inside the layout file. Another thing to keep in mind is that the styles that you create will usually target a single type of widget. This is because different widgets will have support for their own set of attributes.
A theme contains a collection of named resources which can be referenced by different styles, layouts and widgets etc. This basically means that a theme is not bound to any specific view or widget. It simply provides you a way of assigning semantic names to different resources. One example of this would be the colorPrimary
attribute. It isn't tied to any particular view but applies to the whole activity or app.
Conclusion
In this tutorial, you learned how to create and apply custom Android styles and themes. Feel free to use this knowledge to give new and better looks to your apps. However, try not to get too carried away—most Android users today are so accustomed to Material Design that deviating from its guidelines can annoy them.
To learn more about styles and themes, I suggest you go through the Styles and Themes Guide.