Contents

Hide contents

Real-World Android by Tutorials

Before You Begin

Section 0: 6 chapters
Show chapters Hide chapters

Section I: Developing Real World Apps

Section 1: 7 chapters
Show chapters Hide chapters
There is an updated edition of this book available! View Latest Edition

14 Style & Theme
Written by Subhrajyoti Sen

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

A polished user interface makes a good first impression. It can even be one of the reasons users like using your app. A key feature of a polished user interface is consistency in components across different sections of the app. These components include color schemes, shapes, typography and more. These days, another important feature of the user interface is having a dark theme.

Android lets you use styles and themes to achieve these goals and much more.

In this chapter, you’ll learn about:

  • Styles, themes and their differences.
  • The order of different modes of styling.
  • Using theme overlays to tweak specific attributes.
  • Adding styling support to custom views.
  • Adding dark theme support to your app.

As first step open, as usual, the starter project in the material for this chapter.

Defining styles and themes

Usually, you define styles and themes in res/styles.xml, which contains a collection of attributes and their values. These can be specific to a certain view or they can apply to a collection of views.

Structure of a style

A typical style looks like this:

<style name="LargeText">
  <item name="android:textSize">@dimen/large_text</item>
</style>
<style name="LargeRedText">
  <item name="android:textSize">@dimen/large_text</item>
  <item name="android:textColor">@android:color/red</item>
</style>

Structure of a theme

The structure of a theme is identical to that of a style:

<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
  <item name="colorPrimary">@color/colorPrimary</item>
  <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
  <item name="colorAccent">@color/colorAccent</item>
</style>

Style hierarchy

Android provides a wide variety of ways to set attributes in your app. For example, you can set view attributes in XML layouts, apply a style to the view and apply a theme to your activity or even the entire app.

Theme overlay

Sometimes, you want to modify the appearance of a View or ViewGroup but the attribute(s) you want to change derive from a theme. Take the example of MaterialButton.

<com.google.android.material.button.MaterialButton
  android:id="@+id/red_button"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:background="#DA2222"
  android:layout_marginTop="@dimen/default_margin"
  android:text="Red Button"
  app:layout_constraintBottom_toBottomOf="parent"
  android:layout_marginBottom="@dimen/default_margin"
  app:layout_constraintEnd_toEndOf="parent"
  app:layout_constraintStart_toStartOf="parent"
  app:layout_constraintTop_toBottomOf="@id/adopt_button" />
Figure 14.1 — A Green Button
Nejino 09.6 — U Hhior Halgot

<style name="ThemeOverlay.PetSave.RedButton" parent="">
  <item name="colorPrimary">#DA2222</item>
</style>
<com.google.android.material.button.MaterialButton
  android:id="@+id/red_button"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:theme="@style/ThemeOverlay.PetSave.RedButton"
  android:layout_marginTop="@dimen/default_margin"
  android:text="Red Button"
  app:layout_constraintBottom_toBottomOf="parent"
  android:layout_marginBottom="@dimen/default_margin"
  app:layout_constraintEnd_toEndOf="parent"
  app:layout_constraintStart_toStartOf="parent"
  app:layout_constraintTop_toBottomOf="@id/adopt_button" />
Figure 14.2 — A Red Button
Mijibi 37.4 — U Nik Jaqtiz

TextAppearance

textAppearance lets you define text-specific styling for a TextView and decouple it from the rest of the styling. One benefit of textAppearance is you can programmatically set a view to use it at any time, whereas you can only specify a style when the view inflates.

<style name="PetLabelTextAppearance" parent="TextAppearance.MaterialComponents.Headline3" >
  <item name="android:textSize">@dimen/large_text</item>
  <item name="android:textStyle">bold</item>
</style>
<TextView
  android:id="@+id/special_needs_label"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_marginStart="@dimen/default_margin"
  android:layout_marginTop="@dimen/default_margin"
  android:text="@string/special_needs"
  android:textAppearance="@style/PetLabelTextAppearance"
  app:layout_constraintStart_toStartOf="parent"
  app:layout_constraintTop_toBottomOf="@id/divider" />
Figure 14.3 — Using TextAppearance
Jadoto 76.7 — Ojads JedkAxweofukme

Setting up dark themes

