Skip to content

Commit 3c332f9

Browse files
committed
[Lists] Introduce ListItemLayout and ListItemViewHolder with catalog demo
PiperOrigin-RevId: 791886366
1 parent 2f3d4f9 commit 3c332f9

File tree

15 files changed

+619
-2
lines changed

15 files changed

+619
-2
lines changed
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*
2+
* Copyright 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.material.catalog.lists;
18+
19+
import io.material.catalog.R;
20+
21+
import android.content.Context;
22+
import android.graphics.Rect;
23+
import android.os.Bundle;
24+
import androidx.recyclerview.widget.LinearLayoutManager;
25+
import androidx.recyclerview.widget.RecyclerView;
26+
import androidx.recyclerview.widget.RecyclerView.Adapter;
27+
import androidx.recyclerview.widget.RecyclerView.ItemDecoration;
28+
import android.view.LayoutInflater;
29+
import android.view.View;
30+
import android.view.ViewGroup;
31+
import android.widget.ImageView;
32+
import android.widget.TextView;
33+
import android.widget.Toast;
34+
import androidx.annotation.NonNull;
35+
import androidx.annotation.Nullable;
36+
import com.google.android.material.card.MaterialCardView;
37+
import com.google.android.material.listitem.ListItemViewHolder;
38+
import io.material.catalog.feature.DemoFragment;
39+
import java.util.ArrayList;
40+
import java.util.List;
41+
42+
/** A fragment that displays a List demos with custom content for the Catalog app. */
43+
public class ListsCustomContentDemoFragment extends DemoFragment {
44+
45+
@NonNull
46+
@Override
47+
public View onCreateDemoView(
48+
@NonNull LayoutInflater layoutInflater,
49+
@Nullable ViewGroup viewGroup,
50+
@Nullable Bundle bundle) {
51+
RecyclerView view =
52+
(RecyclerView) layoutInflater.inflate(R.layout.cat_lists_fragment, viewGroup, false);
53+
54+
view.setLayoutManager(new LinearLayoutManager(getContext()));
55+
List<CustomCardData> data = new ArrayList<>();
56+
for (int i = 0; i < 20; i++) {
57+
data.add(new CustomCardData(i+1));
58+
}
59+
60+
view.setAdapter(new ListsAdapter(data));
61+
view.addItemDecoration(new MarginItemDecoration(getContext()));
62+
63+
return view;
64+
}
65+
66+
/** An Adapter that shows custom list items */
67+
public static class ListsAdapter extends Adapter<CustomItemViewHolder> {
68+
69+
private final List<CustomCardData> items;
70+
71+
public ListsAdapter(@NonNull List<CustomCardData> items) {
72+
this.items = items;
73+
}
74+
75+
@NonNull
76+
@Override
77+
public CustomItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int position) {
78+
ViewGroup item =
79+
(ViewGroup)
80+
LayoutInflater.from(parent.getContext()).inflate(R.layout.cat_list_item_viewholder, parent, /* attachToRoot= */ false);
81+
return new CustomItemViewHolder(item);
82+
}
83+
84+
@Override
85+
public void onBindViewHolder(
86+
@NonNull CustomItemViewHolder viewHolder, int position) {
87+
CustomCardData data = getItemAt(position);
88+
viewHolder.bind(data);
89+
}
90+
91+
@Override
92+
public int getItemCount() {
93+
return items.size();
94+
}
95+
96+
@NonNull
97+
public CustomCardData getItemAt(int i) {
98+
return items.get(i);
99+
}
100+
}
101+
102+
static class CustomCardData {
103+
int cardNumber;
104+
boolean checked;
105+
public CustomCardData(int i) {
106+
cardNumber = i;
107+
}
108+
}
109+
110+
static class MarginItemDecoration extends ItemDecoration {
111+
private final int itemMargin;
112+
113+
public MarginItemDecoration(Context context) {
114+
itemMargin = context.getResources().getDimensionPixelSize(R.dimen.cat_list_item_margin);
115+
}
116+
117+
@Override
118+
public void getItemOffsets(@NonNull Rect outRect,
119+
@NonNull View view,
120+
@NonNull RecyclerView parent,
121+
@NonNull RecyclerView.State state) {
122+
int position = parent.getChildAdapterPosition(view);
123+
if (position != state.getItemCount() - 1) {
124+
outRect.bottom = itemMargin;
125+
}
126+
}
127+
}
128+
129+
/** A ViewHolder that shows a custom list item */
130+
public static class CustomItemViewHolder extends ListItemViewHolder {
131+
132+
private final ImageView startButton;
133+
private final ImageView endButton;
134+
private final TextView text;
135+
private final MaterialCardView cardView;
136+
137+
138+
public CustomItemViewHolder(@NonNull View itemView) {
139+
super(itemView);
140+
startButton = itemView.findViewById(R.id.cat_list_item_start_icon);
141+
endButton = itemView.findViewById(R.id.cat_list_item_end_icon);
142+
text = itemView.findViewById(R.id.cat_list_item_text);
143+
cardView = itemView.findViewById(R.id.cat_list_item_card_view);
144+
}
145+
146+
public void bind(@NonNull CustomCardData data) {
147+
super.bind();
148+
text.setText(String.valueOf(data.cardNumber));
149+
startButton.setImageResource(R.drawable.logo_avatar_anonymous_40dp);
150+
endButton.setImageResource(R.drawable.ic_drag_handle_vd_theme_24px);
151+
152+
cardView.setChecked(data.checked);
153+
cardView.setOnClickListener(
154+
v -> {
155+
Toast.makeText(v.getContext(), R.string.mtrl_list_item_clicked, Toast.LENGTH_SHORT)
156+
.show();
157+
cardView.toggle();
158+
data.checked = !data.checked;
159+
});
160+
}
161+
}
162+
}

