Android Theming Basics

Application theming can be one of the most confusing questions in Android development. As the project continues to grow, it becomes increasingly difficult to maintain component styles and themed application. If you don’t have a good design system, you can end up with inconsistent designs and inappropriate colors in your application. A good understanding of styling and theming will help you create a consistent user experience throughout your application. Plus, if you’re thinking about migrating to Compose, a poor design system can create additional complications.

A good design system requires proper styling and theming. This helps create consistent and reusable styles for our components. But how do you actually create the right style and theme system?

There is no single answer to this question; it can be divided into 5 parts.

  • Attributes

  • Default style

  • Style versus theme

  • Theme overlay

  • TextAppearance

… … …

Attributes

It all starts with an attribute. Without attributes, there would be no characteristics in XML that we could define. Attributes are named values ​​that have their definition in the file attrs.xml… An attribute can be defined for both presentation and theme. For example, the attribute android:layout_width is a view attribute and colorPrimary – topic attribute.

The view attribute is set in the XML view either by setting directly on the tag, or indirectly using style (will be mentioned later) .. Let’s see how we can set the red background of a button using the view attribute android:backgroundTint

Note: The prefix android is used to access the built-in attributes.

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:backgroundTint="@color/red" />

Let’s say you want to change its background to white. You can do this by setting the attribute android:backgroundTint to white.

It’s good if you only change one button. But what if you want to change all red buttons to white? This can be achieved:

  • Using the theme attribute for android:backgroundTint

  • Create and style all buttons

Topic attribute Is an attribute that does not belong to any view and can be changed at the theme level.

To use the theme attribute for android:backgroundTint, let’s first define a custom theme attribute called myButtonBackground in attrs.xml

Adding a custom theme attribute:

<resources>
    <attr name="myButtonBackground" format="color" />
</resources>

The attribute type is set using the field format… The format can be specified as a single or multiple type, for example,

android:background format="reference|color"which accepts both drawable resource references (“reference”) and color (“color”).

Now you can use the attribute myButtonBackground to set the background of your button. But before that you need to set the value myButtonBackground… It must be defined in either the theme or the theme overlay to be used.

Setting the value of the theme attribute:

<style name="Theme.MyApp" parent="...">
    <item name="myButtonBackground">@color/red</item>
</style>

Then you can use this attribute to set the background of your buttons.

Setting the background of a button using a custom attribute:

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:backgroundTint="?attr/myButtonBackground" />

If you change the value of the attribute, the background of all buttons using the attribute will change. ?attr/myButtonBackground as background. Alternatively you can use ?myButtonBackground as an abbreviation instead of ?attr/myButtonBackground

But the installation android:backgroundTint on the myButtonBackground all buttons can be overwhelming. To solve this problem, we will create a style and apply it to all buttons using the default style.

Default style

Have you ever noticed that if you don’t give a button any background, you still get a background drawable? This is because the Button component has a default style like any other view. Default style used as the base presentation style.

Let’s check the default button style.

// Source code: 
// https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/Button.java
public class Button extends TextView {
  ...
  
  public Button(Context context, AttributeSet attrs) {
    this(context, attrs, com.android.internal.R.attr.buttonStyle);
  }
  
  public Button(Context context, AttributeSet attrs, int defStyleAttr) {
    this(context, attrs, defStyleAttr, 0);
  }
}

The default button style is set using the theme attribute R.attr.buttonStyle… This means that you can change the default style of all buttons in your application using this attribute.

Let’s change the style of the default button in our theme so that the background is red.

Setting the default button style using an attribute buttonStyle:

<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="Theme.StylesNThemes" parent="Theme.AppCompat.DayNight.DarkActionBar">
      ...
      <item name="android:buttonStyle">@style/MyButton</item>
    </style>

    <style name="MyButton">
      <item name="android:background">@color/red</item>
    </style>
</resources>

Then, when you create a button, you will receive the following.

Button with red background

Doesn’t this look like text with a red background? This is because the style MyButton does not inherit from any styles. For this reason, all buttons will only contain a background image attribute in their default style. Let’s check how the default button style looks in AppCompat

The default button style in AppCompat:

<style name="Widget.AppCompat.Button" parent="Base.Widget.AppCompat.Button"/>

