Skip to content

Commit 9302f54

Browse files
manabu-nakamurakendrickumstattd
authored andcommitted
[Slider] Fixed behaviour when Slider is in a scrolling container
Resolves #4511 GIT_ORIGIN_REV_ID=58e771c48f0a4bf13f62a303a9495a73f414b73b PiperOrigin-RevId: 720309810
1 parent 9a2890c commit 9302f54

File tree

6 files changed

+141
-19
lines changed

6 files changed

+141
-19
lines changed

catalog/java/io/material/catalog/slider/SliderFragment.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,13 @@ public Fragment createFragment() {
105105
return new SliderVerticalDemoFragment();
106106
}
107107
});
108+
additionalDemos.add(
109+
new Demo(R.string.cat_slider_demo_vertical_scroll_container_title) {
110+
@Override
111+
public Fragment createFragment() {
112+
return new SliderVerticalScrollContainerDemoFragment();
113+
}
114+
});
108115
return additionalDemos;
109116
}
110117

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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+
* https://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.slider;
18+
19+
import io.material.catalog.R;
20+
21+
import android.os.Bundle;
22+
import android.view.LayoutInflater;
23+
import android.view.View;
24+
import android.view.ViewGroup;
25+
import android.widget.LinearLayout;
26+
import androidx.annotation.NonNull;
27+
import androidx.annotation.Nullable;
28+
import com.google.android.material.slider.Slider;
29+
import com.google.android.material.slider.SliderOrientation;
30+
import io.material.catalog.feature.DemoFragment;
31+
32+
/**
33+
* A fragment that displays a list of vertical {@link Slider} inside a HorizontalScrollView to
34+
* showcase how touch events are handled.
35+
*/
36+
public class SliderVerticalScrollContainerDemoFragment extends DemoFragment {
37+
38+
@Nullable
39+
@Override
40+
public View onCreateDemoView(
41+
@NonNull LayoutInflater layoutInflater,
42+
@Nullable ViewGroup viewGroup,
43+
@Nullable Bundle bundle) {
44+
View view =
45+
layoutInflater.inflate(
46+
R.layout.cat_slider_demo_vertical_scroll, viewGroup, false /* attachToRoot */);
47+
LinearLayout sliderContainer = view.findViewById(R.id.sliderContainer);
48+
for (int i = 0; i < 50; i++) {
49+
Slider slider = new Slider(layoutInflater.getContext());
50+
slider.setValueTo(11f);
51+
slider.setOrientation(SliderOrientation.VERTICAL);
52+
sliderContainer.addView(
53+
slider, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT);
54+
}
55+
return view;
56+
}
57+
}

catalog/java/io/material/catalog/slider/res/layout/cat_slider_demo_scroll.xml

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,15 @@
1414
See the License for the specific language governing permissions and
1515
limitations under the License.
1616
-->
17-
<androidx.core.widget.NestedScrollView
18-
xmlns:android="http://schemas.android.com/apk/res/android"
19-
android:layout_width="match_parent"
20-
android:layout_height="match_parent">
17+
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
18+
android:layout_width="match_parent"
19+
android:layout_height="match_parent">
2120

2221
<LinearLayout
2322
android:id="@+id/sliderContainer"
24-
android:layout_width="match_parent"
25-
android:layout_height="wrap_content"
26-
android:paddingLeft="16dp"
27-
android:paddingRight="16dp"
28-
android:orientation="vertical">
29-
30-
31-
</LinearLayout>
23+
android:layout_width="match_parent"
24+
android:layout_height="wrap_content"
25+
android:paddingLeft="16dp"
26+
android:paddingRight="16dp"
27+
android:orientation="vertical" />
3228
</androidx.core.widget.NestedScrollView>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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+
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
18+
android:layout_width="match_parent"
19+
android:layout_height="match_parent">
20+
21+
<LinearLayout
22+
android:id="@+id/sliderContainer"
23+
android:layout_width="wrap_content"
24+
android:layout_height="wrap_content"
25+
android:paddingLeft="16dp"
26+
android:paddingRight="16dp"
27+
android:orientation="horizontal" />
28+
</HorizontalScrollView>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<string name="cat_slider_demo_corner_title" description="Title for the Slider with different corner sizes demo [CHAR LIMIT=NONE]">Slider custom corners demo</string>
2424
<string name="cat_slider_demo_icon_title" description="Title for the Slider with different track icons demo [CHAR LIMIT=NONE]">Slider track icons demo</string>
2525
<string name="cat_slider_demo_vertical_title" description="Title for the vertical Slider demo [CHAR LIMIT=NONE]">Slider vertical demo</string>
26+
<string name="cat_slider_demo_vertical_scroll_container_title" description="Title for the vertical Slider inside scrolling container demo [CHAR LIMIT=NONE]">Vertical Slider in scrolling container demo</string>
2627

2728
<string name="cat_slider_description" description="Body text describing the Slider widget within the design system [CHAR LIMIT=NONE]">
2829
Sliders let users select from a range of values by moving the slider thumb.