Dark themes have dark background colors and light foreground colors, and the Material dark theme system helps you make dark options for your app. Some of the benefits of providing one are:

<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
  <!-- Customize your theme here. -->
  <item name="colorPrimary">@color/colorPrimary</item>
  <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
  <item name="android:colorBackground">@color/colorBackground</item>
  <item name="progressButtonStyle">@style/ProgressButtonStyle</item>
</style>

Understanding material color attributes

Before creating a dark theme, you need to understand the color system in Material Design components.

Adding a theme toggle

To let the user switch between themes, you’ll add a toggle, which will have three options:

<?xml version="1.0" encoding="utf-8"?>
<menu
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:android="http://schemas.android.com/apk/res/android">
  <item
    android:id="@+id/light_theme"
    android:title="Light theme"
    app:showAsAction="never" />
  <item
    android:id="@+id/dark_theme"
    android:title="Dark Theme"
    app:showAsAction="never" />
  <item
    android:id="@+id/follow_system"
    android:title="Follow System"
    app:showAsAction="never" />
</menu>
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
  // ...
  override fun onCreateOptionsMenu(menu: Menu): Boolean {
    val inflater = menuInflater
    inflater.inflate(R.menu.theme_options, menu) // HERE
    return true
  }
}
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
  // ...
  override fun onOptionsItemSelected(item: MenuItem): Boolean {
    val themeMode = when (item.itemId) {
      R.id.light_theme -> {
        AppCompatDelegate.MODE_NIGHT_NO
      }
      R.id.dark_theme -> {
        AppCompatDelegate.MODE_NIGHT_YES
      }
      else -> {
        AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
      }
    }
    AppCompatDelegate.setDefaultNightMode(themeMode)
    return true
  }
}
Figure 14.4 — Theme Options
Pajise 94.3 — Fnaju Eqvoeyh

Resolving dark theme inconsistencies

Use the toggle to switch to the dark theme and explore the app. Observe the same screens in both light and dark themes. Some of the inconsistencies you’ll notice are:

Using theme attributes

As Android developers, one of the first things you learn is not to hard code color values, but to use color resources instead. So instead of using #FFFFFF, you might define colorWhite and use this color resource throughout your app.

<com.google.android.material.floatingactionbutton.FloatingActionButton
  android:id="@+id/call"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_margin="@dimen/default_margin"
  android:contentDescription="@string/contact"
  android:src="@drawable/ic_call_24dp"
  android:visibility="gone"
  app:backgroundTint="@color/colorPrimary"
  app:layout_constraintBottom_toBottomOf="parent"
  app:layout_constraintEnd_toEndOf="parent"
  app:tint="@android:color/white"
  tools:visibility="visible" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
  //...
  app:tint="?attr/colorOnPrimary"
  tools:visibility="visible" />
Figure 14.5 — Floating Action Button Color
Yeruwu 98.6 — Xbeoduqd Ogteiy Kattow Kexuq

Fixing other hard-coded colors

Similarly, open fragment_search.xml and look at AppBarLayout. You’ll notice that background is a static color:

<com.google.android.material.appbar.AppBarLayout
  android:id="@+id/collapsible_search_params_container"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:background="@android:color/white"
  app:layout_constraintEnd_toEndOf="parent"
  app:layout_constraintStart_toStartOf="parent"
  app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.AppBarLayout
  android:id="@+id/collapsible_search_params_container"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:background="?attr/colorPrimarySurface"
  app:layout_constraintEnd_toEndOf="parent"
  app:layout_constraintStart_toStartOf="parent"
  app:layout_constraintTop_toTopOf="parent">
Figure 14.6 — Search Bar in Dark Mode
Qamici 62.1 — Kiodqr Cij et Zowm Noru

Using night colors

You might have noticed that you haven’t specified any separate color values for the dark theme, yet switching to dark theme displays different colors in many places. That’s because Material Components themes have default values for dark themes. If you want to tweak these values, you can do so by defining night color resources.

Figure 14.7 — New Resource Directory
Bukuma 88.5 — Hel Xadiotsu Bamutrumf

Figure 14.8 — New Resource Directory
Rikimo 91.0 — Tam Dineefni Mowelvuzh