<style name="Base.Widget.AppCompat.Button" parent="android:Widget">
    <item name="android:background">@drawable/abc_btn_default_mtrl_shape</item>
    <item name="android:textAppearance">?android:attr/textAppearanceButton</item>
    <item name="android:minHeight">48dip</item>
    <item name="android:minWidth">88dip</item>
    <item name="android:focusable">true</item>
    <item name="android:clickable">true</item>
    <item name="android:gravity">center_vertical|center_horizontal</item>
</style>

As you can see, these attributes are the base style for the button. Let’s point out Widget.AppCompat.Button as a parent of the style MyButton and change the attribute background on the backgroundTintbecause we only want to change the color, not the drawable.

Setting the parent style of a button MyButton:

<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="Theme.StylesNThemes" parent="Theme.AppCompat.DayNight.DarkActionBar">
      ...
      <item name="android:buttonStyle">@style/MyButton</item>
    </style>

    <style name="MyButton" parent="Widget.AppCompat.Button">
      <item name="android:backgroundTint">@color/red</item>
    </style>
</resources>

Then we get a button with a red background.

Button with red background

Style and theme

We already mentioned style and theme, but what is the difference between them? Both style and theme are a set of attributesbut the difference is in which case they are applied. Styles are to be applied to views, and themes are to actions or the entire application. For this reason, a style should only contain presentation attributes and a theme should only contain theme attributes.

You can change the presentation style in three ways:

  • Changing a view attribute in a layout file

  • Create a new style and apply it using a view attribute style in layout file

  • Specifying the default style

Let’s see how you can change the background of a button in a layout file.

Changing the background of a button in a layout file:

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/button_background" />

Now let’s check out how we can create a style and apply it to this button.

Button style with custom background drawable:

<resources>
   <style name="MyButton" parent="Widget.AppCompat.Button">
      <item name="android:background">@drawable/button_background</item>
    </style>
</resources>

Setting a custom style for the button:

<Button
    style="@style/MyButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

When applying a style, only presentation attributes… If you try to install any topic attribute inside style MyButton, it won’t work. For clarity, here’s an example:

  • Use theme attribute colorPrimary inside the drawable background of the button.

  • Change the value colorPrimary inside style MyButton

The background drawable that uses the attribute colorPrimary:

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <corners android:radius="2dp" />
    <solid android:color="?colorPrimary" />
    <padding
        android:bottom="4dp"
        android:left="8dp"
        android:right="8dp"
        android:top="4dp" />
</shape>

Changing the theme attribute colorPrimary inside the button style:

<resources>
    <style name="Theme.StylesNThemes" parent="Theme.AppCompat.DayNight.DarkActionBar">
      <item name="colorPrimary">@color/purple_500</item>
    </style>

    <style name="MyButton" parent="Widget.AppCompat.Button">
        <item name="android:background">@drawable/button_background</item>
        <item name="android:textColor">@color/white</item>
        <!-- Setting a theme attribute inside the MyButton style -->
        <item name="colorPrimary">@color/red</item>
    </style>
</resources>

Then, even though we set the foreground color to red, we get a button with a purple background.

Button with purple background

This is because the view only knows about its own attributes; The Button is unaware of the colorPrimary attribute, so it is ignored.

A view gets its attributes from a layout file or style attribute. If the presentation style includes a theme attribute, it will be ignored.

How, then, can you change the theme attributes for just one view? Here comes to the rescue overlay themes

Overlay themes

Overlay themes Is a technique used when overriding theme attributes for any view or view group. Theme overlays are very useful when you are updating the theme of a specific part of your application.

There are two steps to applying a theme overlay:

  • Create a style consisting of the theme attributes that you want to change.

  • Apply this style to your layout file with android:theme or programmatically with ContextThemeWrapper

Let’s continue with the above scenario where we change the background color of the button using the theme attribute colorPrimary… First we need to create a theme overlay style in which we will set colorPrimary

Overlay a theme for a button:

<!-- res/values/theme_overlays.xml -->
<resources>
    <style name="ThemeOverlay.StylesNThemes.Button.Red" parent="">
      <item name="colorPrimary">@color/red</item>
    </style>
</resources>

The theme overlay has no parent.

Also, it’s best to start naming the style with ThemeOverlayas it will be easier to distinguish from other styles. This naming technique is also used in Material Components and AppCompat

