Skip to content

Commit b8f4b22

Browse files
ymarianhunterstich
authored andcommitted
[Slider] Added min separation support
PiperOrigin-RevId: 322212299
1 parent f7bad9a commit b8f4b22

File tree

5 files changed

+162
-18
lines changed

5 files changed

+162
-18
lines changed

docs/components/Slider.md

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -287,17 +287,18 @@ slider also has tick marks.
287287

288288
#### Track attributes
289289

290-
  | Attribute | Related method(s) | Default value
291-
--------------------------------------- | ------------------------ | --------------------------------------------------------- | -------------
292-
**Min value** | `android:valueFrom` | `setValueFrom`<br/>`getValueFrom` | N/A
293-
**Max value** | `android:valueTo` | `setValueTo`<br/>`getValueTo` | N/A
294-
**Step size (discrete)** | `android:stepSize` | `setStepSize`<br/>`getStepSize` | N/A
295-
**Initial selected value (continuous)** | `android:value` | `setValue`<br/>`getValue` | N/A
296-
**Initial selected values (discrete)** | `app:values` | `setValues`<br/>`getValues` | N/A
297-
**Height** | `app:trackHeight` | `setTrackHeight`<br/>`getTrackHeight` | `4dp`
298-
**Color** | `app:trackColor` | `setTrackTintList`<br/>`getTrackTintList` | `null`
299-
**Color for track's active part** | `app:trackColorActive` | `setTrackActiveTintList`<br/>`getTrackActiveTintList` | `?attr/colorPrimary`
300-
**Color for track's inactive part** | `app:trackColorInactive` | `setTrackInactiveTintList`<br/>`getTrackInactiveTintList` | `?attr/colorPrimary` at 24%
290+
&nbsp; | Attribute | Related method(s) | Default value
291+
------------------------------------------- | ------------------------ | --------------------------------------------------------- | -------------
292+
**Min value** | `android:valueFrom` | `setValueFrom`<br/>`getValueFrom` | N/A
293+
**Max value** | `android:valueTo` | `setValueTo`<br/>`getValueTo` | N/A
294+
**Step size (discrete)** | `android:stepSize` | `setStepSize`<br/>`getStepSize` | N/A
295+
**Initial selected value (Slider)** | `android:value` | `setValue`<br/>`getValue` | N/A
296+
**Initial selected values (RangeSlider)** | `app:values` | `setValues`<br/>`getValues` | N/A
297+
**Height** | `app:trackHeight` | `setTrackHeight`<br/>`getTrackHeight` | `4dp`
298+
**Color** | `app:trackColor` | `setTrackTintList`<br/>`getTrackTintList` | `null`
299+
**Color for track's active part** | `app:trackColorActive` | `setTrackActiveTintList`<br/>`getTrackActiveTintList` | `?attr/colorPrimary`
300+
**Color for track's inactive part** | `app:trackColorInactive` | `setTrackInactiveTintList`<br/>`getTrackInactiveTintList` | `?attr/colorPrimary` at 24%
301+
**Minimum separation for adjacent thumbs** | `app:minSeparation` | `setMinSeparation`<br/>`getMinSeparation` | `0dp`
301302

302303
_**Note:** `app:trackColor` takes precedence over `app:trackColorActive` and
303304
`app:trackColorInative`. It's a shorthand for setting both values to the same

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

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,9 @@ abstract class BaseSlider<
195195
private static final int HALO_ALPHA = 63;
196196
private static final double THRESHOLD = .0001;
197197

198-
private static final int DEF_STYLE_RES = R.style.Widget_MaterialComponents_Slider;
198+
static final int DEF_STYLE_RES = R.style.Widget_MaterialComponents_Slider;
199+
static final int UNIT_VALUE = 1;
200+
static final int UNIT_PX = 0;
199201

