Skip to content

Commit 660423f

Browse files
pubiqqkendrickumstattd
authored andcommitted
[Slider] Add new tick visibility modes
Resolves #2897 GIT_ORIGIN_REV_ID=82e11301b0ecf48b7d8486e8b29954bd1c4aebe8 PiperOrigin-RevId: 720997942
1 parent 76dce73 commit 660423f

File tree

7 files changed

+177
-41
lines changed

7 files changed

+177
-41
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@
163163
android:valueFrom="0"
164164
android:valueTo="10"
165165
android:stepSize="1"
166-
app:tickVisible="false"/>
166+
app:tickVisibilityMode="hidden" />
167167
</LinearLayout>
168168

169169
<com.google.android.material.materialswitch.MaterialSwitch

docs/components/Slider.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,12 +364,15 @@ Element | Attribute | Related method(s)
364364
| **Color for tick's inactive part** | `app:tickColorInactive` | `setTickInactiveTintList`<br/>`getTickInactiveTintList` | `?attr/colorPrimary` |
365365
| **Radius for tick's active part** | `app:tickRadiusActive` | `setTickActiveRadius`<br/>`getTickActiveRadius` | `null` (1/2 trackStopIndicatorSize) |
366366
| **Radius for tick's inactive part** | `app:tickRadiusInactive` | `setTickInactiveRadius`<br/>`getTickInactiveRadius` | `null` (1/2 trackStopIndicatorSize) |
367-
| **Tick visible** | `app:tickVisible` | `setTickVisible`<br/>`isTickVisible()` | `true` |
367+
| **Tick visible** (deprecated) | `app:tickVisible` | `setTickVisible`<br/>`isTickVisible()` | `true` |
368+
| **Tick visibility mode** | `app:tickVisibilityMode` | `setTickVisibilityMode`<br/>`getTickVisibilityMode()` | `autoLimit` |
368369

369370
**Note:** `app:tickColor` takes precedence over `app:tickColorActive` and
370-
`app:tickColorInative`. It's a shorthand for setting both values to the same
371+
`app:tickColorInactive`. It's a shorthand for setting both values to the same
371372
thing.
372373

374+
**Note:** `app:tickVisible` is deprecated in favor of `app:tickVisibilityMode`.
375+
373376
#### Styles
374377

375378
Element | Style

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

Lines changed: 104 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
import static com.google.android.material.slider.LabelFormatter.LABEL_WITHIN_BOUNDS;
3030
import static com.google.android.material.slider.SliderOrientation.HORIZONTAL;
3131
import static com.google.android.material.slider.SliderOrientation.VERTICAL;
32+
import static com.google.android.material.slider.TickVisibilityMode.TICK_VISIBILITY_AUTO_HIDE;
33+
import static com.google.android.material.slider.TickVisibilityMode.TICK_VISIBILITY_AUTO_LIMIT;
34+
import static com.google.android.material.slider.TickVisibilityMode.TICK_VISIBILITY_HIDDEN;
3235
import static com.google.android.material.theme.overlay.MaterialThemeOverlay.wrap;
3336
import static java.lang.Float.compare;
3437
import static java.lang.Math.abs;
@@ -157,8 +160,10 @@
157160
* discrete mode. This is a short hand for setting both the {@code tickColorActive} and {@code
158161
* tickColorInactive} to the same thing. This takes precedence over {@code tickColorActive}
159162
* and {@code tickColorInactive}.
160-
* <li>{@code tickVisible}: Whether to show the tick marks. Only used when the slider is in
161-
* discrete mode.
163+
* <li>{@code tickVisible} (<b>deprecated</b>, use {@code tickVisibilityMode} instead): Whether to
164+
* show the tick marks. Only used when the slider is in discrete mode.
165+
* <li>{@code tickVisibilityMode}: Mode to specify the visibility of tick marks. Only used when
166+
* the slider is in discrete mode.
162167
* <li>{@code trackColorActive}: The color of the active part of the track.
163168
* <li>{@code trackColorInactive}: The color of the inactive part of the track.
164169
* <li>{@code trackColor}: The color of the whole track. This is a short hand for setting both the
@@ -210,6 +215,7 @@
210215
* @attr ref com.google.android.material.R.styleable#Slider_tickColorActive
211216
* @attr ref com.google.android.material.R.styleable#Slider_tickColorInactive
212217
* @attr ref com.google.android.material.R.styleable#Slider_tickVisible
218+
* @attr ref com.google.android.material.R.styleable#Slider_tickVisibilityMode
213219
* @attr ref com.google.android.material.R.styleable#Slider_trackColor
214220
* @attr ref com.google.android.material.R.styleable#Slider_trackColorActive
215221
* @attr ref com.google.android.material.R.styleable#Slider_trackColorInactive
@@ -366,7 +372,7 @@ abstract class BaseSlider<
366372
private int focusedThumbIdx = -1;
367373
private float stepSize = 0.0f;
368374
private float[] ticksCoordinates;
369-
private boolean tickVisible = true;
375+
private int tickVisibilityMode;
370376
private int tickActiveRadius;
371377
private int tickInactiveRadius;
372378
private int trackWidth;
@@ -583,7 +589,11 @@ private void processAttributes(Context context, AttributeSet attrs, int defStyle
583589
? haloColor
584590
: AppCompatResources.getColorStateList(context, R.color.material_slider_halo_color));
585591

