Skip to content

Commit 4e4e8f3

Browse files
imhappidsn5ft
authored andcommitted
[BottomSheet] Add support for Nested Scrolling with multiple nested scroll children.
PiperOrigin-RevId: 817350806
1 parent 54be28b commit 4e4e8f3

File tree

12 files changed

+529
-43
lines changed

12 files changed

+529
-43
lines changed

catalog/java/io/material/catalog/bottomsheet/BottomSheetFragment.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ public Fragment createFragment() {
7171
return new BottomSheetUnscrollableContentDemoFragment();
7272
}
7373
});
74+
additionalDemos.add(
75+
new Demo(R.string.cat_bottomsheet_multiple_scrollable_content_demo_title) {
76+
@Override
77+
public Fragment createFragment() {
78+
return new BottomSheetMultipleScrollableContentDemoFragment();
79+
}
80+
});
7481
return additionalDemos;
7582
}
7683

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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.bottomsheet;
17+
18+
import io.material.catalog.R;
19+
20+
import android.app.Dialog;
21+
import android.os.Bundle;
22+
import androidx.fragment.app.Fragment;
23+
import androidx.fragment.app.FragmentManager;
24+
import androidx.fragment.app.FragmentStatePagerAdapter;
25+
import android.view.LayoutInflater;
26+
import android.view.View;
27+
import android.view.ViewGroup;
28+
import android.widget.FrameLayout;
29+
import androidx.annotation.LayoutRes;
30+
import androidx.annotation.NonNull;
31+
import androidx.annotation.Nullable;
32+
import androidx.viewpager.widget.ViewPager;
33+
import com.google.android.material.bottomsheet.BottomSheetDialog;
34+
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
35+
import io.material.catalog.feature.DemoFragment;
36+
import io.material.catalog.windowpreferences.WindowPreferencesManager;
37+
38+
/**
39+
* A fragment that displays the a BottomSheet demo with multiple scrollable content for the Catalog
40+
* app.
41+
*/
42+
public class BottomSheetMultipleScrollableContentDemoFragment extends DemoFragment {
43+
@Override
44+
public View onCreateDemoView(
45+
LayoutInflater layoutInflater, @Nullable ViewGroup viewGroup, @Nullable Bundle bundle) {
46+
View view = layoutInflater.inflate(getDemoContent(), viewGroup, false /* attachToRoot */);
47+
View button = view.findViewById(R.id.bottomsheet_button);
48+
button.setOnClickListener(v -> new BottomSheet().show(getParentFragmentManager(), ""));
49+
return view;
50+
}
51+
52+
@LayoutRes
53+
protected int getDemoContent() {
54+
return R.layout.cat_bottomsheet_additional_demo_fragment;
55+
}
56+
57+
/** A custom bottom sheet dialog fragment. */
58+
@SuppressWarnings("RestrictTo")
59+
public static class BottomSheet extends BottomSheetDialogFragment {
60+
private BottomSheetViewPagerAdapter adapter;
61+
62+
/**
63+
* A simple fragment containing scrollable content, used as pages within the {@link ViewPager}.
64+
*/
65+
public static class ScrollableDemoFragment extends Fragment {
66+
@Nullable
67+
@Override
68+
public View onCreateView(
69+
@NonNull LayoutInflater inflater,
70+
@Nullable ViewGroup container,
71+
@Nullable Bundle savedInstanceState) {
72+
return inflater.inflate(R.layout.cat_bottomsheet_viewpager_page_content, container, false);
73+
}
74+
75+
@Override
76+
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {}
77+
}
78+
79+
private class BottomSheetViewPagerAdapter extends FragmentStatePagerAdapter {
80+
BottomSheetViewPagerAdapter(@NonNull FragmentManager fm) {
81+
super(fm);
82+
}
83+
84+
@Override
85+
@NonNull
86+
public Fragment getItem(int i) {
87+
return new ScrollableDemoFragment();
88+
}
89+
90+
@Override
91+
public int getCount() {
92+
return 5;
93+
}
94+
}
95+
96+
@NonNull
97+
@Override
98+
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
99+
// Set up BottomSheetDialog
100+
adapter = new BottomSheetViewPagerAdapter(getChildFragmentManager());
101+
BottomSheetDialog bottomSheetDialog =
102+
new BottomSheetDialog(
103+
getContext(), R.style.ThemeOverlay_Catalog_BottomSheetDialog_MultiScrollable);
104+
new WindowPreferencesManager(requireContext())
105+
.applyEdgeToEdgePreference(bottomSheetDialog.getWindow());
106+
View content =
107+
LayoutInflater.from(getContext())
108+
.inflate(R.layout.cat_bottomsheet_viewpager_content, new FrameLayout(getContext()));
109+
bottomSheetDialog.setContentView(content);
110+
bottomSheetDialog.getBehavior().setPeekHeight(400);
111+
ViewPager viewPager = content.findViewById(R.id.cat_bottom_sheet_viewpager);
112+
viewPager.setAdapter(adapter);
113+
return bottomSheetDialog;
114+
}
115+
}
116+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
Copyright 2025 The Android Open Source Project
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+
http://www.apache.org/licenses/LICENSE-2.0
8+
Unless required by applicable law or agreed to in writing, software
9+
distributed under the License is distributed on an "AS IS" BASIS,
10+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
See the License for the specific language governing permissions and
12+
limitations under the License.
13+
-->
14+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
15+
android:layout_width="match_parent"
16+
android:layout_height="match_parent"
17+
android:orientation="vertical">
18+
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
19+
android:layout_width="match_parent"
20+
android:layout_height="wrap_content"/>
21+
<androidx.viewpager.widget.ViewPager
22+
android:id="@+id/cat_bottom_sheet_viewpager"
23+
android:layout_width="match_parent"
24+
android:layout_height="match_parent"/>
25+
</LinearLayout>
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
Copyright 2025 The Android Open Source Project
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+
http://www.apache.org/licenses/LICENSE-2.0
8+
Unless required by applicable law or agreed to in writing, software
9+
distributed under the License is distributed on an "AS IS" BASIS,
10+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
See the License for the specific language governing permissions and
12+
limitations under the License.
13+
-->
14+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
15+
xmlns:app="http://schemas.android.com/apk/res-auto"
16+
android:orientation="vertical"
17+
android:layout_width="match_parent"
18+
android:layout_height="match_parent">
19+
20+
<androidx.core.widget.NestedScrollView
21+
android:layout_width="match_parent"
22+
android:layout_height="wrap_content">
23+
24+
<LinearLayout
25+
android:id="@+id/bottom_drawer_2"
26+
android:layout_width="match_parent"
27+
android:layout_height="600dp"
28+
android:orientation="vertical">
29+
30+
<TextView
31+
style="@style/selectableTextView"
32+
android:layout_width="match_parent"
33+
android:layout_height="100dp"
34+
app:drawableLeftCompat="@drawable/ic_person_add_24dp"
35+
app:drawableStartCompat="@drawable/ic_person_add_24dp"
36+
android:text="@string/cat_bottomsheet_label_add_people"/>
37+
38+
<TextView
39+
style="@style/selectableTextView"
40+
android:layout_width="match_parent"
41+
android:layout_height="100dp"
42+
app:drawableLeftCompat="@drawable/ic_link_24dp"
43+
app:drawableStartCompat="@drawable/ic_link_24dp"
44+
android:text="@string/cat_bottomsheet_label_copy_link"/>
45+
46+
<TextView
47+
style="@style/selectableTextView"
48+
android:layout_width="match_parent"
49+
android:layout_height="100dp"
50+
app:drawableLeftCompat="@drawable/ic_open_in_browser_24dp"
51+
app:drawableStartCompat="@drawable/ic_open_in_browser_24dp"
52+
android:text="@string/cat_bottomsheet_label_open_in"/>
53+
54+
<TextView
55+
style="@style/selectableTextView"
56+
android:layout_width="match_parent"
57+
android:layout_height="100dp"
58+
app:drawableLeftCompat="@drawable/ic_folder_24dp"
59+
app:drawableStartCompat="@drawable/ic_folder_24dp"
60+
android:gravity="center_vertical"
61+
android:text="@string/cat_bottomsheet_label_move"/>
62+
63+
<TextView
64+
style="@style/selectableTextView"
65+
android:layout_width="match_parent"
66+
android:layout_height="100dp"
67+
app:drawableLeftCompat="@drawable/ic_offline_pin_24dp"
68+
app:drawableStartCompat="@drawable/ic_offline_pin_24dp"
69+
android:text="@string/cat_bottomsheet_label_available_offline"/>
70+
71+
<TextView
72+
style="@style/selectableTextView"
73+
android:layout_width="match_parent"
74+
android:layout_height="100dp"
75+
app:drawableLeftCompat="@drawable/ic_star_24dp"
76+
app:drawableStartCompat="@drawable/ic_star_24dp"
77+
android:text="@string/cat_bottomsheet_label_star"/>
78+
79+
<TextView
80+
style="@style/selectableTextView"
81+
android:layout_width="match_parent"
82+
android:layout_height="100dp"
83+
app:drawableLeftCompat="@drawable/ic_edit_24dp"
84+
app:drawableStartCompat="@drawable/ic_edit_24dp"
85+
android:text="@string/cat_bottomsheet_label_rename"/>
86+
87+
<TextView
88+
style="@style/selectableTextView"
89+
android:layout_width="match_parent"
90+
android:layout_height="100dp"
91+
app:drawableLeftCompat="@drawable/ic_delete_24dp"
92+
app:drawableStartCompat="@drawable/ic_delete_24dp"
93+
android:text="@string/cat_bottomsheet_label_remove"/>
94+
95+
<TextView
96+
style="@style/selectableTextView"
97+
android:layout_width="match_parent"
98+
android:layout_height="100dp"
99+
app:drawableLeftCompat="@drawable/ic_person_add_24dp"
100+
app:drawableStartCompat="@drawable/ic_person_add_24dp"
101+
android:text="@string/cat_bottomsheet_label_add_people"/>
102+
103+
<TextView
104+
style="@style/selectableTextView"
105+
android:layout_width="match_parent"
106+
android:layout_height="100dp"
107+
app:drawableLeftCompat="@drawable/ic_link_24dp"
108+
app:drawableStartCompat="@drawable/ic_link_24dp"
109+
android:text="@string/cat_bottomsheet_label_copy_link"/>
110+
111+
<TextView
112+
style="@style/selectableTextView"
113+
android:layout_width="match_parent"
114+
android:layout_height="100dp"
115+
app:drawableLeftCompat="@drawable/ic_open_in_browser_24dp"
116+
app:drawableStartCompat="@drawable/ic_open_in_browser_24dp"
117+
android:text="@string/cat_bottomsheet_label_open_in"/>
118+
119+
<TextView
120+
style="@style/selectableTextView"
121+
android:layout_width="match_parent"
122+
android:layout_height="100dp"
123+
app:drawableLeftCompat="@drawable/ic_folder_24dp"
124+
app:drawableStartCompat="@drawable/ic_folder_24dp"
125+
android:gravity="center_vertical"
126+
android:text="@string/cat_bottomsheet_label_move"/>
127+
128+
</LinearLayout>
129+
</androidx.core.widget.NestedScrollView>
130+
131+
</LinearLayout>

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@
5959
<string name="cat_bottomsheet_unscrollable_content_demo_title" translatable="false">
6060
Non-scrollable text input content demo
6161
</string>
62+
<string name="cat_bottomsheet_multiple_scrollable_content_demo_title" description="The title for the bottom sheet demo with multiple scrollable views. [CHAR_LIMIT=100]">
63+
Multiple scrollable content demo
64+
</string>
6265
<string name="cat_bottomsheet_label_add_people">Add People</string>
6366
<string name="cat_bottomsheet_label_copy_link">Copy link</string>
6467
<string name="cat_bottomsheet_label_open_in">Open In</string>

