Skip to content

[Slider] Add new tick visibility modes #2897

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@
android:valueFrom="0"
android:valueTo="10"
android:stepSize="1"
app:tickVisible="false"/>
app:tickVisibilityMode="hidden" />
</LinearLayout>

<com.google.android.material.materialswitch.MaterialSwitch
Expand Down
7 changes: 5 additions & 2 deletions docs/components/Slider.md
Original file line number Diff line number Diff line change
Expand Up @@ -364,12 +364,15 @@ Element | Attribute | Related method(s)
| **Color for tick's inactive part** | `app:tickColorInactive` | `setTickInactiveTintList`<br/>`getTickInactiveTintList` | `?attr/colorPrimary` |
| **Radius for tick's active part** | `app:tickRadiusActive` | `setTickActiveRadius`<br/>`getTickActiveRadius` | `null` (1/2 trackStopIndicatorSize) |
| **Radius for tick's inactive part** | `app:tickRadiusInactive` | `setTickInactiveRadius`<br/>`getTickInactiveRadius` | `null` (1/2 trackStopIndicatorSize) |
| **Tick visible** | `app:tickVisible` | `setTickVisible`<br/>`isTickVisible()` | `true` |
| **Tick visible** (deprecated) | `app:tickVisible` | `setTickVisible`<br/>`isTickVisible()` | `true` |
| **Tick visibility mode** | `app:tickVisibilityMode` | `setTickVisibilityMode`<br/>`getTickVisibilityMode()` | `autoLimit` |

**Note:** `app:tickColor` takes precedence over `app:tickColorActive` and
`app:tickColorInative`. It's a shorthand for setting both values to the same
`app:tickColorInactive`. It's a shorthand for setting both values to the same
thing.

**Note:** `app:tickVisible` is deprecated in favor of `app:tickVisibilityMode`.

#### Styles

Element | Style
Expand Down
114 changes: 97 additions & 17 deletions lib/java/com/google/android/material/slider/BaseSlider.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
import static com.google.android.material.slider.LabelFormatter.LABEL_WITHIN_BOUNDS;
import static com.google.android.material.slider.SliderOrientation.HORIZONTAL;
import static com.google.android.material.slider.SliderOrientation.VERTICAL;
import static com.google.android.material.slider.TickVisibilityMode.TICK_VISIBILITY_AUTO_HIDE;
import static com.google.android.material.slider.TickVisibilityMode.TICK_VISIBILITY_AUTO_LIMIT;
import static com.google.android.material.slider.TickVisibilityMode.TICK_VISIBILITY_HIDDEN;
import static com.google.android.material.theme.overlay.MaterialThemeOverlay.wrap;
import static java.lang.Float.compare;
import static java.lang.Math.abs;
Expand Down Expand Up @@ -157,8 +160,10 @@
* discrete mode. This is a short hand for setting both the {@code tickColorActive} and {@code
* tickColorInactive} to the same thing. This takes precedence over {@code tickColorActive}
* and {@code tickColorInactive}.
* <li>{@code tickVisible}: Whether to show the tick marks. Only used when the slider is in
* discrete mode.
* <li>{@code tickVisible} (<b>deprecated</b>, use {@code tickVisibilityMode} instead):
* Whether to show the tick marks. Only used when the slider is in discrete mode.
* <li>{@code tickVisibilityMode}: Mode to specify the visibility of tick marks. Only used when
* the slider is in discrete mode.
* <li>{@code trackColorActive}: The color of the active part of the track.
* <li>{@code trackColorInactive}: The color of the inactive part of the track.
* <li>{@code trackColor}: The color of the whole track. This is a short hand for setting both the
Expand Down Expand Up @@ -210,6 +215,7 @@
* @attr ref com.google.android.material.R.styleable#Slider_tickColorActive
* @attr ref com.google.android.material.R.styleable#Slider_tickColorInactive
* @attr ref com.google.android.material.R.styleable#Slider_tickVisible
* @attr ref com.google.android.material.R.styleable#Slider_tickVisibilityMode
* @attr ref com.google.android.material.R.styleable#Slider_trackColor
* @attr ref com.google.android.material.R.styleable#Slider_trackColorActive
* @attr ref com.google.android.material.R.styleable#Slider_trackColorInactive
Expand Down Expand Up @@ -352,7 +358,7 @@ abstract class BaseSlider<
private int focusedThumbIdx = -1;
private float stepSize = 0.0f;
private float[] ticksCoordinates;
private boolean tickVisible = true;
private int tickVisibilityMode;
private int tickActiveRadius;
private int tickInactiveRadius;
private int trackWidth;
Expand Down Expand Up @@ -566,7 +572,10 @@ private void processAttributes(Context context, AttributeSet attrs, int defStyle
? haloColor
: AppCompatResources.getColorStateList(context, R.color.material_slider_halo_color));