586-
tickVisible = a.getBoolean(R.styleable.Slider_tickVisible, true);
592+
tickVisibilityMode =
593+
a.hasValue(R.styleable.Slider_tickVisibilityMode)
594+
? a.getInt(R.styleable.Slider_tickVisibilityMode, -1)
595+
: convertToTickVisibilityMode(a.getBoolean(R.styleable.Slider_tickVisible, true));
596+
587597
boolean hasTickColor = a.hasValue(R.styleable.Slider_tickColor);
588598
int tickColorInactiveRes =
589599
hasTickColor ? R.styleable.Slider_tickColor : R.styleable.Slider_tickColorInactive;
@@ -1774,22 +1784,58 @@ public void setTickInactiveTintList(@NonNull ColorStateList tickColor) {
17741784
/**
17751785
* Returns whether the tick marks are visible. Only used when the slider is in discrete mode.
17761786
*
1777-
* @see #setTickVisible(boolean)
17781787
* @attr ref com.google.android.material.R.styleable#Slider_tickVisible
17791788
*/
17801789
public boolean isTickVisible() {
1781-
return tickVisible;
1790+
switch (tickVisibilityMode) {
1791+
case TICK_VISIBILITY_AUTO_LIMIT:
1792+
return true;
1793+
case TICK_VISIBILITY_AUTO_HIDE:
1794+
return getDesiredTickCount() <= getMaxTickCount();
1795+
case TICK_VISIBILITY_HIDDEN:
1796+
return false;
1797+
default:
1798+
throw new IllegalStateException("Unexpected tickVisibilityMode: " + tickVisibilityMode);
1799+
}
17821800
}
17831801

17841802
/**
17851803
* Sets whether the tick marks are visible. Only used when the slider is in discrete mode.
17861804
*
17871805
* @param tickVisible The visibility of tick marks.
17881806
* @attr ref com.google.android.material.R.styleable#Slider_tickVisible
1807+
* @deprecated Use {@link #setTickVisibilityMode(int)} instead.
17891808
*/
1809+
@Deprecated
17901810
public void setTickVisible(boolean tickVisible) {
1791-
if (this.tickVisible != tickVisible) {
1792-
this.tickVisible = tickVisible;
1811+
setTickVisibilityMode(convertToTickVisibilityMode(tickVisible));
1812+
}
1813+
1814+
@TickVisibilityMode
1815+
private int convertToTickVisibilityMode(boolean tickVisible) {
1816+
return tickVisible ? TICK_VISIBILITY_AUTO_LIMIT : TICK_VISIBILITY_HIDDEN;
1817+
}
1818+
1819+
/**
1820+
* Returns the current tick visibility mode.
1821+
*
1822+
* @see #setTickVisibilityMode(int)
1823+
* @attr ref com.google.android.material.R.styleable#Slider_tickVisibilityMode
1824+
*/
1825+
@TickVisibilityMode
1826+
public int getTickVisibilityMode() {
1827+
return tickVisibilityMode;
1828+
}
1829+
1830+
/**
1831+
* Sets the tick visibility mode. Only used when the slider is in discrete mode.
1832+
*
1833+
* @see #getTickVisibilityMode()
1834+
* @attr ref com.google.android.material.R.styleable#Slider_tickVisibilityMode
1835+
*/
1836+
public void setTickVisibilityMode(@TickVisibilityMode int tickVisibilityMode) {
1837+
if (this.tickVisibilityMode != tickVisibilityMode) {
1838+
this.tickVisibilityMode = tickVisibilityMode;
17931839
postInvalidate();
17941840
}
17951841
}
@@ -2412,37 +2458,70 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
24122458
updateHaloHotspot();
24132459
}
24142460

2415-
private void maybeCalculateTicksCoordinates() {
2461+
private void updateTicksCoordinates() {
2462+
validateConfigurationIfDirty();
2463+
24162464
if (stepSize <= 0.0f) {
2465+
updateTicksCoordinates(/* tickCount= */ 0);
24172466
return;
24182467
}
24192468

2420-
validateConfigurationIfDirty();
2469+
final int tickCount;
2470+
switch (tickVisibilityMode) {
2471+
case TICK_VISIBILITY_AUTO_LIMIT:
2472+
tickCount = min(getDesiredTickCount(), getMaxTickCount());
2473+
break;
2474+
case TICK_VISIBILITY_AUTO_HIDE:
2475+
int desiredTickCount = getDesiredTickCount();
2476+
tickCount = desiredTickCount <= getMaxTickCount() ? desiredTickCount : 0;
2477+
break;
2478+
case TICK_VISIBILITY_HIDDEN:
2479+
tickCount = 0;
2480+
break;
2481+
default:
2482+
throw new IllegalStateException("Unexpected tickVisibilityMode: " + tickVisibilityMode);
2483+
}
2484+
2485+
updateTicksCoordinates(tickCount);
2486+
}
2487+
2488+
private void updateTicksCoordinates(int tickCount) {
2489+
if (tickCount == 0) {
2490+
ticksCoordinates = null;
2491+
return;
2492+
}
24212493

2422-
int tickCount = (int) ((valueTo - valueFrom) / stepSize + 1);
2423-
// Limit the tickCount if they will be too dense.
2424-
tickCount = min(tickCount, trackWidth / minTickSpacing + 1);
24252494
if (ticksCoordinates == null || ticksCoordinates.length != tickCount * 2) {
24262495
ticksCoordinates = new float[tickCount * 2];
24272496
}
24282497

24292498
float interval = trackWidth / (float) (tickCount - 1);
2499+
float trackCenterY = calculateTrackCenter();
2500+
24302501
for (int i = 0; i < tickCount * 2; i += 2) {
24312502
ticksCoordinates[i] = trackSidePadding + i / 2f * interval;
2432-
ticksCoordinates[i + 1] = calculateTrackCenter();
2503+
ticksCoordinates[i + 1] = trackCenterY;
24332504
}
24342505

24352506
if (isVertical()) {
24362507
rotationMatrix.mapPoints(ticksCoordinates);
24372508
}
24382509
}
24392510

2511+
private int getDesiredTickCount() {
2512+
return (int) ((valueTo - valueFrom) / stepSize + 1);
2513+
}
2514+
2515+
private int getMaxTickCount() {
2516+
return trackWidth / minTickSpacing + 1;
2517+
}
2518+
24402519
private void updateTrackWidth(int width) {
24412520
// Update the visible track width.
24422521
trackWidth = max(width - trackSidePadding * 2, 0);
24432522

24442523
// Update the visible tick coordinates.
2445-
maybeCalculateTicksCoordinates();
2524+
updateTicksCoordinates();
24462525
}
24472526

24482527
private void updateHaloHotspot() {
@@ -2457,10 +2536,7 @@ private void updateHaloHotspot() {
24572536
rotationMatrix.mapPoints(haloBounds);
24582537
}
24592538
background.setHotspotBounds(
2460-
(int) haloBounds[0],
2461-
(int) haloBounds[1],
2462-
(int) haloBounds[2],
2463-
(int) haloBounds[3]);
2539+
(int) haloBounds[0], (int) haloBounds[1], (int) haloBounds[2], (int) haloBounds[3]);
24642540
}
24652541
}
24662542
}
@@ -2478,7 +2554,7 @@ protected void onDraw(@NonNull Canvas canvas) {
24782554
validateConfigurationIfDirty();
24792555

24802556
// Update the visible tick coordinates.
2481-
maybeCalculateTicksCoordinates();
2557+
updateTicksCoordinates();
24822558
}
24832559

