Skip to content

Commit 6eab388

Browse files
committed
[Slider] Add new tick visibility modes
1 parent bb646b6 commit 6eab388

File tree

7 files changed

+173
-26
lines changed

7 files changed

+173
-26
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: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ selections from a range of values.
1616

1717
**Contents**
1818

19-
* [Design & API Documentation](#design-api-documentation)
19+
* [Design & API Documentation](#design--api-documentation)
2020
* [Using sliders](#using-sliders)
2121
* [Continuous slider](#continuous-slider)
2222
* [Discrete slider](#discrete-slider)
@@ -348,12 +348,15 @@ Element | Attribute | Related method(s)
348348
| **Color for tick's inactive part** | `app:tickColorInactive` | `setTickInactiveTintList`<br/>`getTickInactiveTintList` | `?attr/colorPrimary` |
349349
| **Radius for tick's active part** | `app:tickRadiusActive` | `setTickActiveRadius`<br/>`getTickActiveRadius` | `1dp` |
350350
| **Radius for tick's inactive part** | `app:tickRadiusInactive` | `setTickInactiveRadius`<br/>`getTickInactiveRadius` | `1dp` |
351-
| **Tick visible** | `app:tickVisible` | `setTickVisible`<br/>`isTickVisible()` | `true` |
351+
| **Tick visible** (deprecated) | `app:tickVisible` | `setTickVisible`<br/>`isTickVisible()` | `true` |
352+
| **Tick visibility mode** | `app:tickVisibilityMode` | `setTickVisibilityMode`<br/>`getTickVisibilityMode()` | `autoLimit` |
352353

353354
**Note:** `app:tickColor` takes precedence over `app:tickColorActive` and
354-
`app:tickColorInative`. It's a shorthand for setting both values to the same
355+
`app:tickColorInactive`. It's a shorthand for setting both values to the same
355356
thing.
356357

358+
**Note:** `app:tickVisible` is deprecated in favor of `app:tickVisibilityMode`.
359+
357360
#### Styles
358361

359362
Element | Style

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

Lines changed: 102 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
import static com.google.android.material.slider.LabelFormatter.LABEL_GONE;
2626
import static com.google.android.material.slider.LabelFormatter.LABEL_VISIBLE;
2727
import static com.google.android.material.slider.LabelFormatter.LABEL_WITHIN_BOUNDS;
28+
import static com.google.android.material.slider.TickVisibilityMode.TICK_VISIBILITY_AUTO_HIDE;
29+
import static com.google.android.material.slider.TickVisibilityMode.TICK_VISIBILITY_AUTO_LIMIT;
30+
import static com.google.android.material.slider.TickVisibilityMode.TICK_VISIBILITY_HIDDEN;
31+
import static com.google.android.material.slider.TickVisibilityMode.TICK_VISIBILITY_VISIBLE_ALL;
2832
import static com.google.android.material.theme.overlay.MaterialThemeOverlay.wrap;
2933
import static java.lang.Float.compare;
3034
import static java.lang.Math.abs;
@@ -151,8 +155,10 @@
151155
* discrete mode. This is a short hand for setting both the {@code tickColorActive} and {@code
152156
* tickColorInactive} to the same thing. This takes precedence over {@code tickColorActive}
153157
* and {@code tickColorInactive}.
154-
* <li>{@code tickVisible}: Whether to show the tick marks. Only used when the slider is in
155-
* discrete mode.
158+
* <li>{@code tickVisible} (deprecated, use {@code tickVisibilityMode} instead): Whether to show
159+
* the tick marks. Only used when the slider is in discrete mode.
160+
* <li>{@code tickVisibilityMode}: Mode to specify the visibility of tick marks. Only used when
161+
* the slider is in discrete mode.
156162
* <li>{@code trackColorActive}: The color of the active part of the track.
157163
* <li>{@code trackColorInactive}: The color of the inactive part of the track.
158164
* <li>{@code trackColor}: The color of the whole track. This is a short hand for setting both the
@@ -202,6 +208,7 @@
202208
* @attr ref com.google.android.material.R.styleable#Slider_tickColorActive
203209
* @attr ref com.google.android.material.R.styleable#Slider_tickColorInactive
204210
* @attr ref com.google.android.material.R.styleable#Slider_tickVisible
211+
* @attr ref com.google.android.material.R.styleable#Slider_tickVisibilityMode
205212
* @attr ref com.google.android.material.R.styleable#Slider_trackColor
206213
* @attr ref com.google.android.material.R.styleable#Slider_trackColorActive
207214
* @attr ref com.google.android.material.R.styleable#Slider_trackColorInactive
@@ -322,7 +329,7 @@ abstract class BaseSlider<
322329
private int focusedThumbIdx = -1;
323330
private float stepSize = 0.0f;
324331
private float[] ticksCoordinates;
325-
private boolean tickVisible = true;
332+
private int tickVisibilityMode;
326333
private int tickActiveRadius;
327334
private int tickInactiveRadius;
328335
private int trackWidth;
@@ -501,7 +508,10 @@ private void processAttributes(Context context, AttributeSet attrs, int defStyle
501508
? haloColor
502509
: AppCompatResources.getColorStateList(context, R.color.material_slider_halo_color));
503510

504-
tickVisible = a.getBoolean(R.styleable.Slider_tickVisible, true);
511+
tickVisibilityMode = a.hasValue(R.styleable.Slider_tickVisibilityMode)
512+
? a.getInt(R.styleable.Slider_tickVisibilityMode, -1)
513+
: convertToTickVisibilityMode(a.getBoolean(R.styleable.Slider_tickVisible, true));
514+
505515
boolean hasTickColor = a.hasValue(R.styleable.Slider_tickColor);
506516
int tickColorInactiveRes =
507517
hasTickColor ? R.styleable.Slider_tickColor : R.styleable.Slider_tickColorInactive;
@@ -1668,22 +1678,61 @@ public void setTickInactiveTintList(@NonNull ColorStateList tickColor) {
16681678
/**
16691679
* Returns whether the tick marks are visible. Only used when the slider is in discrete mode.
16701680
*
1671-
* @see #setTickVisible(boolean)
16721681
* @attr ref com.google.android.material.R.styleable#Slider_tickVisible
16731682
*/
16741683
public boolean isTickVisible() {
1675-
return tickVisible;
1684+
switch (tickVisibilityMode) {
1685+
case TICK_VISIBILITY_VISIBLE_ALL:
1686+
case TICK_VISIBILITY_AUTO_LIMIT:
1687+
return true;
1688+
case TICK_VISIBILITY_AUTO_HIDE:
1689+
return getDesiredTickCount() <= getMaxTickCount();
1690+
case TICK_VISIBILITY_HIDDEN:
1691+
return false;
1692+
default:
1693+
throw new RuntimeException("Unexpected tickVisibilityMode: " + tickVisibilityMode);
1694+
}
16761695
}
16771696

16781697
/**
16791698
* Sets whether the tick marks are visible. Only used when the slider is in discrete mode.
16801699
*
16811700
* @param tickVisible The visibility of tick marks.
16821701
* @attr ref com.google.android.material.R.styleable#Slider_tickVisible
1702+
* @deprecated Use {@link #setTickVisibilityMode(int)} instead.
16831703
*/
1704+
@Deprecated
16841705
public void setTickVisible(boolean tickVisible) {
1685-
if (this.tickVisible != tickVisible) {
1686-
this.tickVisible = tickVisible;
1706+
setTickVisibilityMode(convertToTickVisibilityMode(tickVisible));
1707+
}
1708+
1709+
@TickVisibilityMode
1710+
private int convertToTickVisibilityMode(boolean tickVisible) {
1711+
return tickVisible
1712+
? TICK_VISIBILITY_AUTO_LIMIT
1713+
: TICK_VISIBILITY_HIDDEN;
1714+
}
1715+
1716+
/**
1717+
* Returns the current tick visibility mode.
1718+
*
1719+
* @see #setTickVisibilityMode(int)
1720+
* @attr ref com.google.android.material.R.styleable#Slider_tickVisibilityMode
1721+
*/
1722+
@TickVisibilityMode
1723+
public int getTickVisibilityMode() {
1724+
return tickVisibilityMode;
1725+
}
1726+
1727+
/**
1728+
* Sets the tick visibility mode. Only used when the slider is in discrete mode.
1729+
*
1730+
* @see #getTickVisibilityMode()
1731+
* @attr ref com.google.android.material.R.styleable#Slider_tickVisibilityMode
1732+
*/
1733+
public void setTickVisibilityMode(@TickVisibilityMode int tickVisibilityMode) {
1734+
if (this.tickVisibilityMode != tickVisibilityMode) {
1735+
this.tickVisibilityMode = tickVisibilityMode;
16871736
postInvalidate();
16881737
}
16891738
}
@@ -1933,24 +1982,60 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
19331982
updateHaloHotspot();
19341983
}
19351984

1936-
private void maybeCalculateTicksCoordinates() {
1985+
private void updateTicksCoordinates() {
1986+
validateConfigurationIfDirty();
1987+
19371988
if (stepSize <= 0.0f) {
1989+
updateTicksCoordinates(/* tickCount= */ 0);
19381990
return;
19391991
}
19401992

1941-
validateConfigurationIfDirty();
1993+
final int tickCount;
1994+
switch (tickVisibilityMode) {
1995+
case TICK_VISIBILITY_VISIBLE_ALL:
1996+
tickCount = getDesiredTickCount();
1997+
break;
1998+
case TICK_VISIBILITY_AUTO_LIMIT:
1999+
tickCount = min(getDesiredTickCount(), getMaxTickCount());
2000+
break;
2001+
case TICK_VISIBILITY_AUTO_HIDE:
2002+
int desiredTickCount = getDesiredTickCount();
2003+
tickCount = desiredTickCount <= getMaxTickCount() ? desiredTickCount : 0;
2004+
break;
2005+
case TICK_VISIBILITY_HIDDEN:
2006+
tickCount = 0;
2007+
break;
2008+
default:
2009+
throw new RuntimeException("Unexpected tickVisibilityMode: " + tickVisibilityMode);
2010+
}
2011+
2012+
updateTicksCoordinates(tickCount);
2013+
}
2014+
2015+
private int getDesiredTickCount() {
2016+
return (int) ((valueTo - valueFrom) / stepSize + 1);
2017+
}
2018+
2019+
private int getMaxTickCount() {
2020+
return trackWidth / minTickSpacing + 1;
2021+
}
2022+
2023+
private void updateTicksCoordinates(int tickCount) {
2024+
if (tickCount == 0) {
2025+
ticksCoordinates = null;
2026+
return;
2027+
}
19422028

1943-
int tickCount = (int) ((valueTo - valueFrom) / stepSize + 1);
1944-
// Limit the tickCount if they will be too dense.
1945-
tickCount = min(tickCount, trackWidth / minTickSpacing + 1);
19462029
if (ticksCoordinates == null || ticksCoordinates.length != tickCount * 2) {
19472030
ticksCoordinates = new float[tickCount * 2];
19482031
}
19492032

19502033
float interval = trackWidth / (float) (tickCount - 1);
2034+
float trackCenterY = calculateTrackCenter();
2035+
19512036
for (int i = 0; i < tickCount * 2; i += 2) {
19522037
ticksCoordinates[i] = trackSidePadding + i / 2f * interval;
1953-
ticksCoordinates[i + 1] = calculateTrackCenter();
2038+
ticksCoordinates[i + 1] = trackCenterY;
19542039
}
19552040
}
19562041

@@ -1959,7 +2044,7 @@ private void updateTrackWidth(int width) {
19592044
trackWidth = max(width - trackSidePadding * 2, 0);
19602045

19612046
// Update the visible tick coordinates.
1962-
maybeCalculateTicksCoordinates();
2047+
updateTicksCoordinates();
19632048
}
19642049

19652050
private void updateHaloHotspot() {
@@ -1988,7 +2073,7 @@ protected void onDraw(@NonNull Canvas canvas) {
19882073
validateConfigurationIfDirty();
19892074

19902075
// Update the visible tick coordinates.
1991-
maybeCalculateTicksCoordinates();
2076+
updateTicksCoordinates();
19922077
}
19932078

19942079
super.onDraw(canvas);
@@ -2213,7 +2298,7 @@ private float[] getCornerRadii(float leftSide, float rightSide) {
22132298
}
22142299

22152300
private void maybeDrawTicks(@NonNull Canvas canvas) {
2216-
if (!tickVisible || stepSize <= 0.0f) {
2301+
if (ticksCoordinates == null || ticksCoordinates.length == 0) {
22172302
return;
22182303
}
22192304

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.google.android.material.slider;
2+
3+
import androidx.annotation.IntDef;
4+
5+
import java.lang.annotation.Retention;
6+
import java.lang.annotation.RetentionPolicy;
7+
8+
/**
9+
* Mode to specify the visibility of tick marks.
10+
*/
11+
@IntDef({
12+
TickVisibilityMode.TICK_VISIBILITY_VISIBLE_ALL,
13+
TickVisibilityMode.TICK_VISIBILITY_AUTO_LIMIT,
14+
TickVisibilityMode.TICK_VISIBILITY_AUTO_HIDE,
15+
TickVisibilityMode.TICK_VISIBILITY_HIDDEN
16+
})
17+
@Retention(RetentionPolicy.SOURCE)
18+
public @interface TickVisibilityMode {
19+
20+
/**
21+
* All tick marks will be drawn, even if they are spaced too densely.
22+
*/
23+
int TICK_VISIBILITY_VISIBLE_ALL = 0;
24+
25+
/**
26+
* All tick marks will be drawn if they are not spaced too densely. Otherwise, the maximum
27+
* allowed number of tick marks will be drawn.
28+
* Note that in this case, the drawn ticks may not match the actual snap values.
29+
*/
30+
int TICK_VISIBILITY_AUTO_LIMIT = 1;
31+
32+
/**
33+
* All tick marks will be drawn if they are not spaced too densely. Otherwise, the tick marks
34+
* will not be drawn.
35+
*/
36+
int TICK_VISIBILITY_AUTO_HIDE = 2;
37+
38+
/**
39+
* Tick marks will not be drawn.
40+
*/
41+
int TICK_VISIBILITY_HIDDEN = 3;
42+
}

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: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,24 @@
6969
<attr name="tickRadiusActive" format="dimension" />
7070
<!-- The radius of the inactive tick. Only used when the slider is in discrete mode.-->
7171
<attr name="tickRadiusInactive" format="dimension" />
72-
<!-- Whether to show the tick marks. Only used when the slider is in discrete mode. -->
72+
<!-- Whether to show the tick marks. Only used when the slider is in discrete mode.
73+
{@deprecated Use tickVisibilityMode instead.} -->
7374
<attr name="tickVisible" format="boolean" />
75+
<!-- Mode to specify the visibility of tick marks. Only used when the slider is in
76+
discrete mode. -->
77+
<attr name="tickVisibilityMode" format="enum">
78+
<!-- All tick marks will be drawn, even if they are spaced too densely. -->
79+
<enum name="visibleAll" value="0" />
80+
<!-- All tick marks will be drawn if they are not spaced too densely. Otherwise,
81+
the maximum allowed number of tick marks will be drawn.
82+
Note that in this case, the drawn ticks may not match the actual snap values. -->
83+
<enum name="autoLimit" value="1" />
84+
<!-- All tick marks will be drawn if they are not spaced too densely. Otherwise,
85+
the tick marks will not be drawn. -->
86+
<enum name="autoHide" value="2" />
87+
<!-- Tick marks will not be drawn. -->
88+
<enum name="hidden" value="3" />
89+
</attr>
7490
<!-- The color of the track. -->
7591
<attr name="trackColor" />
7692
<!-- 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)