catalog/java/io/material/catalog/bottomsheet/res/values/styles.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,19 @@
1919
<item name="bottomSheetStyle">@style/Widget.Catalog.BottomSheet.Modal.Scrollable</item>
2020
</style>
2121

22+
<style name="ThemeOverlay.Catalog.BottomSheetDialog.MultiScrollable" parent="ThemeOverlay.Material3.BottomSheetDialog">
23+
<item name="bottomSheetStyle">@style/Widget.Catalog.BottomSheet.Modal.MultiScrollable</item>
24+
</style>
25+
2226
<style name="Widget.Catalog.BottomSheet.Modal.Scrollable" parent="Widget.Material3.BottomSheet.Modal">
2327
<item name="paddingBottomSystemWindowInsets">false</item>
2428
</style>
2529

30+
<style name="Widget.Catalog.BottomSheet.Modal.MultiScrollable" parent="Widget.Material3.BottomSheet.Modal">
31+
<item name="paddingBottomSystemWindowInsets">false</item>
32+
<item name="behavior_multipleScrollingChildrenSupported">true</item>
33+
</style>
34+
2635
<style name="selectableTextView" parent="@android:style/Widget.TextView">
2736
<item name="android:textSize">20sp</item>
2837
<item name="android:gravity">center_vertical</item>