24842560
super.onDraw(canvas);
@@ -2673,15 +2749,11 @@ private void drawTrackIcons(
26732749
}
26742750

26752751
// draw track start icons
2676-
calculateBoundsAndDrawTrackIcon(
2677-
canvas, activeTrackBounds, trackIconActiveStart, true);
2678-
calculateBoundsAndDrawTrackIcon(
2679-
canvas, inactiveTrackBounds, trackIconInactiveStart, true);
2752+
calculateBoundsAndDrawTrackIcon(canvas, activeTrackBounds, trackIconActiveStart, true);
2753+
calculateBoundsAndDrawTrackIcon(canvas, inactiveTrackBounds, trackIconInactiveStart, true);
26802754
// draw track end icons
2681-
calculateBoundsAndDrawTrackIcon(
2682-
canvas, activeTrackBounds, trackIconActiveEnd, false);
2683-
calculateBoundsAndDrawTrackIcon(
2684-
canvas, inactiveTrackBounds, trackIconInactiveEnd, false);
2755+
calculateBoundsAndDrawTrackIcon(canvas, activeTrackBounds, trackIconActiveEnd, false);
2756+
calculateBoundsAndDrawTrackIcon(canvas, inactiveTrackBounds, trackIconInactiveEnd, false);
26852757
}
26862758

