Hands-on with Material Components for Android: Chips
Part 5 of a series covering practical usage of Material Components for Android
Featured in Android Weekly Issue #362
This post will be covering the features and API of Chip 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:
Chips are compact components that display discrete information. Given the simplicity of their makeup and small size, they are flexible enough to be used for entering information, filtering content, selection and triggering actions. Chips should be grouped accordingly and are rarely used as standalone elements.
From a design perspective, there are four main types of chips which can be used in different scenarios:
- Input chips: Represent discrete information (an entity, attribute, etc.) as input to a field. Characterized by the close icon which is used to remove them from a group.
- Choice chips: Represent a single choice selection in a group of two or more chips. Characterized by the different checked/unchecked colors, toggled via a tap.
- Filter chips: Represent a multiple choice filter in a group of two or more chips. Characterized by the checked icon, toggled via a tap.
- Action chips: Simply used to trigger a given action. Contains standard icon and text label.
Basic usage 🏁
A Chip
can be included in your layout like so:
<FrameLayout
...>
<com.google.android.material.chip.Chip
android:id="@+id/chip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Chip" />
</FrameLayout>
Choosing a style 🤔
As discussed in the intro section above, a variety of chip types exist. These types map to styles that you can apply to a Chip
. The full list of styles and their attributes can be found on GitHub. These style variants inherit from Widget.MaterialComponents.Chip
, each with an optional style suffix:
- Input chip:
*.Entry
(standalone default) - Choice chip:
*.Choice
- Filter chip:
*.Filter
- Action chip:
*.Action
(default)
Applying attributes 🎛️
A number of attributes exist to customize the appearance and behavior of Chip
s. The defaults for these attributes vary depending on the style of Chip
used. A large amount of attributes exist and not all of them are listed here (eg. Icon padding). Some attributes are purposefully left out and have been reserved for the “Theming” section below. The full list of attributes can be found on GitHub.
Chip icon
chipIcon
: An iconDrawable
that is displayed at the start of theChip
.chipIconVisible
: Whether or not the chip icon is visible.chipIconSize
: The size of the icon.
Close icon
closeIcon
: A clickable close iconDrawable
that is displayed at the end of theChip
.closeIconVisible
: Whether or not the close icon is visible.closeIconSize
: The size of the close icon.
Checked icon
checkedIcon
: A check iconDrawable
that is displayed at the start of theChip
and is toggled via tapping theChip
.checkedIconVisible
: Whether or not the checked icon is visible.android:checkable
: Whether or not checkable tapping is enabled.
Listening for clicks, closes and checks 👂
Chip
s have a few UI elements that can respond to clicks. If the android:checkable
attribute is enabled, clicks on the chip itself will toggle the checked/unchecked state. All of these events can be observed with callbacks.
Listening for a click on the Chip
itself is done like so:
chip.setOnClickListener {
// Handle chip click
}
We can also listen for a click on the Chip
close icon:
chip.setOnCloseIconClickListener {
// Handle chip close icon click
}
Finally, we can listen for checked/unchecked state changes:
chip.setOnCheckedChangeListener { chip, isChecked ->
// Handle chip checked/unchecked
}
Grouping Chips 👨👩👧👦
As mentioned above in the intro section, Chip
s are most commonly used in groups. Strictly speaking, any ViewGroup
can be used to achieve this (eg. a RecyclerView
). That being said, the ChipGroup
class exists to conveniently handle certain multi-Chip
layout and behavior patterns. Specifically, this includes reflowing Chip
s across multiple lines and handling single selection.
Chip
s can be grouped with a ChipGroup
like so:
<com.google.android.material.chip.ChipGroup
android:id="@+id/chipGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.chip.Chip
android:id="@+id/choice1"
... />
<com.google.android.material.chip.Chip
android:id="@+id/choice2"
... />
...
</com.google.android.material.chip.ChipGroup>
If a single horizontal line of Chip
s is preferable over multi-line reflow, you would implement this like so:
<!-- Optionally wrap ChipGroup in a HorizontalScrollView for scrolling behavior --><com.google.android.material.chip.ChipGroup
...
app:singleLine="true"> ...</com.google.android.material.chip.ChipGroup>
The spacing between grouped Chip
s (which defaults to 8dp) can be adjusted with the chipSpacing
, chipSpacingHorizontal
and chipSpacingVertical
attributes:
The singleSelection
attribute can be set to true on a ChipGroup
in order to toggle single-select and multi-select behavior of child Chip
s.
The selectionRequired
attribute can be set to true on a ChipGroup
to prevent all child Chip
s from being deselected (i.e. At least one option should be chosen).
Lastly, a number of APIs exist for programmatically setting, getting and listening for changes to child Chip
checked/unchecked state:
chipGroup.check(R.id.choice1)
val checkedChipId = chipGroup.checkedChipId // Will return View.NO_ID if singleSelection = false
val checkedChipIds = chipGroup.checkedChipIds
chipGroup.setOnCheckedChangeListener { group, checkedId ->
// Handle child Chip checked/unchecked
}
Advanced usage with ChipDrawable 🤓
In certain cases, you may wish to use a standalone chip where a Drawable
is required instead of a View
. For this, we can use the ChipDrawable
class.
Firstly, an XML representation of the chip needs to be added to your res/xml folder, as a separate file, using the <chip>
tag:
<chip
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:chipIcon="@drawable/ic_chip"
app:closeIconVisible="false"
android:text="ChipDrawable" />
All of the non-interactive styleable attributes from the Chip
View
are supported. A ChipDrawable
can then be inflated from this resource like so:
val chipDrawable = ChipDrawable.createFromResource(context, R.xml.chip)
As an example, consider an editable e-mail address field that converts addresses to chips as they are typed and validated. We can combine ChipDrawable
with spans to add a chip to an EditText
:
chip.setBounds(0, 0, chip.intrinsicWidth, chip.intrinsicHeight)
val span = ImageSpan(chip)
val text = editText.text!!
text.setSpan(span, 0, text.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
Theming 🎨
Chips 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 global custom Chip
, ChipDrawable
and ChipGroup
styles, reference them in your app theme with the chipStyle
, chipStandaloneStyle
and chipGroupStyle
attributes respectively.
Color
The color of the Chip
background can be customized with the chipBackgroundColor
attribute. This requires a ColorStateList
, meaning a <selector>
for checked/enabled/disabled states is required. It defaults to colorPrimary
(checked)/colorOnSurface
(unchecked) for choice chips and colorOnSurface
for all other types, with different opacities per state.
The color of the text label can be customized with the android:textColor
attribute. This too requires a ColorStateList
. It defaults to colorPrimary
(checked)/colorOnSurface
(unchecked) for choice chips and colorOnSurface
for all other types, with different opacities per state.
The color of the Chip
icons can be customized with the chipIconTint
and closeIconTint
attributes. There is no attribute for the checked icon, so the tint should be embedded in the resource. These too require ColorStateList
s. The default for chipIconTint
is no tint while closeIconTint
defaults to colorOnSurface
.
Chips can have an optional border stroke and the chipStrokeColor
attribute can be used for this. This defaults to colorOnSurface
. Seeing as the chipStrokeWidth
attribute defaults to 0dp, this stroke is not visible by default.
Lastly, the color of the chip touch ripple can be customized with the rippleColor
attribute. It too accepts a ColorStateList
and defaults to colorPrimary
(pressed)/colorOnSurface
(focused) for choice chips and colorOnSurface
for all other types, with different opacities per state.
Typography
At the time of writing, the latest release of Material Components for Android is 1.2.0-alpha06
and global type theming attributes (eg. fontFamily
) do not affect Chip
. You can star the issue for this on the issue tracker.
To achieve this, we need to specify a custom text appearance style for chips:
<style name="ChipTextAppearance" parent="TextAppearance.MaterialComponents.Chip">
<item name="fontFamily">@font/roboto_mono</item>
<item name="android:fontFamily">@font/roboto_mono</item>
</style>
We could apply this directly to a chip or in an individual chip style by referencing it with the android:textAppearance
attribute.
Shape
The shape of a chip background can be customized with the shapeAppearance
attribute. This defaults to shapeAppearanceSmallComponent
.
More resources 📚
- The source code for the Playground app used in this article can be found on GitHub.
- Chips Design Documentation
- Chips API Documentation
I hope this post has provided some insight into Chips 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