docs/components/BottomSheet.md

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,18 @@ Element | Attribute | Related method(s) | Def
5656
More information about these attributes and how to use them in the
5757
[setting behavior](#setting-behavior) section.
5858

59-
Behavior | Related method(s) | Default value
60-
------------------------------------------- | ------------------------------------------------------------------------- | -------------
61-
`app:behavior_peekHeight` | `setPeekHeight`<br/>`getPeekHeight` | `auto`
62-
`app:behavior_hideable` | `setHideable`<br/>`isHideable` | `false` for standard<br/>`true` for modal
63-
`app:behavior_skipCollapsed` | `setSkipCollapsed`<br/>`getSkipCollapsed` | `false`
64-
`app:behavior_fitToContents` | `setFitToContents`<br/>`isFitToContents` | `true`
65-
`app:behavior_draggable` | `setDraggable`<br/>`isDraggable` | `true`
66-
`app:behavior_draggableOnNestedScroll` | `setDraggableOnNestedScroll`<br/>`isDraggableOnNestedScroll` | `true`
67-
`app:behavior_halfExpandedRatio` | `setHalfExpandedRatio`<br/>`getHalfExpandedRatio` | `0.5`
68-
`app:behavior_expandedOffset` | `setExpandedOffset`<br/>`getExpandedOffset` | `0dp`
69-
`app:behavior_significantVelocityThreshold` | `setSignificantVelocityThreshold` <br/> `getSignificantVelocityThreshold` | `500 pixels/s`
59+
Behavior | Related method(s) | Default value
60+
------------------------------------------------- | ------------------------------------------------------------------------- | -------------
61+
`app:behavior_peekHeight` | `setPeekHeight`<br/>`getPeekHeight` | `auto`
62+
`app:behavior_hideable` | `setHideable`<br/>`isHideable` | `false` for standard<br/>`true` for modal
63+
`app:behavior_skipCollapsed` | `setSkipCollapsed`<br/>`getSkipCollapsed` | `false`
64+
`app:behavior_fitToContents` | `setFitToContents`<br/>`isFitToContents` | `true`
65+
`app:behavior_draggable` | `setDraggable`<br/>`isDraggable` | `true`
66+
`app:behavior_draggableOnNestedScroll` | `setDraggableOnNestedScroll`<br/>`isDraggableOnNestedScroll` | `true`
67+
`app:behavior_halfExpandedRatio` | `setHalfExpandedRatio`<br/>`getHalfExpandedRatio` | `0.5`
68+
`app:behavior_expandedOffset` | `setExpandedOffset`<br/>`getExpandedOffset` | `0dp`
69+
`app:behavior_significantVelocityThreshold` | `setSignificantVelocityThreshold` <br/> `getSignificantVelocityThreshold` | `500 pixels/s`
70+
`app:behavior_multipleScrollingChildrenSupported` | N/A | `false`
7071

7172
To save behavior on configuration change:
7273

0 commit comments

Comments
 (0)