Hands-on with Material Components for Android: Buttons
Part 4 of a series covering practical usage of Material Components for Android
This post will be covering the features and API of Button 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:
Buttons are arguably the most widely-used components in any app. This is largely due to their versatility, allowing users to perform actions and make choices that ultimately guide the flow of an experience. A single line of contained text and/or an icon indicate the action a button can perform.
Material Buttons are slightly different to traditional Android buttons in that they do not include additional insets (4dp on the left/right) and have more letter-spacing, different default colors and other attributes that improve legibility and affordance amongst other components.
From a design perspective, there are three main types of buttons which are intended to offer hierarchical levels of emphasis:
- Text button (low emphasis): No container. Best used for less important actions, especially when other main content needs to be emphasised.
- Outlined button (medium emphasis): Stroked container. Best used for important (but not primary) actions and offer a “lighter” visual feel.
- Contained button (high emphasis): Filled container. Best used for primary actions that should draw the users attention. These can either be raised or unelevated.
In addition to this, buttons can be grouped together into a fourth type: Toggle button. This allows related button actions to be horizontally arranged in a common container. The buttons themselves can be selected/deselected to indicate an active/inactive choice.
Basic usage 🏁
A MaterialButton
can be included in your layout like so:
<FrameLayout
...>
<com.google.android.material.button.MaterialButton
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Show" />
</FrameLayout>
Choosing a style 🤔
As discussed in the intro section above, a variety of button types exist. These types map to styles that you can apply to a MaterialButton
. There also exists a variety of sub-styles for specific use cases, such as to adjust padding for an icon. The full list of styles and their attributes can be found on GitHub. These style variants inherit from Widget.MaterialComponents.Button
, each with an optional style suffix:
- Text button:
*.TextButton
(main),*.TextButton.Icon
,*.TextButton.Snackbar
,*.TextButton.Dialog
,*.TextButton.Dialog.Icon
,*.TextButton.Dialog.Flush
- Outlined button:
*.OutlinedButton
(main),*.OutlinedButton.Icon
- Contained button (unelevated):
*.UnelevatedButton
(main),*.UnelevatedButton.Icon
- Contained button (raised): No suffix (default, main),
*.Icon
Adding an icon 🔷
An icon can be added to a button. It is displayed at the start, before the text label. In order to get the correct icon padding, it is recommended that you use a *.Icon
style variant (shown above in the “Choosing a style” section).
The icon can be added in XML:
<com.google.android.material.button.MaterialButton
...
style="@style/Widget.MaterialComponents.Button.Icon"
app:icon="@drawable/ic_show_black_18dp" />
Alternatively, it can be done programmatically:
// Using icon resource ID
textButton.setIconResource(R.drawable.ic_show_black_18dp)
// Using icon Drawable
val showDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_show_black_18dp)
textButton.icon = showDrawable
A few additional attributes exist for adjusting the icon size and position:
iconSize
: The width/height of the icon. The default value is the suppliedDrawable
’s intrinsic width.
iconGravity
: The gravity of the icon. This can be set tostart
(ICON_GRAVITY_START
, default, at start of button),end
(ICON_GRAVITY_END
, at end of button),textStart
(ICON_GRAVITY_TEXT_START
, at start of centered text label) or textEnd (ICON_GRAVITY_TEXT_END
, at end of centered text label).
iconPadding
: The spacing between the icon and the text label. Typically you would not want to change this. The default value is 4dp for text buttons and 8dp for all other types.
Attributes related to icon tinting are discussed in the “Theming” section below.
Grouping Buttons to create a Toggle Button 👨👩👧👦
In order to create a toggle button, we need to add MaterialButton
s as child View
s to a MaterialButtonToggleGroup
(a custom ViewGroup
).
Note: MaterialButtonToggleGroup
was added in the 1.1.0-alpha05 release of Material Components for Android.
Grouping
This can be done in XML:
<com.google.android.material.button.MaterialButtonToggleGroup
android:id="@+id/toggleGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:id="@+id/button1"
... />
<com.google.android.material.button.MaterialButton
android:id="@+id/button2"
... />
<com.google.android.material.button.MaterialButton
android:id="@+id/button3"
... /></com.google.android.material.button.MaterialButtonToggleGroup>
Alternatively, it can be done programmatically:
toggleGroup.addView(button1, 0, layoutParams)
toggleGroup.addView(button2, 1, layoutParams)
toggleGroup.addView(button3, 2, layoutParams)
The MaterialButtonToggleGroup
handles layout and adjusting of only the relevant shape corners in the row of MaterialButton
s. The appearance of the MaterialButton
s is determined by whichever style they each use. It is advised to use a consistent style for all children and also recommended to use the outlined button type.
Adjusting selected behavior
When added to a MaterialButtonToggleGroup
, child MaterialButton
s automatically become “selectable” (i.e. The android:checkable
attribute is set to true).
Thus, there exists a couple of attributes for adjusting how MaterialButtonToggleGroup
manages this:
singleSelection
: Determines whether or not only a single button in the group can be checked at a time. The default value is false, meaning multiple buttons can be checked/unchecked independently.
selectionRequired
: Determines whether or not at least one button in the group must be checked at a time. The default value is false, meaning all buttons can be unchecked.checkedButton
: The ID of the button that should be checked by default. The default valueView.NO_ID
.
Listening for selection state
We are able to query for and adjust the current checked button(s) in a variety of ways:
val checkedId = toggleGroup.checkedButtonId // Will return View.NO_ID if singleSelection = false
val checkedIds = toggleGroup.checkedButtonIds // Potentially an empty list
toggleGroup.check(R.id.button1) // Checks a specific button
toggleGroup.uncheck(R.id.button1) // Unchecks a specific button
toggleGroup.clearChecked() // Unchecks all buttons
We are also able to listen for checked changes by adding an OnButtonCheckedListener
to a MaterialButtonToggleGroup
:
toggleGroup.addOnButtonCheckedListener { group, checkedId, isChecked ->
// Do something for checked change
}
Listeners can also be removed with the MaterialButtonToggleGroup#removeListener
and MaterialButtonToggleGroup#clearListeners
functions.
Orientation
The default arrangement of buttons within a toggle group is horizontal. However, seeing as MaterialButtonToggleGroup
extends LinearLayout
, it also supports vertical arrangement. This can be set programmatically or in XML:
<com.google.android.material.button.MaterialButtonToggleGroup
android:id="@+id/toggleGroup"
...
android:orientation="vertical">
<com.google.android.material.button.MaterialButton
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
...
android:insetTop="0dp"
android:insetBottom="0dp"
android:minHeight="36dp" />
...
</com.google.android.material.button.MaterialButtonToggleGroup>
The interesting thing to note here is the extra attributes on the child MaterialButton
(s). It is recommended to set the width to match_parent
and to remove the top/bottom insets from the child buttons so as to have them sit flush against each other vertically. This also, however, requires adjusting the minHeight
to make up for the lack of insets.
Theming 🎨
Buttons can be themed in terms of the three Material Theming subsystems: color, typography and shape. We have already shown which styles to use in the “Choosing a style” section above. When implementing a global custom MaterialButton
and MaterialButtonToggleGroup
styles, reference them in your app theme with the materialButtonStyle
/materialButtonOutlinedStyle
and materialButtonToggleGroupStyle
attributes respectively.
Color
The color of the MaterialButton
background can be customized with the backgroundTint
attribute. This requires a ColorStateList
, meaning a <selector>
for checked/enabled/disabled states is required. It defaults to colorPrimary
(enabled)/colorOnSurface
(disabled) for contained buttons and transparent(unchecked)/colorPrimary
(checked) for all other types, with different opacities per state. There is also a backgroundTintMode
attribute to change the tint PorterDuff.Mode
, although typically you would want to keep this the same.
The color of the text label can be customized with the android:textColor
attribute. This too requires a ColorStateList
. It defaults to colorOnPrimary
(enabled)/colorOnSurface
(disabled) for contained buttons and colorPrimary
(enabled or checked)/colorOnSurface
(disabled or unchecked) for all other types, with different opacities per state.
The color of the optional icon can be customized with the iconTint
attribute. This too requires a ColorStateList
and the defaults are the same as those of android:textColor
. As before, there is also an iconTintMode
attribute.
Lastly, the color of the button touch ripple can be customized with the rippleColor
attribute. It too accepts a ColorStateList
and defaults to colorOnPrimary
for contained buttons and colorPrimary
for all other types, with different opacities per state.
Typography
The button text label will adopt the fontFamily
attribute defined in your app theme.
While you would typically want to keep most aspects of the button text appearance as is, the Material Guidelines suggest we can use sentence case over the standard all caps for the text label, if desired. To achieve this, we would create a new style:
<style name="ButtonTextAppearance" parent="TextAppearance.MaterialComponents.Button">
<item name="android:textAllCaps">false</item>
</style>
We could apply this directly to a button or in an individual button style by referencing it with the android:textAppearance
attribute. Alternatively, it can be applied globally by referencing it in your app theme with the textAppearanceButton
attribute.
Shape
The shape of a button background can be customized with the shapeAppearance
attribute. This defaults to shapeAppearanceSmallComponent
.
While not strictly shape theming, it is worth mentioning that the width of an outlined button stroke can be adjusted with the strokeWidth
attribute. This defaults to 1dp.
More resources 📚
- The source code for the Playground app used in this article can be found on GitHub.
- Buttons Design Documentation
- Buttons API Documentation
- Toggle Buttons API Documentation
I hope this post has provided some insight into Material Buttons and how they can be used in your Android app(s). If you have any questions, thoughts or suggestions then I’d love to hear from you!
Find me on Twitter @ricknout