tickVisible = a.getBoolean(R.styleable.Slider_tickVisible, true);
tickVisibilityMode = a.hasValue(R.styleable.Slider_tickVisibilityMode)
? a.getInt(R.styleable.Slider_tickVisibilityMode, -1)
: convertToTickVisibilityMode(a.getBoolean(R.styleable.Slider_tickVisible, true));

boolean hasTickColor = a.hasValue(R.styleable.Slider_tickColor);
int tickColorInactiveRes =
hasTickColor ? R.styleable.Slider_tickColor : R.styleable.Slider_tickColorInactive;
Expand Down Expand Up @@ -1768,22 +1777,60 @@ public void setTickInactiveTintList(@NonNull ColorStateList tickColor) {
/**
* Returns whether the tick marks are visible. Only used when the slider is in discrete mode.
*
* @see #setTickVisible(boolean)
* @attr ref com.google.android.material.R.styleable#Slider_tickVisible
*/
public boolean isTickVisible() {
return tickVisible;
switch (tickVisibilityMode) {
case TICK_VISIBILITY_AUTO_LIMIT:
return true;
case TICK_VISIBILITY_AUTO_HIDE:
return getDesiredTickCount() <= getMaxTickCount();
case TICK_VISIBILITY_HIDDEN:
return false;
default:
throw new RuntimeException("Unexpected tickVisibilityMode: " + tickVisibilityMode);
}
}

/**
* Sets whether the tick marks are visible. Only used when the slider is in discrete mode.
*
* @param tickVisible The visibility of tick marks.
* @attr ref com.google.android.material.R.styleable#Slider_tickVisible
* @deprecated Use {@link #setTickVisibilityMode(int)} instead.
*/
@Deprecated
public void setTickVisible(boolean tickVisible) {
if (this.tickVisible != tickVisible) {
this.tickVisible = tickVisible;
setTickVisibilityMode(convertToTickVisibilityMode(tickVisible));
}

@TickVisibilityMode
private int convertToTickVisibilityMode(boolean tickVisible) {
return tickVisible
? TICK_VISIBILITY_AUTO_LIMIT
: TICK_VISIBILITY_HIDDEN;
}

/**
* Returns the current tick visibility mode.
*
* @see #setTickVisibilityMode(int)
* @attr ref com.google.android.material.R.styleable#Slider_tickVisibilityMode
*/
@TickVisibilityMode
public int getTickVisibilityMode() {
return tickVisibilityMode;
}

/**
* Sets the tick visibility mode. Only used when the slider is in discrete mode.
*
* @see #getTickVisibilityMode()
* @attr ref com.google.android.material.R.styleable#Slider_tickVisibilityMode
*/
public void setTickVisibilityMode(@TickVisibilityMode int tickVisibilityMode) {
if (this.tickVisibilityMode != tickVisibilityMode) {
this.tickVisibilityMode = tickVisibilityMode;
postInvalidate();
}
}
Expand Down Expand Up @@ -2337,24 +2384,57 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
updateHaloHotspot();
}

private void maybeCalculateTicksCoordinates() {
private void updateTicksCoordinates() {
validateConfigurationIfDirty();

if (stepSize <= 0.0f) {
updateTicksCoordinates(/* tickCount= */ 0);
return;
}

validateConfigurationIfDirty();
final int tickCount;
switch (tickVisibilityMode) {
case TICK_VISIBILITY_AUTO_LIMIT:
tickCount = min(getDesiredTickCount(), getMaxTickCount());
break;
case TICK_VISIBILITY_AUTO_HIDE:
int desiredTickCount = getDesiredTickCount();
tickCount = desiredTickCount <= getMaxTickCount() ? desiredTickCount : 0;
break;
case TICK_VISIBILITY_HIDDEN:
tickCount = 0;
break;
default:
throw new RuntimeException("Unexpected tickVisibilityMode: " + tickVisibilityMode);
}

updateTicksCoordinates(tickCount);
}

private int getDesiredTickCount() {
return (int) ((valueTo - valueFrom) / stepSize + 1);
}

private int getMaxTickCount() {
return trackWidth / minTickSpacing + 1;
}

private void updateTicksCoordinates(int tickCount) {
if (tickCount == 0) {
ticksCoordinates = null;
return;
}

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

float interval = trackWidth / (float) (tickCount - 1);
float trackCenterY = calculateTrackCenter();

for (int i = 0; i < tickCount * 2; i += 2) {
ticksCoordinates[i] = trackSidePadding + i / 2f * interval;
ticksCoordinates[i + 1] = calculateTrackCenter();
ticksCoordinates[i + 1] = trackCenterY;
}

if (isVertical()) {
Expand All @@ -2367,7 +2447,7 @@ private void updateTrackWidth(int width) {
trackWidth = max(width - trackSidePadding * 2, 0);

// Update the visible tick coordinates.
maybeCalculateTicksCoordinates();
updateTicksCoordinates();
}

private void updateHaloHotspot() {
Expand Down Expand Up @@ -2404,7 +2484,7 @@ protected void onDraw(@NonNull Canvas canvas) {
validateConfigurationIfDirty();

// Update the visible tick coordinates.
maybeCalculateTicksCoordinates();
updateTicksCoordinates();
}

super.onDraw(canvas);
Expand Down Expand Up @@ -2703,7 +2783,7 @@ private float[] getCornerRadii(float leftSide, float rightSide) {
}

private void maybeDrawTicks(@NonNull Canvas canvas) {
if (!tickVisible || stepSize <= 0.0f) {
if (ticksCoordinates == null || ticksCoordinates.length == 0) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.material.slider;

import androidx.annotation.IntDef;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
* Mode to specify the visibility of tick marks.
*/
@IntDef({
TickVisibilityMode.TICK_VISIBILITY_AUTO_LIMIT,
TickVisibilityMode.TICK_VISIBILITY_AUTO_HIDE,
TickVisibilityMode.TICK_VISIBILITY_HIDDEN
})
@Retention(RetentionPolicy.SOURCE)
public @interface TickVisibilityMode {

/**
* All tick marks will be drawn if they are not spaced too densely. Otherwise, the maximum
* allowed number of tick marks will be drawn.
* Note that in this case the drawn ticks may not match the actual snap values.
*/
int TICK_VISIBILITY_AUTO_LIMIT = 0;

/**
* All tick marks will be drawn if they are not spaced too densely. Otherwise, the tick marks
* will not be drawn.
*/
int TICK_VISIBILITY_AUTO_HIDE = 1;

/**
* Tick marks will not be drawn.
*/
int TICK_VISIBILITY_HIDDEN = 2;
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<public name="tickRadiusActive" type="attr" />
<public name="tickRadiusInactive" type="attr" />
<public name="tickVisible" type="attr" />
<public name="tickVisibilityMode" type="attr" />
<public name="trackColorActive" type="attr" />
<public name="trackColorInactive" type="attr" />
<public name="trackHeight" type="attr" />
Expand Down
16 changes: 15 additions & 1 deletion lib/java/com/google/android/material/slider/res/values/attrs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,22 @@
<attr name="tickRadiusActive" format="dimension" />
<!-- The radius of the inactive tick. Only used when the slider is in discrete mode.-->
<attr name="tickRadiusInactive" format="dimension" />
<!-- Whether to show the tick marks. Only used when the slider is in discrete mode. -->
<!-- Whether to show the tick marks. Only used when the slider is in discrete mode.
{@deprecated Use tickVisibilityMode instead.} -->
<attr name="tickVisible" format="boolean" />
<!-- Mode to specify the visibility of tick marks. Only used when the slider is in
discrete mode. -->
<attr name="tickVisibilityMode" format="enum">
<!-- All tick marks will be drawn if they are not spaced too densely. Otherwise,
the maximum allowed number of tick marks will be drawn.
Note that in this case the drawn ticks may not match the actual snap values. -->
<enum name="autoLimit" value="0" />
<!-- All tick marks will be drawn if they are not spaced too densely. Otherwise,
the tick marks will not be drawn. -->
<enum name="autoHide" value="1" />
<!-- Tick marks will not be drawn. -->
<enum name="hidden" value="2" />
</attr>
<!-- The color of the track. -->
<attr name="trackColor" />
<!-- The color of active portion of the track. -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,20 +89,20 @@ public static Iterable<Object[]> data() {
new Object[] {
/* valueFrom = */ 10f,
/* valueTo = */ 3f,
/* stepValue = */ 2f,
/* stepValue = */ 0f,
/* value = */ 10f,
IllegalStateException.class,
},
new Object[] {
/* valueFrom = */ 0f, /* valueTo = */ 1f, /* stepValue = */ 0, /* value = */ 0f, null,
},
new Object[] {
/* valueFrom = */ 10f,
/* valueTo = */ 3f,
/* stepValue = */ 2f,
/* value = */ 10f,
IllegalStateException.class,
},
new Object[] {
/* valueFrom = */ 0f, /* valueTo = */ 1f, /* stepValue = */ 0, /* value = */ 0f, null,
},
new Object[] {
/* valueFrom = */ 0f,
/* valueTo = */ 5f,
Expand Down