Android: Light & Dark Theme with Material Components

android

In essence, a dark theme displays mostly dark surfaces with lodw light. It contrasts with light themes, where white backgrounds and light surfaces predominate.

What are the advantages of dark themes? How are they implemented in Android? Can a light and a dark theme live together in the same app? In this post I give you the answers. A second part explains how to create a preferences screen with a theme selector. The approach is purely technical—we’ll talk about code, not graphic design.


>> Read this post in Spanish here <<

Contents

  1. The rise of darkness
  2. Sample project
  3. Themes with Material Design and Material Components
  4. Android dark mode
  5. Light and dark theme in the same app with DayNight
    1. The magic of DayNight themes
    2. Color naming
    3. Theme-specific styles and resources
  6. Code

The rise of darkness

Apple boosted the popularity of dark themes with the release of macOS Mojave (2018) and iOS 13 (2019). These operating systems let users choose either a traditional or a dark-style interface. They also enable developers to include a dark theme in their apps that is activated when the OS interface is the dark one. Over time, this synchronization of styles spread to some websites, such as Google.

Android and Windows copied incorporated these features almost immediately. But in favor of Android I note that, in the beginning, its style was dark (Android 2, 2010).

android ejemplos tema oscuro

Advocates of dark themes claim several benefits. The most obvious is that the increased contrast makes screen content more visible in low-light conditions. Although this increase usually requires an extra screen brightness, this high brightness does not dazzle as it would with a light theme because of its white background.

Another benefit relates to energy efficiency. Dark themes save energy because they require less brightness; even OLED displays turn off black pixels. That’s why Android’s power-saving mode sets the dark theme as the operating system’s default.

Let’s put aside the pragmatic arguments and consider the look and feel. Color highlighting on black backgrounds and surfaces brings designers and UX (‘user experience’) experts new possibilities. Spotify is a good example. In 2014, the company redesigned its apps and websites to give them a unified style based on a dark aesthetic that survives today. In fact, it became a distinctive element of the brand. This news, “Why Spotify went black”, explains the reasons for the change. One of them: “A darker color scheme accentuates the cover art, photographs of artists, and the most important navigation buttons, like play.”

And what about the users? Even today, they perceive dark styles as novel and modern. Light themes, being the usual, may seem boring and unoriginal.

Whatever the reasons behind their use, time has proven that the love for dark themes wasn’t a one-night stand but a romance that stills endures. Indeed, we programmers find them well-established in our work tools: IntelliJ, Visual Studio Code, GitHub, Android Studio… Material Design supports them, and Google eases their development in Android apps. Let’s see how.

Sample project

The main technical features of the sample project:

  • Android Studio Giraffe 2023.3 with Gradle 8.1.3.
  • Java 17. Yet the code is so simple that you can easily adapt it to Kotlin if you prefer that language.
  • Android API 34.
  • Material Components 1.10. This library is a collection of components for building graphical interfaces. They follow the guidelines of Material Design.

The application has a single screen. It consists of a ConstraintLayout that contains these components:

  • A MaterialToolbar that serves as the application bar (action bar or app bar). It includes a drop-down menu.
  • A TextInputLayout input field for typing an email address.
  • A button that verifies the formatting of the text entered into the TextInputLayout.
  • A toggle (Switch).
  • A BottomAppBar with an embedded Floating Action Buttom (FAB).

Here’s the structure.

The code is irrelevant to the tutorial, so I link the layout and the activity.

In the second part of the tutorial, we’ll add a settings screen that lets the user select the theme.

Themes with Material Design and Material Components

Let’s move on to the visual theme of the app. As a parent theme, you must pick one provided by Material Components. You’ll find them with the name Theme.MaterialComponents (Material Design 2) and Theme.Material3 (Material Design 3). We’ll go for Material 2, which is more widespread than version 3 at the moment.

If you want a light theme, pick Theme.MaterialComponents.Light, or one of its variations, as a parent theme. In the sample project, Theme.MaterialComponents.Light.NoActionBar is the right one because I don’t want the Android system’s top action bar. Instead, I display a custom action bar using the MaterialToolbar component.

Do you prefer a dark theme? Then choose Theme.MaterialComponents.NoActionBar, the dark version of the previous one.

You customize the theme in the /res/values/themes.xml file:

<resources xmlns:tools="http://schemas.android.com/tools">

    <style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
        <item name="colorPrimary">@color/purple</item>
        <item name="colorPrimaryVariant">@color/purple_dark</item>
        <item name="colorSecondary">@color/red</item>
        <item name="colorOnPrimary">@color/white</item>
        <item name="colorOnSecondary">@color/white</item>
        <item name="colorError">@color/red</item>
       <!-- bottom bar -->
        <item name="bottomAppBarStyle">@style/Widget.MaterialComponents.BottomAppBar.PrimarySurface</item>
        <!-- toolbar -->
        <item name="toolbarStyle">@style/Widget.MaterialComponents.Toolbar.PrimarySurface</item>
        <item name="actionOverflowButtonStyle">@style/ToolbarStyle.Overflow</item>
        <item name="toolbarNavigationButtonStyle">@style/Toolbar.Button.Navigation.Tinted</item>
        <!-- menús -->
        <item name="popupTheme">@style/Theme.MaterialComponents.Light</item>
        <!-- status bar -->
        <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
    </style>

    <style name="ToolbarStyle.Overflow" parent="Widget.AppCompat.ActionButton.Overflow">
        <item name="android:tint">@color/white</item>
    </style>

    <style name="Toolbar.Button.Navigation.Tinted" parent="Widget.AppCompat.Toolbar.Button.Navigation">
        <item name="tint">@color/white</item>
    </style>