catalog/java/io/material/catalog/lists/ListsFragment.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import io.material.catalog.R;
2020

2121
import androidx.fragment.app.Fragment;
22+
import androidx.annotation.NonNull;
2223
import dagger.Provides;
2324
import dagger.android.ContributesAndroidInjector;
2425
import dagger.multibindings.IntoSet;
@@ -27,6 +28,8 @@
2728
import io.material.catalog.feature.Demo;
2829
import io.material.catalog.feature.DemoLandingFragment;
2930
import io.material.catalog.feature.FeatureDemo;
31+
import java.util.ArrayList;
32+
import java.util.List;
3033

3134
/** A landing fragment that links to List demos for the Catalog app. */
3235
public class ListsFragment extends DemoLandingFragment {
@@ -51,6 +54,20 @@ public Fragment createFragment() {
5154
};
5255
}
5356

57+
@Override
58+
@NonNull
59+
public List<Demo> getAdditionalDemos() {
60+
List<Demo> additionalDemos = new ArrayList<>();
61+
additionalDemos.add(
62+
new Demo(R.string.cat_lists_custom_content_demo_title) {
63+
@Override
64+
public Fragment createFragment() {
65+
return new ListsCustomContentDemoFragment();
66+
}
67+
});
68+
return additionalDemos;
69+
}
70+
5471
/** The Dagger module for {@link ListsFragment} dependencies. */
5572
@dagger.Module
5673
public abstract static class Module {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
Copyright 2025 The Android Open Source Project
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
-->
17+
<com.google.android.material.listitem.ListItemLayout
18+
xmlns:android="http://schemas.android.com/apk/res/android"
19+
android:layout_width="match_parent"
20+
android:layout_height="wrap_content">
21+
<com.google.android.material.card.MaterialCardView
22+
android:id="@+id/cat_list_item_card_view"
23+
android:layout_width="match_parent"
24+
android:layout_height="wrap_content"
25+
android:checkable="true"
26+
android:clickable="true"
27+
android:focusable="true">
28+
29+
<LinearLayout
30+
android:orientation="horizontal"
31+
android:layout_width="match_parent"
32+
android:layout_height="wrap_content"
33+
android:gravity="center_vertical">
34+
35+
<ImageView
36+
android:id="@+id/cat_list_item_start_icon"
37+
android:layout_width="wrap_content"
38+
android:layout_height="wrap_content"
39+
android:paddingTop="8dp"
40+
android:paddingBottom="8dp"
41+
android:paddingStart="16dp"
42+
android:paddingEnd="16dp"
43+
android:contentDescription="@string/cat_list_item_icon_content_description" />
44+
45+
<TextView
46+
android:id="@+id/cat_list_item_text"
47+
android:layout_width="0dp"
48+
android:layout_height="wrap_content"
49+
android:layout_weight="1"/>
50+
51+
<ImageView
52+
android:id="@+id/cat_list_item_end_icon"
53+
android:layout_width="wrap_content"
54+
android:layout_height="wrap_content"
55+
android:paddingTop="8dp"
56+
android:paddingBottom="8dp"
57+
android:paddingStart="16dp"
58+
android:paddingEnd="16dp"
59+
android:contentDescription="@string/cat_list_item_icon_content_description" />
60+
61+
</LinearLayout>
62+
</com.google.android.material.card.MaterialCardView>
63+
</com.google.android.material.listitem.ListItemLayout>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
Copyright 2025 The Android Open Source Project
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
https://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
-->
17+
<resources>
18+
<dimen name="cat_list_item_margin">2dp</dimen>
19+
</resources>

catalog/java/io/material/catalog/lists/res/values/strings.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
<string name="mtrl_list_item_three_line">Three Line Item</string>
2828
<string name="mtrl_list_item_secondary_text">Secondary Text</string>
2929
<string name="mtrl_list_item_tertiary_text">Tertiary Text</string>
30-
30+
<string name="cat_lists_custom_content_demo_title" description="Label for the demo that shows custom list content. [CHAR_LIMIT=100]">Custom Content Demo</string>
31+
<string name="cat_list_item_icon_content_description" description="Description for sample icon in custom list item demo. [CHAR_LIMIT=100]">List item icon</string>
3132
</resources>
3233

lib/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def srcDirs = [
6969
'com/google/android/material/imageview',
7070
'com/google/android/material/internal',
7171
'com/google/android/material/loadingindicator',
72+
'com/google/android/material/listitem',
7273
'com/google/android/material/materialswitch',
7374
'com/google/android/material/math',
7475
'com/google/android/material/menu',

lib/java/com/google/android/material/dialog/res/values/themes_base.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@
279279
<item name="floatingToolbarStyle">@style/Widget.Material3.FloatingToolbar</item>
280280
<item name="floatingToolbarVibrantStyle">@style/Widget.Material3.FloatingToolbar.Vibrant</item>
281281
<item name="linearProgressIndicatorStyle">@style/Widget.Material3.LinearProgressIndicator</item>
282+
<item name="listItemLayoutStyle">@style/Widget.Material3.ListItemLayout</item>
282283
<item name="loadingIndicatorStyle">@style/Widget.Material3.LoadingIndicator</item>
283284
<item name="materialIconButtonStyle">@style/Widget.Material3.Button.IconButton</item>
284285
<item name="materialIconButtonFilledStyle">@style/Widget.Material3.Button.IconButton.Filled</item>
@@ -411,6 +412,11 @@
411412
<item name="shapeAppearanceCornerExtraLarge">@style/ShapeAppearance.Material3.Corner.ExtraLarge</item>
412413
<item name="shapeAppearanceCornerExtraLargeIncreased">@style/ShapeAppearance.Material3.Corner.ExtraLargeIncreased</item>
413414
<item name="shapeAppearanceCornerExtraExtraLarge">@style/ShapeAppearance.Material3.Corner.ExtraExtraLarge</item>
415+
<item name="listItemShapeAppearanceSingle">@style/ShapeAppearance.Material3.ListItem.Single</item>
416+
<item name="listItemShapeAppearanceFirst">@style/ShapeAppearance.Material3.ListItem.First</item>
417+
<item name="listItemShapeAppearanceMiddle">@style/ShapeAppearance.Material3.ListItem.Middle</item>
418+
<item name="listItemShapeAppearanceLast">@style/ShapeAppearance.Material3.ListItem.Last</item>
419+
<item name="listItemShapeAppearanceChecked">@style/ShapeAppearance.Material3.ListItem.Checked</item>
414420

415421
<!-- Deprecated shape styles. -->
416422
<item name="shapeAppearanceSmallComponent">@style/ShapeAppearance.Material3.SmallComponent</item>
@@ -592,6 +598,7 @@
592598
<item name="floatingToolbarStyle">@style/Widget.Material3.FloatingToolbar</item>
593599
<item name="floatingToolbarVibrantStyle">@style/Widget.Material3.FloatingToolbar.Vibrant</item>
594600
<item name="linearProgressIndicatorStyle">@style/Widget.Material3.LinearProgressIndicator</item>
601+
<item name="listItemLayoutStyle">@style/Widget.Material3.ListItemLayout</item>
595602
<item name="loadingIndicatorStyle">@style/Widget.Material3.LoadingIndicator</item>
596603
<item name="materialIconButtonStyle">@style/Widget.Material3.Button.IconButton</item>
597604
<item name="materialIconButtonFilledStyle">@style/Widget.Material3.Button.IconButton.Filled</item>
@@ -724,6 +731,11 @@
724731
<item name="shapeAppearanceCornerExtraLarge">@style/ShapeAppearance.Material3.Corner.ExtraLarge</item>
725732
<item name="shapeAppearanceCornerExtraLargeIncreased">@style/ShapeAppearance.Material3.Corner.ExtraLargeIncreased</item>
726733
<item name="shapeAppearanceCornerExtraExtraLarge">@style/ShapeAppearance.Material3.Corner.ExtraExtraLarge</item>
734+
<item name="listItemShapeAppearanceSingle">@style/ShapeAppearance.Material3.ListItem.Single</item>
735+
<item name="listItemShapeAppearanceFirst">@style/ShapeAppearance.Material3.ListItem.First</item>
736+
<item name="listItemShapeAppearanceMiddle">@style/ShapeAppearance.Material3.ListItem.Middle</item>
737+
<item name="listItemShapeAppearanceLast">@style/ShapeAppearance.Material3.ListItem.Last</item>
738+
<item name="listItemShapeAppearanceChecked">@style/ShapeAppearance.Material3.ListItem.Checked</item>
727739

728740
<!-- Deprecated shape styles. -->
729741
<item name="shapeAppearanceSmallComponent">@style/ShapeAppearance.Material3.SmallComponent</item>

0 commit comments

Comments
 (0)