200202
@NonNull private final Paint inactiveTrackPaint;
201203
@NonNull private final Paint activeTrackPaint;
@@ -255,6 +257,7 @@ private interface TooltipDrawableFactory {
255257
@NonNull private final MaterialShapeDrawable thumbDrawable = new MaterialShapeDrawable();
256258

257259
private float touchPosition;
260+
@SeparationUnit private int separationUnit = UNIT_PX;
258261

259262
/**
260263
* Determines the behavior of the label which can be any of the following.
@@ -273,6 +276,10 @@ private interface TooltipDrawableFactory {
273276
@Retention(RetentionPolicy.SOURCE)
274277
@interface LabelBehavior {}
275278

279+
@IntDef({UNIT_PX, UNIT_VALUE})
280+
@Retention(RetentionPolicy.SOURCE)
281+
@interface SeparationUnit {}
282+
276283
public BaseSlider(@NonNull Context context) {
277284
this(context, null);
278285
}
@@ -1639,11 +1646,32 @@ private boolean snapThumbToValue(int idx, float value) {
16391646

16401647
/** Thumbs cannot cross each other, clamp the value to a bound or the value next to it. */
16411648
private float getClampedValue(int idx, float value) {
1642-
float upperBound = idx + 1 >= values.size() ? valueTo : values.get(idx + 1);
1643-
float lowerBound = idx - 1 < 0 ? valueFrom : values.get(idx - 1);
1649+
float minSeparation = stepSize == 0 ? getMinSeparation() : 0;
1650+
minSeparation = separationUnit == UNIT_PX ? dimenToValue(minSeparation) : minSeparation;
1651+
if (isRtl()) {
1652+
minSeparation = -minSeparation;
1653+
}
1654+
1655+
float upperBound = idx + 1 >= values.size() ? valueTo : values.get(idx + 1) - minSeparation;
1656+
float lowerBound = idx - 1 < 0 ? valueFrom : values.get(idx - 1) + minSeparation;
16441657
return clamp(value, lowerBound, upperBound);
16451658
}
16461659

1660+
private float dimenToValue(float dimen) {
1661+
if (dimen == 0) {
1662+
return 0;
1663+
}
1664+
return ((dimen - trackSidePadding) / trackWidth) * (valueFrom - valueTo) + valueFrom;
1665+
}
1666+
1667+
protected void setSeparationUnit(int separationUnit) {
1668+
this.separationUnit = separationUnit;
1669+
}
1670+
1671+
protected float getMinSeparation() {
1672+
return 0;
1673+
}
1674+
16471675
private float getValueOfTouchPosition() {
16481676
double position = snapPosition(touchPosition);
16491677

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

Lines changed: 114 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,14 @@
2020

2121
import android.content.Context;
2222
import android.content.res.TypedArray;
23+
import android.os.Parcel;
24+
import android.os.Parcelable;
2325
import android.util.AttributeSet;
26+
import android.view.AbsSavedState;
27+
import androidx.annotation.Dimension;
2428
import androidx.annotation.NonNull;
2529
import androidx.annotation.Nullable;
30+
import com.google.android.material.internal.ThemeEnforcement;
2631
import com.google.android.material.slider.RangeSlider.OnChangeListener;
2732
import com.google.android.material.slider.RangeSlider.OnSliderTouchListener;
2833
import java.util.ArrayList;
@@ -37,10 +42,17 @@
3742
* <p>{@code app:values}: <b>Optional.</b> The initial values of the range slider. If not specified,
3843
* the range slider will only have one value equal to {@code android:valueFrom}
3944
*
45+
* <p>{@code app:minSeparation}: <b>Optional.</b> The minimum distance between two thumbs that would
46+
* otherwise overlap.
47+
*
4048
* @attr ref com.google.android.material.R.styleable#RangeSlider_values
49+
* @attr ref com.google.android.material.R.styleable#RangeSlider_minSeparation
4150
*/
4251
public class RangeSlider extends BaseSlider<RangeSlider, OnChangeListener, OnSliderTouchListener> {
4352

53+
private float minSeparation;
54+
private int separationUnit;
55+
4456
public RangeSlider(@NonNull Context context) {
4557
this(context, null);
4658
}
@@ -51,13 +63,16 @@ public RangeSlider(@NonNull Context context, @Nullable AttributeSet attrs) {
5163

5264
public RangeSlider(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
5365
super(context, attrs, defStyleAttr);
54-
TypedArray a = context.obtainStyledAttributes(attrs, new int[] {R.attr.values});
55-
56-
if (a.hasValue(0)) {
57-
int valuesId = a.getResourceId(0, 0);
66+
TypedArray a =
67+
ThemeEnforcement.obtainStyledAttributes(
68+
context, attrs, R.styleable.RangeSlider, defStyleAttr, DEF_STYLE_RES);
69+
if (a.hasValue(R.styleable.RangeSlider_values)) {
70+
int valuesId = a.getResourceId(R.styleable.RangeSlider_values, 0);
5871
TypedArray values = a.getResources().obtainTypedArray(valuesId);
5972
setValues(convertToFloat(values));
6073
}
74+
75+
minSeparation = a.getDimension(R.styleable.RangeSlider_minSeparation, 0);
6176
a.recycle();
6277
}
6378

@@ -104,4 +119,99 @@ private static List<Float> convertToFloat(TypedArray values) {
104119
}
105120
return ret;
106121
}
122+
123+
/**
124+
* Returns the minimum separation between two thumbs
125+
*
126+
* @see #setMinSeparation(float)
127+
* @attr ref com.google.android.material.R.styleable#RangeSlider_minSeparation
128+
*/
129+
@Override
130+
public float getMinSeparation() {
131+
return minSeparation;
132+
}
133+
134+
/**
135+
* Sets the minimum separation between two thumbs
136+
*
137+
* @see #getMinSeparation()
138+
* @attr ref com.google.android.material.R.styleable#RangeSlider_minSeparation
139+
*/
140+
public void setMinSeparation(@Dimension float minSeparation) {
141+
this.minSeparation = minSeparation;
142+
separationUnit = UNIT_PX;
143+
setSeparationUnit(separationUnit);
144+
}
145+
146+
/**
147+
* Sets the minimum separation in the value scale. Useful to create minimum ranges, between
148+
* thumbs.
149+
*
150+
* @see #getMinSeparation()
151+
* @see #setMinSeparation(float)
152+
* @attr ref com.google.android.material.R.styleable#RangeSlider_minSeparation
153+
*/
154+
public void setMinSeparationValue(float minSeparation) {
155+
this.minSeparation = minSeparation;
156+
separationUnit = UNIT_VALUE;
157+
setSeparationUnit(separationUnit);
158+
}
159+
160+
@Override
161+
@NonNull
162+
public Parcelable onSaveInstanceState() {
163+
Parcelable superState = super.onSaveInstanceState();
164+
165+
RangeSliderState sliderState = new RangeSliderState(superState);
166+
sliderState.minSeparation = this.minSeparation;
167+
sliderState.separationUnit = this.separationUnit;
168+
169+
return sliderState;
170+
}
171+
172+
@Override
173+
protected void onRestoreInstanceState(@Nullable Parcelable state) {
174+
RangeSliderState savedState = (RangeSliderState) state;
175+
super.onRestoreInstanceState(savedState.getSuperState());
176+
177+
this.minSeparation = savedState.minSeparation;
178+
this.separationUnit = savedState.separationUnit;
179+
setSeparationUnit(separationUnit);
180+
}
181+
182+
static class RangeSliderState extends AbsSavedState {
183+
184+
private float minSeparation;
185+
private int separationUnit;
186+
187+
RangeSliderState(Parcelable superState) {
188+
super(superState);
189+
}
190+
191+
private RangeSliderState(Parcel in) {
192+
super((Parcelable) in.readParcelable(RangeSliderState.class.getClassLoader()));
193+
minSeparation = in.readFloat();
194+
separationUnit = in.readInt();
195+
}
196+
197+
@Override
198+
public void writeToParcel(Parcel out, int flags) {
199+
super.writeToParcel(out, flags);
200+
out.writeFloat(minSeparation);
201+
out.writeInt(separationUnit);
202+
}
203+
204+
public static final Parcelable.Creator<RangeSliderState> CREATOR =
205+
new Parcelable.Creator<RangeSliderState>() {
206+
@Override
207+
public RangeSliderState createFromParcel(Parcel in) {
208+
return new RangeSliderState(in);
209+
}
210+
211+
@Override
212+
public RangeSliderState[] newArray(int size) {
213+
return new RangeSliderState[size];
214+
}
215+
};
216+
}
107217
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@
6161

6262
<declare-styleable name="RangeSlider">
6363
<attr name="values" format="reference" />
64+
65+
<!-- If there is more than one thumb, and the slider is
66+
not discrete the thumbs will be separated by this dimen -->
67+
<attr name="minSeparation" format="dimension"/>
6468
</declare-styleable>
6569

6670
<!-- Style to use for Slider in this theme. -->

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
<item name="trackColorActive">@color/material_slider_active_track_color</item>
2929
<item name="trackColorInactive">@color/material_slider_inactive_track_color</item>
3030
<item name="trackHeight">@dimen/mtrl_slider_track_height</item>
31+
<item name="minSeparation">0dp</item>
3132
</style>
3233

3334
<style name="Widget.MaterialComponents.Slider" parent="Base.Widget.MaterialComponents.Slider"/>

0 commit comments

Comments
 (0)