</resources>

The third line defines a theme named AppTheme. The rest of the file overrides some of the many items (colors, component styles, and so on) from Theme.MaterialComponents.Light.NoActionBar. Since the parent theme already provides a complete visual styling, override only the items you want to customize.

As the Material Design specifications state, the graphical components of Material Components use specific colors according to these predefined color slots. For instance, if you set the colorPrimary slot to blue, all components will apply that same blue to their visual items colored with the colorPrimary slot. 

The first AppTheme items set the color for some of the slots:

  • colorPrimary: the primary and distinctive color of the app. It’s the color of your brand.
  • colorprimaryVariant: an alternative version of the primary color, darker or lighter..
  • colorSurface: the background color of most components, such as the bottom sheet menu or the card.
  • colorSecondary: color that highlights certain elements, such as FAB buttons and the Switch status indicator.
  • colorOnSecondary: the color of the elements displayed on top of those colored with colorSecondary. The color slots whose name begins with the prefix on share this purpose (colorOnPrimary, colorOnSurface…).
  • colorError: it’s what it seems. In the example, the color of the error message associated with the text input.

App colors are declared in the file /res/values/colors.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="purple" type="color">#9a28a1</color>
    <color name="purple_dark" type="color">#6c0f74</color>
    <color name="red" type="color">#af1541</color>
    <color name="white">#ffffff</color>
</resources>

Later, I’ll describe a better way to name the colors.

After the colors, AppTheme customizes the bottom bar, the MaterialToolbar, the status bar, and the menu. In the case of MaterialToolbar, the white color of the icons showing the menu (the three vertical dots) and navigation (the “Back” arrow) is set with separate styles.

Some components, unfortunately, don’t allow the customization of their style within the theme. You must set the style when you add the component to a layout. That’s the case of TextInputLayout:

 <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/text_input_layout_email"
        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"

You must declare the theme in the manifest:

<application
        android:name=".LightDarkApplication"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme">

Voilà! This is how the screen looks with Theme.MaterialComponents.Light.NoActionBar.

With Theme.MaterialComponents.NoActionBar:

<style name="AppTheme" parent="Theme.MaterialComponents.NoActionBar">

It looks good, if you consider my poor aesthetic taste.

Android dark mode

You already know how to set the theme of your app. Still, you need to consider how Android works.

In Android 9 (2018), the dark mode, or dark theme, appeared. It was only available for testing purposes, and hence you activated it in the developer options. The official debut of this feature came a year later with Android 10. Since then, you activate the dark mode in the system settings, in the “Settings” -> “Display” section, or by turning on the power saving mode.

The dark mode applies a dark style to the operating system. Okay, but what about the apps? You set the behavior of an app regarding Android’s dark mode in the app’s theme declaration via the android:forceDarkAllowed boolean property:

<style name="AppTheme" parent="Theme.MaterialComponents.Light">
        <item name="android:forceDarkAllowed" tools:targetApi="q">true</item>

Available options:

trueAndroid gives a dark style to the app when dark mode is activated.
falseIgnores the Android theme setting
(predeterminado)true if the app uses a light theme, such as Theme.MaterialComponents.Light; false otherwise.
According to the table, if you have a light theme and don’t set forceDarkAllowed, when Android switches to dark mode it will give the app a dark color palette. The result can be acceptable… or disastrous. To make things worse, testing how the app looks offers few guarantees, as its appearance depends on the device. For example, MIUI 12 (Xiaomi) ’s coloring differs from the coloring forced by the Android emulator.

Therefore prudence advises you to set forceDarkAllowed to false when your app only has a light theme. This decision prevents Android’s dark mode from ruining your GUIs. And if you provide a dark theme or a light and a dark theme with the technique I’ll explain in the following section, forget about setting forceDarkAllowed, for the default value (false) is already the suitable one.

Light and dark theme in the same app with DayNight

The magic of DayNight themes

WhatsApp, Telegram, Facebook… popular apps provide light and dark themes. Many users are used to this perk and expect the same from other apps. “How can I offer this feature?” you may ask yourself. The key is choosing a DayNight theme as your parent theme. These themes have a superpower: they apply a light or dark style according to Android’s theme.

<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">

That’s easy! Unfortunately, there’s bad news. Styles and colors must fit both light and dark themes, and sometimes the same style or color is inappropriate for both themes. But before addressing this problem, the next section describes the best way to name an app’s colors.

Color naming

As I said, colors are defined in the /res/values/colors.xml. Google suggests naming the colors following a convention that facilitates the identification of each color and its variations. The goal is to have a unique color palette for the app, regardless of the theme. This palette simplifies the configuration of dual themes.