Figure 14.9 — Colors for Night Mode
Yazabu 77.9 — Nefobw tap Pexjc Qoji

Figure 14.10 — Qualified Color Resources
Zoqiqo 46.54 — Wuanoyuos Cegaf Yayaeqnob

<color name="colorPrimary">#BA86FC</color>
<color name="colorPrimaryDark">#000000</color>
<color name="colorBackground">#000000</color>
<color name="colorBackground">#FFFFFF</color>
<item name="android:colorBackground">@color/colorBackground</item>
Figure 14.11 — Dark Theme in Action
Bilucu 53.59 — Fovm Wzise od Uwyaub

Styling custom views

Most of the views Android provides have good styling support out of the box. To give developers a good experience, it’s also important to provide styling support in your custom views. In this section, you’ll make ProgressButton styleable.

Adding styleable attributes

First, you need to modify your view so it can read attribute values from a style. To do this, you need to remove any hard-coded colors from the view.

private val textPaint = Paint().apply {
  isAntiAlias = true
  style = Paint.Style.FILL
  textSize = context.dpToPx(16f)
}

private val backgroundPaint = Paint().apply {
  isAntiAlias = true
  style = Paint.Style.FILL
}

private val progressPaint = Paint().apply {
  isAntiAlias = true
  style = Paint.Style.STROKE
  strokeWidth = context.dpToPx(2f)
}
<attr name="progressButton_backgroundColor" format="color" />
<attr name="progressButton_textColor" format="color" />
<attr name="progressButton_progressColor" format="color" />
val typedValue = TypedValue()
context.theme.resolveAttribute(android.R.attr.colorPrimary, typedValue, true)
val defaultBackgroundColor = typedValue.data
val defaultTextColor = Color.WHITE
val defaultProgressColor = Color.WHITE
val backgroundColor = typedArray.getColor(R.styleable.ProgressButton_progressButton_backgroundColor, defaultBackgroundColor)
backgroundPaint.color = backgroundColor

val textColor = typedArray.getColor(R.styleable.ProgressButton_progressButton_textColor, defaultTextColor)
textPaint.color = textColor

val progressColor = typedArray.getColor(R.styleable.ProgressButton_progressButton_progressColor, defaultProgressColor)
progressPaint.color = progressColor

Default styles

In the last chapter, you learned about the View constructor, which looked like this:

class ProgressButton @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr)
class ProgressButton @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0,
    defStyleRes: Int = 0
) : View(context, attrs, defStyleAttr, defStyleRes)
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ProgressButton, defStyleAttr, defStyleRes)
<attr name="progressButtonStyle" format="reference"/>

Defining your custom view’s style

The next step is to define the style for your custom view. Open styles.xml and add the following code:

<style name="ProgressButtonStyle">
  <item name="progressButton_backgroundColor">?attr/colorPrimary</item>
  <item name="progressButton_textColor">?attr/colorOnPrimary</item>
  <item name="progressButton_progressColor">?attr/colorOnPrimary</item>
</style>
<item name="progressButtonStyle">@style/ProgressButtonStyle</item>
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
  <!-- Customize your theme here. -->
  <item name="colorPrimary">@color/colorPrimary</item>
  <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
  <item name="colorAccent">@color/colorAccent</item>
  <item name="progressButtonStyle">@style/ProgressButtonStyle</item>
</style>

Setting defStyleAttr & defStyleRes

Your final step is to set the values of defStyleAttr and defStyleRes. Open ProgressButton.xml and change the default values of the constructor arguments as follows:

class ProgressButton @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = R.attr.progressButtonStyle,
    defStyleRes: Int = R.style.ProgressButtonStyle
) : View(context, attrs, defStyleAttr, defStyleRes)
Figure 14.12 — Styled Custom View
Muquqe 21.33 — Wvbfep Kojwif Fius

Key points

  • Use styles and themes for consistent UI elements throughout the app.
  • Styles apply to a specific view but themes apply to a view hierarchy.
  • Different styling modes have a different order of precedence.
  • Make your custom views styleable and provide a default style.
  • Use textAppearance to group character level styling attributes.
  • Extend a DayNight variant of an AppCompat or Material Components theme when adding a dark theme.
  • Use theme attributes as often as possible.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2022 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.

Unlock Now