26872759
private boolean hasTrackIcons() {
@@ -2705,9 +2777,7 @@ private void calculateBoundsAndDrawTrackIcon(
27052777
}
27062778

27072779
private void drawTrackIcon(
2708-
@NonNull Canvas canvas,
2709-
@NonNull RectF iconBounds,
2710-
@NonNull Drawable icon) {
2780+
@NonNull Canvas canvas, @NonNull RectF iconBounds, @NonNull Drawable icon) {
27112781
if (isVertical()) {
27122782
rotationMatrix.mapRect(iconBounds);
27132783
}
@@ -2837,7 +2907,7 @@ private float[] getCornerRadii(float leftSide, float rightSide) {
28372907
}
28382908

28392909
private void maybeDrawTicks(@NonNull Canvas canvas) {
2840-
if (!tickVisible || stepSize <= 0.0f) {
2910+
if (ticksCoordinates == null || ticksCoordinates.length == 0) {
28412911
return;
28422912
}
28432913

@@ -3499,7 +3569,8 @@ private boolean isInHorizontalScrollingContainer() {
34993569
ViewParent p = getParent();
35003570
while (p instanceof ViewGroup) {
35013571
ViewGroup parent = (ViewGroup) p;
3502-
boolean canScrollHorizontally = parent.canScrollHorizontally(1) || parent.canScrollHorizontally(-1);
3572+
boolean canScrollHorizontally =
3573+
parent.canScrollHorizontally(1) || parent.canScrollHorizontally(-1);
35033574
if (canScrollHorizontally && parent.shouldDelayChildPressedState()) {
35043575
return true;
35053576
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2024 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 com.google.android.material.slider;
18+
19+
import androidx.annotation.IntDef;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
22+
23+
/** Mode to specify the visibility of tick marks. */
24+
@IntDef({
25+
TickVisibilityMode.TICK_VISIBILITY_AUTO_LIMIT,
26+
TickVisibilityMode.TICK_VISIBILITY_AUTO_HIDE,
27+
TickVisibilityMode.TICK_VISIBILITY_HIDDEN
28+
})
29+
@Retention(RetentionPolicy.SOURCE)
30+
public @interface TickVisibilityMode {
31+
32+
/**
33+
* All tick marks will be drawn if they are not spaced too densely. Otherwise, the maximum allowed
34+
* number of tick marks will be drawn. Note that in this case the drawn ticks may not match the
35+
* actual snap values.
36+
*/
37+
int TICK_VISIBILITY_AUTO_LIMIT = 0;
38+
39+
/**
40+
* All tick marks will be drawn if they are not spaced too densely. Otherwise, the tick marks will
41+
* not be drawn.
42+
*/
43+
int TICK_VISIBILITY_AUTO_HIDE = 1;
44+
45+
/** Tick marks will not be drawn. */
46+
int TICK_VISIBILITY_HIDDEN = 2;
47+
}

lib/java/com/google/android/material/slider/res-public/values/public.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
<public name="tickRadiusActive" type="attr" />
3636
<public name="tickRadiusInactive" type="attr" />
3737
<public name="tickVisible" type="attr" />
38+
<public name="tickVisibilityMode" type="attr" />
3839
<public name="trackColorActive" type="attr" />
3940
<public name="trackColorInactive" type="attr" />
4041
<public name="trackHeight" type="attr" />

lib/java/com/google/android/material/slider/res/values/attrs.xml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,22 @@
7070
<attr name="tickRadiusActive" format="dimension" />
7171
<!-- The radius of the inactive tick. Only used when the slider is in discrete mode.-->
7272
<attr name="tickRadiusInactive" format="dimension" />
73-
<!-- Whether to show the tick marks. Only used when the slider is in discrete mode. -->
73+
<!-- Whether to show the tick marks. Only used when the slider is in discrete mode.
74+
{@deprecated Use tickVisibilityMode instead.} -->
7475
<attr name="tickVisible" format="boolean" />
76+
<!-- Mode to specify the visibility of tick marks. Only used when the slider is in
77+
discrete mode. -->
78+
<attr name="tickVisibilityMode" format="enum">
79+
<!-- All tick marks will be drawn if they are not spaced too densely. Otherwise,
80+
the maximum allowed number of tick marks will be drawn.
81+
Note that in this case the drawn ticks may not match the actual snap values. -->
82+
<enum name="autoLimit" value="0" />
83+
<!-- All tick marks will be drawn if they are not spaced too densely. Otherwise,
84+
the tick marks will not be drawn. -->
85+
<enum name="autoHide" value="1" />
86+
<!-- Tick marks will not be drawn. -->
87+
<enum name="hidden" value="2" />
88+
</attr>
7589
<!-- The color of the track. -->
7690
<attr name="trackColor" />
7791
<!-- The color of active portion of the track. -->

lib/javatests/com/google/android/material/slider/SliderConfigTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,20 +89,20 @@ public static Iterable<Object[]> data() {
8989
new Object[] {
9090
/* valueFrom = */ 10f,
9191
/* valueTo = */ 3f,
92-
/* stepValue = */ 2f,
92+
/* stepValue = */ 0f,
9393
/* value = */ 10f,
9494
IllegalStateException.class,
9595
},
96-
new Object[] {
97-
/* valueFrom = */ 0f, /* valueTo = */ 1f, /* stepValue = */ 0, /* value = */ 0f, null,
98-
},
9996
new Object[] {
10097
/* valueFrom = */ 10f,
10198
/* valueTo = */ 3f,
10299
/* stepValue = */ 2f,
103100
/* value = */ 10f,
104101
IllegalStateException.class,
105102
},
103+
new Object[] {
104+
/* valueFrom = */ 0f, /* valueTo = */ 1f, /* stepValue = */ 0, /* value = */ 0f, null,
105+
},
106106
new Object[] {
107107
/* valueFrom = */ 0f,
108108
/* valueTo = */ 5f,

0 commit comments

Comments
 (0)