16 KiB
Lists
Lists are continuous, vertical indexes of text or images.
List items can have
supporting text and values
Note: Images use various dynamic color schemes.
Design & API documentation
Anatomy
- Leading video thumbnail (optional)
- Container
- Headline
- Supporting text (optional)
- Trailing supporting text (optional)
- Leading icon (optional)
- Leading avatar label text (optional)
- Trailing icon (optional)
- Leading avatar container (optional)
- Divider (optional)
More details on anatomy items in the component guidelines.
Sizes of list items
List items come in three sizes:
- One-line list items contain a maximum of one line of text.
- Two-line list items contain a maximum of two lines of text.
- Three-line list items contains a maximum of three lines of text.
Code implementation
It is encouraged to implement Lists with a RecyclerView with the design
guidelines in the
Material 3 (M3) spec.
Example of a ViewHolder with a one-line list item:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:minHeight="?attr/minTouchTargetSize">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingEnd="16dp"
android:maxLines="1"
android:textAppearance="?attr/textAppearanceSubtitle1"/>
</LinearLayout>
- Use lists to help users find a specific item and act on it
- Order list items in logical ways (like alphabetical or numerical)
- Keep items short and easy to scan
- Show icons, text, and actions in a consistent format
M3 Expressive
Expressive list styling introduces changes in list colors, shapes, and functionalities.
There are 2 expressive list variants; standard and segmented:
- Standard
- Segmented
There are several new views and interfaces that can be used to implement expressive lists.
ListItemLayout is a container for List content that is applied the following
Drawable position states:
- android.R.attr.state_first
- android.R.attr.state_middle
- android.R.attr.state_last
- android.R.attr.state_single
Children of ListItemLayout that wish to be affected by the ListItemLayout's
position state should duplicate the state through setting
android:duplicateParentState=true.
ListItemCardView is recommended as a ListItemLayout child, as it supports
updating its shape / corners based on states.
You can update a ListItemLayout's appearance according to its position in a
list by calling ListItemLayout.updateAppearance(position, itemCount). If
using a RecyclerView, you can use ListItemViewHolder instead and call
ListItemViewHolder.bind() when binding the ViewHolder to do this
automatically.
Sample RecyclerView.Adapter code:
class ListsAdapter(private val items: List<Data>) :
RecyclerView.Adapter<ListsAdapter.ListItemViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, position: Int): ListItemViewHolder {
return ListItemViewHolder.create(parent, R.layout.list_item_viewholder)
}
override fun onBindViewHolder(viewHolder: ListItemViewHolder, position: Int) {
viewHolder.bind(items[position])
viewHolder.itemView.findViewById<TextView>(R.id.list_item_text)?.let { textView ->
textView.text = items[position].text
}
...
Sample ViewHolder code:
<com.google.android.material.listitem.ListItemLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.listitem.ListItemCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checkable="true">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<TextView
android:id="@+id/list_item_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</com.google.android.material.listitem.ListItemCardView>
</com.google.android.material.listitem.ListItemLayout>
You can also implement multi-section lists by calling
ListItemLayout.updateAppearance(position, itemCount) with position and
itemCount that is relative to the section instead of the whole list.
Eg. for the first item in a section of size 10, make sure you call
ListItemLayout.updateAppearance(0, 10).
ListItemLayout also supports a swipe-to-reveal functionality via
SwipeableListItem and RevealableListItem interfaces. The SwipeableListItem
view must be a direct child of ListItemLayout, and have a RevealableListItem
sibling. ListItemLayout supports swiping the SwipeableListItem to fully
reveal the RevealableListItem sibling by default, and can optionally also
support being swiped its whole width.
We provide ListItemCardView as the default SwipeableListItem implementation,
and ListItemRevealLayout as the default RevealableListItem
implementation.
Sample ViewHolder code for swipe-to-reveal:
<com.google.android.material.listitem.ListItemLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- SwipeableListItem -->
<com.google.android.material.listitem.ListItemCardView
android:id="@+id/cat_list_item_card_view"
style="?attr/listItemCardViewSegmentedStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checkable="true"
android:clickable="true"
android:focusable="true"
app:swipeToPrimaryActionEnabled="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView
style="@style/Widget.Material3.ImageView.ListItem.LeadingIcon"
android:layout_width="20dp"
android:layout_height="20dp"
android:contentDescription="@string/icon_content_description"
app:srcCompat="@drawable/ic_star_icon_checkable_24px" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_weight="1"
android:orientation="vertical">
<TextView
style="@style/Widget.Material3.TextView.ListItem.LabelText"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
style="@style/Widget.Material3.TextView.ListItem.SupportingText"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<com.google.android.material.checkbox.MaterialCheckBox
style="@style/Widget.Material3.Checkbox.ListItem"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp" />
</LinearLayout>
</com.google.android.material.listitem.ListItemCardView>
<!-- RevealableListItem -->
<com.google.android.material.listitem.ListItemRevealLayout
android:layout_width="wrap_content"
android:layout_height="match_parent">
<com.google.android.material.button.MaterialButton
style="?attr/materialIconButtonFilledStyle"
android:layout_width="64dp"
android:layout_height="match_parent"
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
app:icon="@drawable/ic_add_24px" />
<com.google.android.material.button.MaterialButton
style="?attr/materialIconButtonFilledStyle"
android:layout_width="64dp"
android:layout_height="match_parent"
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
app:icon="@drawable/ic_add_24px" />
</com.google.android.material.listitem.ListItemRevealLayout>
</com.google.android.material.listitem.ListItemLayout>
If swipe to trigger primary action is enabled
(app:swipeToPrimaryActionEnabled=true on a ListItemCardView), make sure to
implement the primary action to be triggered through a SwipeStateCallback. If
using the ListItemRevealLayout as the RevealableListItem, the primary action
should correspond to the behavior of the last view in the
ListItemRevealLayout.
listItemCardView.addOnSwipeStateChangedCallback { newState ->
if (newState == STATE_SWIPE_PRIMARY_ACTION) {
// do primary action
listItemLayout.setSwipeState(SwipeableListItem.STATE_CLOSED)
}
}
Do not leave the ListItemLayout in the fully swiped state; if using
ListItemRevealLayout, child views are shrunk and do not adhere to the min
touch size. It is meant to visually indicate the primary action is triggered,
and the swipe state should either be reset or the ListItemLayout should be
dismissed.
Key properties
ListItemCardView attributes
| Element | Attribute | Related methods | Default value |
|---|---|---|---|
| Color | app:cardBackgroundColor |
setCardBackgroundColorgetCardBackgroundColor |
@color/transparent (standard style)?attr/colorSurfaceBright (segmented style) ?attr/colorSecondaryContainer (selected) |
| Shape | app:shapeAppearance |
setShapeAppearanceModelgetShapeAppearanceModel |
?attr/listItemShapeAppearanceSingle ?attr/listItemShapeAppearanceFirst ?attr/listItemShapeAppearanceMiddle ?attr/listItemShapeAppearanceLast |
| Ripple color | app:rippleColor |
setRippleColorsetRippleColorResourcegetRippleColor |
?attr/colorOnSurface at 10% opacity (8% when hovered) |
| Swipe enabled | app:swipeEnabled |
setSwipeEnabledisSwipeEnabled |
true |
ListItemRevealLayout attributes
| Element | Attribute | Related methods | Default value |
|---|---|---|---|
| Min child size | app:minRevealedChildWidth |
setMinChildWidthgetMinChildWidth |
6dp |
| Primary Action Swipe Mode | app:primaryActionSwipeMode |
setPrimaryActionSwipeModegetPrimaryActionSwipeMode |
disabled |
Accessibility
Decorative elements inside List items
Any elements of the list item that are purely decorative should not receive separate focus from the list item, or be announced by a screen reader like Talkback. An element of a list can be decorative if it does not have a separate click action from the list item, and if it does not provide any additional information for the list item that should be announced.
You can set the android:focusable and android:importantForAccessibility
attributes to control whether or not these elements are focused and announced by
screenreaders. For example:
<com.google.android.material.imageview.ShapeableImageView
style="@style/Widget.Material3.ShapeableImageView.ListItem.LeadingImage"
android:layout_height="56dp"
android:layout_width="56dp"
app:srcCompat="@drawable/cat_image"
android:focusable="false"
android:importantForAccessibility="no"
android:scaleType="centerCrop"/>
If your list item has any checkable components that are decorative (ie. they are sync'd with the checked state of the list item and are purely a visual indicator) you can use the provided list-specific styles to skip view focus and a11y actions on these elements:
Widget.Material3.Checkbox.ListItemWidget.Material3.RadioButton.ListItemWidget.Material3.Switch.ListItem
For example, if you are using a MaterialCheckbox, use the style
Widget.Material3.Checkbox.ListItem. Do not use this style for any elements
that should have a separate state from the list item, eg. if there are multiple
checkable elements or if the element's checked state should be separate from the
checked state of the list item.
<com.google.android.material.checkbox.MaterialCheckBox
style="@style/Widget.Material3.Checkbox.ListItem"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp" />
Expandable List items
If your list item is expandable, it must announce its collapsed or expanded
state when read by screenreaders like Talkback. You can achieve this by setting
an AccessibilityDelegate on the list item.
The following code snippet demonstrates how to add ACTION_EXPAND and
ACTION_COLLAPSE to the accessibility node. Remember to also implement the
expand/collapse action functionalities appropriately when the accessibility
action is performed.
listItemCardView.accessibilityDelegate = object : View.AccessibilityDelegate() {
override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {
super.onInitializeAccessibilityNodeInfo(host, info)
if (data.expanded) {
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE)
} else {
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND)
}
}
override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
return when (action) {
AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE.id -> {
collapse()
true
}
AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND.id -> {
expand()
true
}
else -> super.performAccessibilityAction(host, action, args)
}
}
}