Let’s apply this overlay to a button in a layout file.

Applying a theme overlay using an attribute android:theme:

<Button
    android:theme="@style/ThemeOverlay.StylesNThemes.Button.Red"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

Be aware that if the theme overlay is applied to a group of views, it will also apply to all of its descendants… In other words, the theme of each child of a view group is overlaid when a theme overlay is applied to a view group.

Applying a theme overlay to a view group:

<!-- Every child of this LinearLayout will be using colorPrimary as red -->
<LinearLayout
    android:theme="@style/ThemeOverlay.StylesNThemes.Button.Red"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="I am red!" />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="I am red too!" />
</LinearLayout>

We can also implement theme overlay programmatically by wrapping the view context with ContextThemeWrapper

Using ContextThemeWrapper to overlay a theme:

class MyButton : MaterialButton {
    constructor(
        context: Context,
        attrs: AttributeSet
    ) : super(
        // It will override the contexts theme with the values inside theme overlay.
        ContextThemeWrapper(context, R.style.ThemeOverlay_StylesNThemes_Button_Red),
        attrs
    )
}

ContextThemeWrapper creates a new context (wrapping the given one) with its own theme.

TextAppearance

TextAppearance is a class that contains data only for styling attributes TextViewrelated to text (for example, textColor, textSizebut not species-related such as maxLines or drawableTop etc.).

TextAppearance set by attribute android:textAppearance on the TextView… Attribute android:textAppearance works the same as the attribute style… The main difference is in the order of priority between the two. style takes precedence over android:textAppearancewhich means that style will always take precedence over general attributes.

You may ask why we need it, because we can ask everything in style? The answer is that we only get the ability to set the attributes associated with the text, and this makes it reusable for everyone. TextViewsleaving the attribute style free.

For example, let’s create the appearance of the text for the title.

Using style TextAppearance.StylesNThemes.Header as a decoration for the appearance of the text:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- You can also use TextAppearance as a parent but then you have to customize everything
     to look like header. Since there is already a style for header in both Material and AppCompat
     we just use one of them depending on the our applications base theme.-->
    <style name="TextAppearance.StylesNThemes.Header" parent="TextAppearance.MaterialComponents.Headline1">
        <!-- If any attribute which is not supported by TextAppearance is set here, 
        it will not be picked by TextView (Assuming this style is set with android:textAppearance). -->
        <item name="android:textSize">120sp</item>
    </style>
</resources>

As you can see, the attribute style for TextView is free and can be used to customize other presentation attributes. You can also set android:textAppearanceby creating a style.

Let’s create a one-line heading style.

One line header style:

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:textAppearance="@style/TextAppearance.StylesNThemes.Header"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Now you can set this style using the style attribute and reuse it for any text.

Applying a one-line heading style to a text view:

<resources>
    <style name="Widget.StylesNThemes.TextView.Header.SingleLine" parent="Widget.MaterialComponents.TextView">
        <item name="android:singleLine">true</item>
        <!-- If we were not using text appearance then we had to set all attributes inside 
         TextAppearance.StylesNThemes.Header here -->
        <item name="android:textAppearance">@style/TextAppearance.StylesNThemes.Header</item>
    </style>
</resources>

If you want to dive deeper into the topic of text appearance and style priority, I highly recommend you read this article

… … …

Conclusion

Attributes are a key concept in styling and theming. Use view attributes for their style and theme attributes for application, Activity, or view hierarchy. If you want to change the style for all instances of a given view type across the entire application, the default styles are fine. Theme overlays are used to override theme attributes, and you can even use them for views in a given hierarchy. Text appearance can help you shape text attributes TextViews via android: textAppearanceleaving the attribute style free.


The translation of the material was prepared as part of the course “Android Developer. Professional”.

Android developers with at least 3 years of experience and everyone who is interested is invited to the online intensive “Full coverage. We cover the Android application with unit / integration / UI tests “
On the intensive we:
– Let’s learn how to cover android application with unit / integration / UI tests.
– Let’s consider various cases: coverage of suspend functions by tests, RX chains.
– Explore popular test writing tools.
– We will discuss the best practices for test coverage.

Similar Posts

Leave a Reply Cancel reply