Hands-on with Material Components for Android: Selection Controls

This post will be covering the features and APIs of Radio Button, Checkbox and Switch components. To find out how to handle initial setup of Material Components for Android (including the Gradle dependency and creating an app theme), please see my original post:

Setting up a Material Components theme for Android

Attribute by attribute

medium.com

Selection Controls are small components for scenarios in which users need to select options or toggle settings. They are typically found on settings screens and dialogs.
From a design perspective, there are three main types of selection controls which can be used in different scenarios:
  • Radio Buttons: A circular control with two possible states; selected or unselected. Has single-select behavior when in a group of other radio buttons (i.e. Only one control can be selected at a time).
  • Checkboxes: A square control with two possible states; checked or unchecked. Has multi-select behavior when in a group of other checkboxes (i.e. Multiple controls can be selected at a time).
  • Switches: A control consisting of a thumb and a track. Has two possible states; on or off.

Basic usage 🏁

MaterialRadioButtonMaterialCheckBox or SwitchMaterial can be included in your layout like so:
<LinearLayout
  ...>

  <com.google.android.material.radiobutton.MaterialRadioButton
    android:id="@+id/radioButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Radio Button" />

  <com.google.android.material.checkbox.MaterialCheckBox
    android:id="@+id/checkbox"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Checkbox" />  <!-- Note: ID cannot be 'switch' as this is a Java keyword -->
  <com.google.android.material.switchmaterial.SwitchMaterial
    android:id="@+id/switchMaterial"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Switch" />

</LinearLayout>

Setting and listening for checks 👂