In particular, Google recommends naming each different color considered as a base or pure color with the suffix 500. Imagine you need a red color. The red you choose as a base color will be named red_500. From there, define the range of red colors you need. How? Increase the suffix number by one hundred units as you intensify the base color until you reach a maximum value of 900, which represents the most intense red you want in your palette.

The number decreases if you do the opposite and create less saturated tones of the base color. The minimum number is 50. By the way, these tones are the characteristic tones of dark themes. The progression of colors -the leap from one number to the next- must be consistent.

The picture below depicts a color palette that meets the proposed criteria.

Generating a palette may seem complicated. Quite the opposite: choose the base colors and use free tools like this one or this one.

The new colors for the project:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <item name="purple_500" type="color">#9a28a1</item>
    <item name="purple_900" type="color">#6c0f74</item>
    <item name="purple_300" type="color">#cd94d0</item>

    <item name="red_500" type="color">#af1541</item>
    <item name="red_300" type="color">#c75b7a</item>

    <color name="white_50">#ffffff</color>

    <color name="black_900">#000000</color>

    <color name="grey_900">#252525</color>

    <color name="cyan_500">#00bcd4</color>

</resources>

Thanks to this nomenclature system, we have a friendly color palette that is easy to understand, use, and change.

Theme-specific styles and resources

Let’s force the dark mode on Android and check the sample project, which is now set with a DayNight theme.


At the very least, we should adjust the color of the status bar. It looks weird.

We can define resources (colors, styles, drawables…) specific to the dark mode if we add the qualifier night to the name of the directories containing these resources. Thus we would have directories like values-night or drawables-night. When the dark mode is activated, the resources in the night directories replace their default equivalents, those in the directories without the night qualifier. This way, we polish the dark theme without affecting the light one.

Let’s try the above strategy. We have to split the main theme into three themes: common, light, and dark. The file with the default theme (/res/values/themes.xml) contains both the common theme and styles as well as those specific to the light mode:

<resources xmlns:tools="http://schemas.android.com/tools">

    <!-- Base theme for light and dark -->
    <style name="Base.AppTheme.LightDark" parent="Theme.MaterialComponents.DayNight.NoActionBar">
        <item name="android:forceDarkAllowed" tools:targetApi="q">false</item>
        <item name="colorOnPrimary">@color/white_50</item>
        <item name="colorOnSecondary">@color/white_50</item>
        <item name="colorError">@color/red_300</item>
        <item name="bottomAppBarStyle">@style/Widget.MaterialComponents.BottomAppBar.PrimarySurface</item>
        <!-- toolbar -->
        <item name="toolbarStyle">@style/Widget.MaterialComponents.Toolbar.PrimarySurface</item>
        <item name="actionOverflowButtonStyle">@style/ToolbarStyle.Overflow</item>
        <item name="toolbarNavigationButtonStyle">@style/Toolbar.Button.Navigation.Tinted</item>
        <!-- menus -->
        <item name="popupTheme">@style/Theme.MaterialComponents.DayNight</item>
    </style>

    <style name="ToolbarStyle.Overflow" parent="Widget.AppCompat.ActionButton.Overflow">
        <item name="android:tint">@color/white_50</item>
    </style>

    <style name="Toolbar.Button.Navigation.Tinted" parent="Widget.AppCompat.Toolbar.Button.Navigation">
        <item name="tint">@color/white_50</item>
    </style>

    <!-- App theme (light version)-->
    <style name="AppTheme.LightDark" parent="Base.AppTheme.LightDark">
        <item name="colorPrimary">@color/purple_500</item>
        <item name="colorPrimaryVariant">@color/purple_900</item>
        <item name="colorSecondary">@color/red_500</item>
        <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
    </style>

</resources>

The file /res/values-night/themes.xml contains the dark version of the theme. In it, we redefine the styles we want that already exist in the other themes.xml file.

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!-- App theme (dark version)-->
    <style name="AppTheme.LightDark" parent="Base.AppTheme.LightDark">
        <item name="colorPrimary">@color/purple_300</item>
        <item name="colorPrimaryVariant">@color/purple_500</item>
        <item name="colorSecondary">@color/cyan_500</item>
        <item name="colorSurface">@color/grey_900</item>
        <item name="android:statusBarColor">@color/black_900</item>
    </style>

</resources>

AppTheme.LightDark changes some colors of the homonymous style located in the other themes.xml file. For example, the primary color changes from purple_500 to purple_300. According to the color naming convention, I switched from the palette’s base purple to a lighter one.

The two themes face to face.

As you can see, the new colorPrimary (textfield, button), colorSecondary (FAB, Switch), and colorSurface (Toolbar, BottomBar, menu) were applied.

The following video shows the complete example. It includes the theme selector developed in the second part of the tutorial.

Code

Download the project from GitHub. For more information, consult “How to import repositories from GitHub with Git, Eclipse and Android Studio\IntelliJ”.

Deja un comentario

Este sitio utiliza Akismet para reducir el spam. Conoce cómo se procesan los datos de tus comentarios.