Skip to content

Commit babc9fc

Browse files
imhappidsn5ft
authored andcommitted
[List] Added Multi section ListItemViewHolder bind method and demo
PiperOrigin-RevId: 793813888
1 parent ab86a01 commit babc9fc

File tree

5 files changed

+270
-0
lines changed

5 files changed

+270
-0
lines changed

catalog/java/io/material/catalog/listitem/ListsFragment.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ public Fragment createFragment() {
6666
return new SegmentedListDemoFragment();
6767
}
6868
});
69+
additionalDemos.add(
70+
new Demo(R.string.cat_lists_multi_section_demo_title) {
71+
@Override
72+
public Fragment createFragment() {
73+
return new MultiSectionListDemoFragment();
74+
}
75+
});
6976
return additionalDemos;
7077
}
7178

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
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+
package io.material.catalog.listitem;
17+
18+
import io.material.catalog.R;
19+
20+
import android.content.Context;
21+
import android.graphics.Rect;
22+
import android.os.Bundle;
23+
import androidx.recyclerview.widget.LinearLayoutManager;
24+
import androidx.recyclerview.widget.RecyclerView;
25+
import androidx.recyclerview.widget.RecyclerView.Adapter;
26+
import androidx.recyclerview.widget.RecyclerView.ItemDecoration;
27+
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
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 multi-section List demo for the Catalog app. */
43+
public class MultiSectionListDemoFragment 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_bright_background_fragment, viewGroup, false);
53+
view.setLayoutManager(new LinearLayoutManager(getContext()));
54+
List<CustomCardData> data = new ArrayList<>();
55+
int itemCount = 0;
56+
data.add(new CustomCardData(0, 0, 1));
57+
itemCount += 1;
58+
59+
data.add(new CustomCardData("Subheader 1"));
60+
61+
for (int i = 0; i < 3; i++) {
62+
data.add(new CustomCardData(itemCount + i, i, 3));
63+
}
64+
65+
data.add(new CustomCardData("Subheader 2"));
66+
67+
for (int i = 0; i < 5; i++) {
68+
data.add(new CustomCardData(itemCount + i, i, 5));
69+
}
70+
71+
view.setAdapter(new ListsAdapter(data));
72+
view.addItemDecoration(new MarginItemDecoration(getContext()));
73+
74+
return view;
75+
}
76+
77+
/** An Adapter that shows custom list items */
78+
public static class ListsAdapter extends Adapter<ViewHolder> {
79+
80+
private static final int VIEW_TYPE_SUBHEADING= 1;
81+
private static final int VIEW_TYPE_LIST_ITEM = 2;
82+
private final List<CustomCardData> items;
83+
84+
public ListsAdapter(@NonNull List<CustomCardData> items) {
85+
this.items = items;
86+
}
87+
88+
@Nullable
89+
@Override
90+
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int position) {
91+
switch (getItemViewType(position)) {
92+
case VIEW_TYPE_LIST_ITEM:
93+
ViewGroup item =
94+
(ViewGroup)
95+
LayoutInflater.from(parent.getContext())
96+
.inflate(
97+
R.layout.cat_list_item_segmented_viewholder, parent, /* attachToRoot= */ false);
98+
return new CustomItemViewHolder(item);
99+
case VIEW_TYPE_SUBHEADING:
100+
TextView subheader =
101+
(TextView)
102+
LayoutInflater.from(parent.getContext())
103+
.inflate(R.layout.cat_list_item_subheader, parent, /* attachToRoot= */ false);
104+
return new SubheaderViewHolder(subheader);
105+
default: // fall out
106+
}
107+
return null;
108+
}
109+
110+
@Override
111+
public int getItemViewType(int position) {
112+
CustomCardData data = getItemAt(position);
113+
if (data.subheading != null) {
114+
return VIEW_TYPE_SUBHEADING;
115+
}
116+
return VIEW_TYPE_LIST_ITEM;
117+
}
118+
119+
@Override
120+
public void onBindViewHolder(
121+
@NonNull ViewHolder viewHolder, int position) {
122+
CustomCardData data = getItemAt(position);
123+
if (getItemViewType(position) == VIEW_TYPE_SUBHEADING) {
124+
((SubheaderViewHolder) viewHolder).bind(data);
125+
} else if (getItemViewType(position) == VIEW_TYPE_LIST_ITEM) {
126+
((CustomItemViewHolder) viewHolder).bind(data);
127+
}
128+
}
129+
130+
@Override
131+
public int getItemCount() {
132+
return items.size();
133+
}
134+
135+
@NonNull
136+
public CustomCardData getItemAt(int i) {
137+
return items.get(i);
138+
}
139+
}
140+
141+
static class CustomCardData {
142+
int cardNumber;
143+
boolean checked;
144+
int indexInSection;
145+
int sectionCount;
146+
String subheading;
147+
148+
public CustomCardData(int cardNumber, int indexInSection, int sectionCount) {
149+
this.cardNumber = cardNumber;
150+
this.indexInSection = indexInSection;
151+
this.sectionCount = sectionCount;
152+
}
153+
154+
public CustomCardData(String subheading) {
155+
this.subheading = subheading;
156+
}
157+
}
158+
159+
static class MarginItemDecoration extends ItemDecoration {
160+
private final int itemMargin;
161+
162+
public MarginItemDecoration(Context context) {
163+
itemMargin = context.getResources().getDimensionPixelSize(R.dimen.cat_list_item_margin);
164+
}
165+
166+
@Override
167+
public void getItemOffsets(@NonNull Rect outRect,
168+
@NonNull View view,
169+
@NonNull RecyclerView parent,
170+
@NonNull RecyclerView.State state) {
171+
int position = parent.getChildAdapterPosition(view);
172+
if (position != state.getItemCount() - 1) {
173+
outRect.bottom = itemMargin;
174+
}
175+
}
176+
}
177+
178+
/** A ViewHolder that shows custom list items */
179+
public static class CustomItemViewHolder extends ListItemViewHolder {
180+
181+
private final ImageView startButton;
182+
private final ImageView endButton;
183+
private final TextView text;
184+
private final MaterialCardView cardView;
185+
186+
187+
public CustomItemViewHolder(@NonNull View itemView) {
188+
super(itemView);
189+
startButton = itemView.findViewById(R.id.cat_list_item_start_icon);
190+
endButton = itemView.findViewById(R.id.cat_list_item_end_icon);
191+
text = itemView.findViewById(R.id.cat_list_item_text);
192+
cardView = itemView.findViewById(R.id.cat_list_item_card_view);
193+
}
194+
195+
public void bind(@NonNull CustomCardData data) {
196+
super.bind(data.indexInSection, data.sectionCount);
197+
text.setText(String.valueOf(data.cardNumber));
198+
startButton.setImageResource(R.drawable.logo_avatar_anonymous_40dp);
199+
endButton.setImageResource(R.drawable.ic_drag_handle_vd_theme_24px);
200+
201+
cardView.setChecked(data.checked);
202+
cardView.setOnClickListener(
203+
v -> {
204+
Toast.makeText(v.getContext(), R.string.mtrl_list_item_clicked, Toast.LENGTH_SHORT)
205+
.show();
206+
cardView.toggle();
207+
data.checked = !data.checked;
208+
});
209+
}
210+
}
211+
212+
/** A ViewHolder that shows a subheader list item */
213+
public static class SubheaderViewHolder extends ViewHolder {
214+
215+
private final TextView text;
216+
217+
public SubheaderViewHolder(@NonNull View itemView) {
218+
super(itemView);
219+
text = itemView.findViewById(R.id.cat_list_subheader_text);
220+
}
221+
222+
public void bind(@NonNull CustomCardData data) {
223+
text.setText(data.subheading);
224+
}
225+
}
226+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
~ Copyright (C) 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+
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
18+
android:id="@+id/cat_list_subheader_text"
19+
android:layout_width="match_parent"
20+
android:layout_height="wrap_content"
21+
android:minHeight="?attr/minTouchTargetSize"
22+
android:padding="18dp"
23+
android:gravity="center_vertical|start"
24+
android:maxLines="1"
25+
android:textAlignment="viewStart"
26+
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
27+
android:textColor="?android:textColorPrimary"/>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,6 @@
2929
<string name="mtrl_list_item_tertiary_text">Tertiary Text</string>
3030
<string name="cat_lists_segmented_demo_title" description="Label for the demo that shows segmented list content. [CHAR_LIMIT=100]">Segmented Demo</string>
3131
<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>
32+
<string name="cat_lists_multi_section_demo_title" description="Label for the demo that shows multi-section list content. [CHAR_LIMIT=100]">Multi-section List Demo</string>
3233
</resources>
3334

lib/java/com/google/android/material/listitem/ListItemViewHolder.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ private ListItemLayout findListItemLayout() {
5959
public void bind() {
6060
int position = getBindingAdapterPosition();
6161
int itemCount = getBindingAdapter().getItemCount();
62+
bind(position, itemCount);
63+
}
64+
65+
/**
66+
* Binds the corresponding {@link ListItemLayout} according given position and item count.
67+
* If there are several sections in the list, the position and item count given should be relative
68+
* to its section.
69+
*/
70+
public void bind(int position, int itemCount) {
6271
if (position == RecyclerView.NO_POSITION || itemCount == 0) {
6372
return;
6473
}

0 commit comments

Comments
 (0)