All of the selection controls extend from the base CompoundButton class. As such, they inherit a checkable behavior that can be toggled both programmatically and via touch interaction.
A selection control can be programmatically toggled:
radioButton.isChecked = truecheckbox.isChecked = trueswitchMaterial.isChecked = true
Listening for checked/unchecked state changes is done like so:
radioButton.setOnCheckedChangeListener { radioButton, isChecked ->
    // Handle radio button checked/unchecked}checkbox.setOnCheckedChangeListener { checkbox, isChecked ->
    // Handle checkbox checked/unchecked}switchMaterial.setOnCheckedChangeListener { switch, isChecked ->
    // Handle switch checked/unchecked}

Grouping Selection Controls 👨‍👩‍👧‍👦

Selection controls are commonly used in groups. Strictly speaking, any ViewGroup can be used to achieve this (eg. a RecyclerView). That being said, the RadioGroup class exists to specifically handle single-select behavior for MaterialRadioButtons.
MaterialRadioButtons grouped with RadioGroup
MaterialRadioButtons can be grouped with a RadioGroup like so:
<RadioGroup
  android:id="@+id/radioGroup"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:orientation="vertical">

  <com.google.android.material.radiobutton.MaterialRadioButton
    android:id="@+id/option1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="First Option" />

  <com.google.android.material.radiobutton.MaterialRadioButton
    android:id="@+id/option2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Second Option" />

  ...

</RadioGroup>
A number of APIs exist for programmatically setting, getting and listening for changes to child MaterialRadioButton checked/unchecked state:
radioGroup.check(R.id.option1)
val checkedRadioButtonId = chipGroup.checkedRadioButtonId // Will return View.NO_ID if nothing checkedradioGroup.setOnCheckedChangeListener { group, checkedId ->
    // Handle child MaterialRadioButton checked/unchecked}

Theming 🎨

Selection controls can be themed in terms of the three Material Theming subsystems: colortypography and shape. When implementing global custom MaterialRadioButtonMaterialCheckBox and SwitchMaterial styles, reference them in your app theme with the radioButtonStylecheckboxStyle and switchStyle attributes respectively.

Color

The color of selection controls defaults to colorOnSurface (unchecked) and colorControlActivated i.e. colorSecondary (checked) defined in your app theme. In order to override this on a per-control level, you will need to either:
  • Use a theme overlay with the above-mentioned color attributes present, applied to the widget with the android:theme attribute.
  • Set the useMaterialThemeColors attribute to false and apply a custom tint ColorStateList using CompoundButtonCompat#setButtonTintList.
Color theming

Typography

The text labels of the selection controls will adopt the fontFamily attribute defined in your app theme. In order to override this in a style or on a per-control level, use the android:textAppearance attribute.
Type theming

Shape

There are no aspects of any of the selection controls that can be adjusted with shape theming.

Speed up your Android Dynamic Features flow testing



More and more Android apps are integrating Dynamic Features into their projects, although the fact that you need Play Store to deliver the features, makes it hard to test the real user flow (i.e user opening the app and installing a module after clicking somewhere).
Android Studio offers a way to select which Dynamic Features to include when running the app, although it doesn’t offer a way to test installation of the Dynamic Feature modules from Play.
Luckily, we have Play Internal App Sharing which allows developers to upload versions of the app and quickly and easily share it with others for testing.
This means that to test the full flow, we must: build a bundle, upload it to Play Console Internal App Sharing, copy the upload link, open it on a device, then install the app to try it out.

There is a better way.

How?

  1. Upload the first version of your App Bundle using the Google Play Console
  2. Create a Google Play Service Account
  3. Grant access to your Play Console (Only the account owner can do that)
  4. Sign your release builds with a valid signingConfig (Required for release builds)
Next, add and apply the plugin to the root and base/app module build.gradle:
build.gradle →
repositories {
    google()
    jcenter()
    maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
    ...
    classpath "com.github.triplet.gradle:play-publisher:2.6.1"
}
app/build.gradle →
apply plugin: 'com.github.triplet.play'/* ... */play {
   serviceAccountCredentials = file("service-account-key.json")
   defaultToAppBundles = true
}
serviceAccountCredentials should point to the Json file that contains the key for the service account you set in Step 2.
defaultToAppBundles set this flag to true to tell the plugin to create app bundles by default (required for Dynamic features)
That’s it. You can run the “installReleasePrivateArtifact” from the console or directly from the Android Studio (Select it from Gradle tasks under app > publishing).
The tasks will build an App Bundle, upload it to Play Internal App Sharing, and launch the Play Store internal app sharing page, pointing all connected devices to the newly uploaded version.
You will still have to manually click into the update button and launch the app.

Using Debug or other variant builds

You have to set up the artifactDir and make the task depend on the bundle${Variant} task.
play {
    serviceAccountCredentials = file(“service-account-key.json”)
    defaultToAppBundles = true    // Defines the path to Debug bundles
    artifactDir = file(“build/outputs/bundle/debug”)
}afterEvaluate {
    tasks.named(“uploadReleasePrivateBundle”).configure {
        dependsOn(“bundleDebug”)
    }
}
With this setup when running “installReleasePrivateArtifact” it will always use the debug variant.

Demo

Setting up a Material Components theme for Android

So, you’ve migrated your Android app to AndroidX and, in the process, have also switched to using Material Components for Android as opposed to the Design Support Library. Alternatively, perhaps you’re in the fortunate situation of starting an app from scratch and get to use these new libraries right away. In either case, the core widgets you incorporate into your app now mostly fall under the com.google.android.material package and bring with them a variety of new theme/style attributes.
This article will only cover the new global theme attributes and per-widget style attributes that are introduced. Given that the Theme.MaterialComponents themes extend the pre-existing Theme.AppCompat variants, they inherit all of their attributes (think colorAccentcolorControlNormal, etc.), which will not be covered.
Let’s begin!

Initial setup 🏗️

This is as simple as adding a single Gradle dependency to your app/module build.gradle file:
implementation "com.google.android.material:material:$material_version"
Material Components for Android is under active development. The initial 1.0.0 release was mostly just a port of the existing com.android.support.design classes over to the new com.google.android.material namespace. Following this have been independent feature releases and, at the time of writing, the latest version is 1.2.0-alpha05. You can track new releases on the GitHub repository.

Choosing a Material Components theme 🤔

As with the AppCompat themes, the Material Components themes include a few base variants for you to choose from:
Material Components themes (from left to right): Theme.MaterialComponents, Theme.material components.NoActionBar, Theme.material components.Light
Material Components themes (from left to right): Theme.MaterialComponents.Light.DarkActionBar, Theme.MaterialComponents.Light.NoActionBar
The key differences in each variant are the light/dark color palettes and the inclusion/exclusion of an ActionBar in each themed Activity‘s Window. There exists DayNight variants of these for supporting automatic dark/light theming.
Note 1: If you are migrating an existing theme and don’t wish to get all of the new attributes at once, use a Theme.MaterialComponents.*.Bridge variant.
Note 2: Some secondary variants are not shown here, such as Theme.MaterialComponents.Dialog.* themes.
To start using one of these themes in your app, you’d add the following to your res/styles.xml file:
<style name="AppTheme" parent="Theme.MaterialComponents.*">
    <!-- Add attributes here --></style>
Finally, you need to reference this in your Manifest:
<manifest
    ...>
    <application
        android:theme="@style/AppTheme">
        ...
    </application>
</manifest>
Note: You can also apply an android:theme per <activity> in your Manifest.

A simple playground screen 🎡

Right, time to get down to business. In order to illustrate the effects of customizing Material Components attributes, we need visual aid. We will be using the playground screen below, which uses the Theme.MaterialComponents.Light base theme and is packed with most Material Components widgets and their variants:
Playground screen

Global theme attributes 🌍

The Material Components themes introduce new attributes that can be used to style elements on a global scale. These can be grouped into three main subsystems: colortypography and shape.

Color

Color attributes consist mainly of primarysecondaryerrorsurface and background colors, along with their respective secondary variants and “on” colors. Some of these have been reused from the AppCompat themes (eg. colorPrimarycolorError and android:colorBackground):
  • colorPrimary: The primary brand color of your app, used most predominantly in theming
  • colorPrimaryVariant: A lighter/darker variant of your primary brand color, used sparingly in theming
  • colorOnPrimary: The color used for elements displayed on top of your primary colors (eg. Text and icons, often white or semi-transparent black depending on accessibility)
  • colorSecondary: The secondary brand color of your app, used mostly as an accent for certain widgets that need to stand out
  • colorSecondaryVariant: A lighter/darker variant of your secondary brand color, used sparingly in theming
  • colorOnSecondary: The color used for elements displayed on top of your secondary colors
  • colorError: The color used for errors (often a shade of red)
  • colorOnError: The color used for elements displayed on top of your error color
  • colorSurface: The color used for surfaces (i.e. Material “sheets”)
  • colorOnSurface: The color used for elements displayed on top of your surface color
  • android:colorBackground: The color behind all other screen content
  • colorOnBackground: The color used for elements displayed on top of your background color
These colors can be added to your app theme like so:
<style name="AppTheme" parent="Theme.MaterialComponents.Light">
    <item name="colorPrimary">#212121</item>
    <item name="colorPrimaryVariant">#000000</item>
    <item name="colorOnPrimary">#FFFFFF</item>
    <item name="colorSecondary">#2962FF</item>
    <item name="colorSecondaryVariant">#0039CB</item>
    <item name="colorOnSecondary">#FFFFFF</item>
    <item name="colorError">#F44336</item>
    <item name="colorOnError">#FFFFFF</item>
    <item name="colorSurface">#FFFFFF</item>
    <item name="colorOnSurface">#212121</item>
    <item name="android:colorBackground">@color/background</item>
    <item name="colorOnBackground">#212121</item>
</style><color name="background">#FAFAFA</color>
Note 1: Hex color codes are not currently supported for android:colorBackground, hence why a color resource was used.
Note 2: Use android:statusBarColor and android:navigationBarColor attributes to theme system bars.
The result can be observed in our playground screen:
Playground screen with global color attributes customized
A great way to quickly preview the appearance of primary/secondary colors is to use the Material Color Tool.

Typography

Type attributes adhere to the Material Type System in terms of text typefaceweightsizecase and letter spacing. The attributes reference TextAppearance.MaterialComponents.* styles that implement (and are named after) the various type scales:
  • textAppearanceHeadline1: Light, 96sp
  • textAppearanceHeadline2: Light, 60sp
  • textAppearanceHeadline3: Regular, 48sp
  • textAppearanceHeadline4: Regular, 34sp
  • textAppearanceHeadline5: Regular, 24sp
  • textAppearanceHeadline6: Medium, 20sp
  • textAppearanceSubtitle1: Regular, 16sp
  • textAppearanceSubtitle2: Medium, 14sp
  • textAppearanceBody1: Regular, 16sp
  • textAppearanceBody2: Regular, 14sp
  • textAppearanceCaption: Regular, 12sp
  • textAppearanceButton: Regular, 14sp, all caps
  • textAppearanceOverline: Regular, 12sp, all caps
The Material Components widgets will use these styles as per the Material guidelines.
You would typically want to keep the default weight, size, case and letter spacing for each style. However, a custom typeface can really make your app stand out. One might assume this requires overriding each and every one of these attributes. Thankfully, this can be done in a far more concise way by adding the following attributes to your app theme:
<style name="AppTheme" parent="Theme.MaterialComponents.Light">
    ...
    <item name="fontFamily">@font/roboto_mono</item>
    <item name="android:fontFamily">@font/roboto_mono</item>
</style>
These attributes reference an XML Font or a Downloadable Font that you’ve added to your res/font folder and will apply a custom typeface to every widget and text style in your app. There was certainly a time when it wasn’t this easy on Android!
If you do, however, wish to customize one of the Material Components text appearance styles, you would do so like this:
<style name="AppTheme" parent="Theme.MaterialComponents.Light">
    ...
    <item name="textAppearanceButton">@style/AppTextAppearance.Button</item>
</style><style name="AppTextAppearance.Button" parent="TextAppearance.MaterialComponents.Button">
    ...
    <item name="android:textAllCaps">false</item>
</style>
The results can be observed in our playground screen:
Playground screen with global type attributes customized
Lastly, Google Fonts is a great place to start if you’re looking for free-to-use, custom typefaces (which happen to work really well with Downloadable Fonts too).

Shape

Shape attributes refer to the general form of each surface and widget in your app. When you consider that these components can be of varying width/height and be raised/unelevated/outlined, this reduces down to one aspect of customization… Corners.
Material Components corners can either be part of the rounded (default) or cut cornerFamily and have a cornerSize to customize the size. A treatment can be applied to all corners or a subset. The shape theme attributes reference ShapeAppearance.MaterialComponents.* styles:
  • shapeAppearanceSmallComponent: For small components, such as Buttons and Chips
  • shapeAppearanceMediumComponent: For medium components, such as Cards
  • shapeAppearanceLargeComponent: For large components, such as Bottom Sheets
The Material Components widgets will use these styles as per the Material guidelines.
If you wish to customize the Material Components shape appearance styles, you would do so like this:
<style name="AppTheme" parent="Theme.MaterialComponents.Light">
    ...
    <item name="shapeAppearanceSmallComponent">@style/AppShapeAppearance.SmallComponent</item>
    <item name="shapeAppearanceMediumComponent">@style/AppShapeAppearance.MediumComponent</item>
</style><style name="AppShapeAppearance.SmallComponent" parent="ShapeAppearance.MaterialComponents.SmallComponent">
    <item name="cornerFamily">cut</item>
    <item name="cornerSize">8dp</item>
</style><style name="AppShapeAppearance.MediumComponent" parent="ShapeAppearance.MaterialComponents.MediumComponent">
    <item name="cornerFamily">cut</item>
    <item name="cornerSize">8dp</item>
</style>
The result can be observed in our playground screen:
Playground screen with global shape attributes customized

Widget styles and attributes 📱

While global theming covers the majority of our needs, there are times when we may wish to customize the attributes of individual widgets. We will explore the styles (and relevant attributes) of common widgets and how these can be referenced in your Material Components theme.

Buttons

Material Buttons include four main variants that all inherit from the base Widget.MaterialComponents.Button style, each with an optional style suffix: raised (default, no suffix), unelevated (*.UnelevatedButton), outlined (*.OutlinedButton) and text (*.TextButton). All button variants use the textAppearanceButton theme attribute for their typography styles.
The key attributes for customizing these styles are as follows:
  • backgroundTint: The tint color applied to the button background. The default enabled color is transparent for text buttons and colorPrimary for all other variants.
  • iconTint: The tint color applied to an optional button icon. The default enabled color is colorPrimary for text buttons and colorOnPrimary for all other variants.
  • rippleColor: The color of the button touch ripple. The default color is colorOnPrimary for raised/unelevated buttons and colorPrimary for outlined/text buttons.
  • strokeColor: The color of the stroke around the button background. The default color is colorOnSurface for outlined buttons and transparent for all other variants.
  • strokeWidth: The width of the stroke around the button background. The default value is 1dp for outlined buttons and 0dp for all other variants.
  • shapeAppearance: The shape appearance of the button background. The default value is shapeAppearanceSmallComponent.
The base button style (used by the MaterialButton widget class) can be customized and applied globally like so:
<style name="AppTheme" parent="Theme.MaterialComponents.Light">
    ...
    <item name="materialButtonStyle">@style/AppButton</item>
</style><style name="AppButton" parent="Widget.MaterialComponents.Button">
    <item name="backgroundTint">?attr/colorSecondary</item>
</style>
The result can be observed in our playground screen:
Customized Button widget styles

Text Fields

Material Text Fields include two main variants. As a result of porting the pre-existing AppCompat TextInputLayout and TextInputEditText classes, there are in fact two base styles: Widget.MaterialComponents.TextInputLayout.* and Widget.MaterialComponents.TextInputEditText.*. The variants have a style suffix and include filled box (default, *.FilledBox) and outlined box (*.OutlinedBox). All text field variants use the standard text appearance for input and the textAppearanceCaption theme attribute for “helper” text (labels, errors, counters, etc.).
The key attributes for customizing the Widget.MaterialComponents.TextInputLayout.* styles are as follows:
  • boxBackgroundMode: The mode of the box background, which can be either filledoutline or none.
  • boxBackgroundColor: The color of the text field background. The default enabled color is colorOnSurface for filled box text fields and transparent for outlined box text fields.
  • boxStrokeColor: The color of the stroke around the text field background. The default color is colorOnSurface (in default state) for outlined box text fields and is ignored for filled box text fields.
  • hintTextColor/errorTextColor/counterTextColor: Various colors for different “helper” text sub-components.
  • shapeAppearance: The shape appearance of the text field background. The default value is shapeAppearanceSmallComponent.
The base text field style (used by the TextInputLayout widget class) can be customized and applied globally like so:
<style name="AppTheme" parent="Theme.MaterialComponents.Light">
    ...
    <item name="textInputStyle">@style/AppTextField</item>
</style><style name="AppTextField" parent="Widget.MaterialComponents.TextInputLayout.FilledBox">
    <item name="boxBackgroundColor">@color/text_field_background</item>
</style>
Note: text_field_background is a res/color <selector> that uses colorSecondary and the same alpha values as the default boxBackgroundColor <selector>.
The result can be observed in our playground screen:
Customized Text Field widget styles

Cards

Material Cards are considered to be “surfaces” and make use of the Widget.MaterialComponents.CardView style. The key attributes for customizing them are as follows:
  • cardBackgroundColor: The color of the card background. The default color is colorSurface.
  • cardElevation: The elevation of the card. The default value is 1dp.
  • shapeAppearance: The shape appearance of the card background. The default value is shapeAppearanceMediumComponent.
The base card style (used by the MaterialCardView widget class) can be customized and applied globally like so:
<style name="AppTheme" parent="Theme.MaterialComponents.Light">
    ...
    <item name="materialCardViewStyle">@style/AppCard</item>
</style><style name="AppCard" parent="Widget.MaterialComponents.CardView">
    <item name="cardElevation">8dp</item>
</style>
The result can be observed in our playground screen:
Customized Card widget style

Bottom Navigation

Material Bottom Navigation includes two main variants that inherit from the base Widget.MaterialComponents.BottomNavigationView style, with an optional style suffix: surface (default, no suffix) and colored (*.Colored). Bottom Navigation labels use the textAppearanceCaption theme attribute for their typography styles.
The key attributes for customizing these styles are as follows:
  • backgroundTint: The color of the bottom navigation background. The default color is colorSurface for surface bottom navigation and colorPrimary for colored bottom navigation.
  • itemTextColor/itemIconTint: The colors of bottom navigation item icons and labels. The default colors are colorOnSurface/colorPrimary(selected) for surface bottom navigation and colorOnPrimary for colored bottom navigation.
  • itemHorizontalTranslationEnabled: A flag to set whether or not a translation animation should occur when selecting bottom navigation items. The default value is false.
The base bottom navigation style (used by the BottomNavigationView widget class) can be customized and applied globally like so:
<style name="AppTheme" parent="Theme.MaterialComponents.Light">
    ...
    <item name="bottomNavigationStyle">@style/AppBottomNavigation</item>
</style><style name="AppBottomNavigation" parent="Widget.MaterialComponents.BottomNavigation.Colored" />
The result can be observed in our playground screen:
Customized Bottom Navigation widget style
This is certainly not exhaustive. A more comprehensive list of all components and their attributes can be found in the Material Components for Android Docs.

Build a Material Theme

The Material Components for Android library includes a module that allows you to easily customize an existing Material Theme. It provides you with a set of XML files (color.xml/night/color.xmltype.xml and shape.xml) which include all of the necessary baseline theme attributes mentioned in this article. The values can be tweaked and previewed in a corresponding sample app. When you’re happy with the chosen values, the files can be dropped into a new/existing Android Studio project. A web version is also available on Glitch.
The “Build a Material Theme” sample app

More resources 📚


I hope this post has provided some insight into theming your app using Material Components for Android. If you have any questions, thoughts or suggestions then I’d love to hear from you!

How to extract filename from Uri?

Now, we can extract filename with and without extension :) You will convert your bitmap to uri and get the real path of your file. Now w...