lib/java/com/google/android/material/slider/BaseSlider.java

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ abstract class BaseSlider<
263263
private static final double THRESHOLD = .0001;
264264
private static final float THUMB_WIDTH_PRESSED_RATIO = .5f;
265265
private static final int TRACK_CORNER_SIZE_UNSET = -1;
266+
private static final float TOUCH_SLOP_RATIO = .8f;
266267

267268
static final int DEF_STYLE_RES = R.style.Widget_MaterialComponents_Slider;
268269
static final int UNIT_VALUE = 1;
@@ -349,7 +350,8 @@ abstract class BaseSlider<
349350
@Px private int trackIconSize;
350351
@Px private int trackIconPadding;
351352
private int labelPadding;
352-
private float touchDownX;
353+
private float touchDownAxis1;
354+
private float touchDownAxis2;
353355
private MotionEvent lastEvent;
354356
private LabelFormatter formatter;
355357
private boolean thumbIsPressed = false;
@@ -2964,18 +2966,25 @@ public boolean onTouchEvent(@NonNull MotionEvent event) {
29642966
return false;
29652967
}
29662968

2967-
float eventCoordinate = isVertical() ? event.getY() : event.getX();
2968-
touchPosition = (eventCoordinate - trackSidePadding) / trackWidth;
2969+
float eventCoordinateAxis1 = isVertical() ? event.getY() : event.getX();
2970+
float eventCoordinateAxis2 = isVertical() ? event.getX() : event.getY();
2971+
touchPosition = (eventCoordinateAxis1 - trackSidePadding) / trackWidth;
29692972
touchPosition = max(0, touchPosition);
29702973
touchPosition = min(1, touchPosition);
29712974

29722975
switch (event.getActionMasked()) {
29732976
case MotionEvent.ACTION_DOWN:
2974-
touchDownX = eventCoordinate;
2977+
touchDownAxis1 = eventCoordinateAxis1;
2978+
touchDownAxis2 = eventCoordinateAxis2;
29752979

29762980
// If we're inside a vertical scrolling container,
29772981
// we should start dragging in ACTION_MOVE
2978-
if (isPotentialVerticalScroll(event)) {
2982+
if (!isVertical() && isPotentialVerticalScroll(event)) {
2983+
break;
2984+
}
2985+
// If we're inside a horizontal scrolling container,
2986+
// we should start dragging in ACTION_MOVE
2987+
if (isVertical() && isPotentialHorizontalScroll(event)) {
29792988
break;
29802989
}
29812990

@@ -2998,8 +3007,15 @@ public boolean onTouchEvent(@NonNull MotionEvent event) {
29983007
case MotionEvent.ACTION_MOVE:
29993008
if (!thumbIsPressed) {
30003009
// Check if we're trying to scroll vertically instead of dragging this Slider
3001-
if (isPotentialVerticalScroll(event)
3002-
&& abs(eventCoordinate - touchDownX) < scaledTouchSlop) {
3010+
if (!isVertical()
3011+
&& isPotentialVerticalScroll(event)
3012+
&& abs(eventCoordinateAxis1 - touchDownAxis1) < scaledTouchSlop) {
3013+
return false;
3014+
}
3015+
// Check if we're trying to scroll horizontally instead of dragging this Slider
3016+
if (isVertical()
3017+
&& isPotentialHorizontalScroll(event)
3018+
&& abs(eventCoordinateAxis2 - touchDownAxis2) < scaledTouchSlop * TOUCH_SLOP_RATIO) {
30033019
return false;
30043020
}
30053021
getParent().requestDisallowInterceptTouchEvent(true);
@@ -3479,6 +3495,19 @@ private boolean isInVerticalScrollingContainer() {
34793495
return false;
34803496
}
34813497

3498+
private boolean isInHorizontalScrollingContainer() {
3499+
ViewParent p = getParent();
3500+
while (p instanceof ViewGroup) {
3501+
ViewGroup parent = (ViewGroup) p;
3502+
boolean canScrollHorizontally = parent.canScrollHorizontally(1) || parent.canScrollHorizontally(-1);
3503+
if (canScrollHorizontally && parent.shouldDelayChildPressedState()) {
3504+
return true;
3505+
}
3506+
p = p.getParent();
3507+
}
3508+
return false;
3509+
}
3510+
34823511
private static boolean isMouseEvent(MotionEvent event) {
34833512
return event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE;
34843513
}
@@ -3487,6 +3516,10 @@ private boolean isPotentialVerticalScroll(MotionEvent event) {
34873516
return !isMouseEvent(event) && isInVerticalScrollingContainer();
34883517
}
34893518

3519+
private boolean isPotentialHorizontalScroll(MotionEvent event) {
3520+
return !isMouseEvent(event) && isInHorizontalScrollingContainer();
3521+
}
3522+
34903523
@SuppressWarnings("unchecked")
34913524
private void dispatchOnChangedProgrammatically() {
34923525
for (L listener : changeListeners) {

0 commit comments

Comments
 (0)