Skip to content

Commit 17da000

Browse files
drchenhunterstich
authored andcommitted
[Slider] Add support for custom thumb drawables
Resolves #1522 PiperOrigin-RevId: 429607888
1 parent 0803a22 commit 17da000

File tree

3 files changed

+195
-30
lines changed

3 files changed

+195
-30
lines changed

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

Lines changed: 143 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
import androidx.annotation.ColorRes;
6868
import androidx.annotation.DimenRes;
6969
import androidx.annotation.Dimension;
70+
import androidx.annotation.DrawableRes;
7071
import androidx.annotation.IntDef;
7172
import androidx.annotation.IntRange;
7273
import androidx.annotation.NonNull;
@@ -293,7 +294,9 @@ private interface TooltipDrawableFactory {
293294
@NonNull private ColorStateList trackColorActive;
294295
@NonNull private ColorStateList trackColorInactive;
295296

296-
@NonNull private final MaterialShapeDrawable thumbDrawable = new MaterialShapeDrawable();
297+
@NonNull private final MaterialShapeDrawable defaultThumbDrawable = new MaterialShapeDrawable();
298+
@Nullable private Drawable customThumbDrawable;
299+
@NonNull private List<Drawable> customThumbDrawablesForValues = Collections.emptyList();
297300

298301
private float touchPosition;
299302
@SeparationUnit private int separationUnit = UNIT_PX;
@@ -379,7 +382,8 @@ public TooltipDrawable createTooltipDrawable() {
379382
setClickable(true);
380383

381384
// Set up the thumb drawable to always show the compat shadow.
382-
thumbDrawable.setShadowCompatibilityMode(MaterialShapeDrawable.SHADOW_COMPAT_MODE_ALWAYS);
385+
defaultThumbDrawable.setShadowCompatibilityMode(
386+
MaterialShapeDrawable.SHADOW_COMPAT_MODE_ALWAYS);
383387

384388
scaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
385389

@@ -435,7 +439,7 @@ private void processAttributes(Context context, AttributeSet attrs, int defStyle
435439
context, R.color.material_slider_active_track_color));
436440
ColorStateList thumbColor =
437441
MaterialResources.getColorStateList(context, a, R.styleable.Slider_thumbColor);
438-
thumbDrawable.setFillColor(thumbColor);
442+
defaultThumbDrawable.setFillColor(thumbColor);
439443

440444
if (a.hasValue(R.styleable.Slider_thumbStrokeColor)) {
441445
setThumbStrokeColor(
@@ -801,6 +805,96 @@ public void setStepSize(float stepSize) {
801805
}
802806
}
803807

808+
/**
809+
* Sets the custom thumb drawable which will be used for all value positions. Note that the custom
810+
* drawable provided will be resized to match the thumb radius set by {@link #setThumbRadius(int)}
811+
* or {@link #setThumbRadiusResource(int)}. Be aware that the image quality may be compromised
812+
* during resizing.
813+
*
814+
* @see #setCustomThumbDrawable(Drawable)
815+
* @see #setCustomThumbDrawablesForValues(int...)
816+
* @see #setCustomThumbDrawablesForValues(Drawable...)
817+
*/
818+
void setCustomThumbDrawable(@DrawableRes int drawableResId) {
819+
setCustomThumbDrawable(getResources().getDrawable(drawableResId));
820+
}
821+
822+
/**
823+
* Sets the custom thumb drawable which will be used for all value positions. Note that the custom
824+
* drawable provided will be resized to match the thumb radius set by {@link #setThumbRadius(int)}
825+
* or {@link #setThumbRadiusResource(int)}. Be aware that the image quality may be compromised
826+
* during resizing.
827+
*
828+
* @see #setCustomThumbDrawable(int)
829+
* @see #setCustomThumbDrawablesForValues(int...)
830+
* @see #setCustomThumbDrawablesForValues(Drawable...)
831+
*/
832+
void setCustomThumbDrawable(@NonNull Drawable drawable) {
833+
customThumbDrawable = initializeCustomThumbDrawable(drawable);
834+
customThumbDrawablesForValues.clear();
835+
postInvalidate();
836+
}
837+
838+
/**
839+
* Sets custom thumb drawables. The drawables provided will be used in its corresponding value
840+
* position - i.e., the first drawable will be used to indicate the first value, and so on. If
841+
* the number of drawables is less than the number of values, the default drawable will be used
842+
* for the remaining values.
843+
*
844+
* <p>Note that the custom drawables provided will be resized to match the thumb radius set by
845+
* {@link #setThumbRadius(int)} or {@link #setThumbRadiusResource(int)}. Be aware that the image
846+
* quality may be compromised during resizing.
847+
*
848+
* @see #setCustomThumbDrawablesForValues(Drawable...)
849+
*/
850+
void setCustomThumbDrawablesForValues(@NonNull @DrawableRes int... customThumbDrawableResIds) {
851+
Drawable[] customThumbDrawables = new Drawable[customThumbDrawableResIds.length];
852+
for (int i = 0; i < customThumbDrawableResIds.length; i++) {
853+
customThumbDrawables[i] = getResources().getDrawable(customThumbDrawableResIds[i]);
854+
}
855+
setCustomThumbDrawablesForValues(customThumbDrawables);
856+
}
857+
858+
/**
859+
* Sets custom thumb drawables. The drawables provided will be used in its corresponding value
860+
* position - i.e., the first drawable will be used to indicate the first value, and so on. If
861+
* the number of drawables is less than the number of values, the default drawable will be used
862+
* for the remaining values.
863+
*
864+
* <p>Note that the custom drawables provided will be resized to match the thumb radius set by
865+
* {@link #setThumbRadius(int)} or {@link #setThumbRadiusResource(int)}. Be aware that the image
866+
* quality may be compromised during resizing.
867+
*
868+
* @see #setCustomThumbDrawablesForValues(int...)
869+
*/
870+
void setCustomThumbDrawablesForValues(@NonNull Drawable... customThumbDrawables) {
871+
this.customThumbDrawable = null;
872+
this.customThumbDrawablesForValues = new ArrayList<>();
873+
for (Drawable originalDrawable : customThumbDrawables) {
874+
this.customThumbDrawablesForValues.add(initializeCustomThumbDrawable(originalDrawable));
875+
}
876+
postInvalidate();
877+
}
878+
879+
private Drawable initializeCustomThumbDrawable(Drawable originalDrawable) {
880+
Drawable drawable = originalDrawable.mutate().getConstantState().newDrawable();
881+
adjustCustomThumbDrawableBounds(drawable);
882+
return drawable;
883+
}
884+
885+
private void adjustCustomThumbDrawableBounds(Drawable drawable) {
886+
int thumbDiameter = thumbRadius * 2;
887+
int originalWidth = drawable.getIntrinsicWidth();
888+
int originalHeight = drawable.getIntrinsicHeight();
889+
if (originalWidth == -1 && originalHeight == -1) {
890+
drawable.setBounds(0, 0, thumbDiameter, thumbDiameter);
891+
} else {
892+
float scaleRatio = (float) thumbDiameter / Math.max(originalWidth, originalHeight);
893+
drawable.setBounds(
894+
0, 0, (int) (originalWidth * scaleRatio), (int) (originalHeight * scaleRatio));
895+
}
896+
}
897+
804898
/** Returns the index of the currently focused thumb */
805899
public int getFocusedThumbIndex() {
806900
return focusedThumbIdx;
@@ -898,7 +992,7 @@ public void setLabelFormatter(@Nullable LabelFormatter formatter) {
898992
* @attr ref com.google.android.material.R.styleable#Slider_thumbElevation
899993
*/
900994
public float getThumbElevation() {
901-
return thumbDrawable.getElevation();
995+
return defaultThumbDrawable.getElevation();
902996
}
903997

904998
/**
@@ -908,7 +1002,7 @@ public float getThumbElevation() {
9081002
* @attr ref com.google.android.material.R.styleable#Slider_thumbElevation
9091003
*/
9101004
public void setThumbElevation(float elevation) {
911-
thumbDrawable.setElevation(elevation);
1005+
defaultThumbDrawable.setElevation(elevation);
9121006
}
9131007

9141008
/**
@@ -947,9 +1041,16 @@ public void setThumbRadius(@IntRange(from = 0) @Dimension int radius) {
9471041
thumbRadius = radius;
9481042
maybeIncreaseTrackSidePadding();
9491043

950-
thumbDrawable.setShapeAppearanceModel(
1044+
defaultThumbDrawable.setShapeAppearanceModel(
9511045
ShapeAppearanceModel.builder().setAllCorners(CornerFamily.ROUNDED, thumbRadius).build());
952-
thumbDrawable.setBounds(0, 0, thumbRadius * 2, thumbRadius * 2);
1046+
defaultThumbDrawable.setBounds(0, 0, thumbRadius * 2, thumbRadius * 2);
1047+
1048+
if (customThumbDrawable != null) {
1049+
adjustCustomThumbDrawableBounds(customThumbDrawable);
1050+
}
1051+
for (Drawable customDrawable : customThumbDrawablesForValues) {
1052+
adjustCustomThumbDrawableBounds(customDrawable);
1053+
}
9531054

9541055
postInvalidate();
9551056
}
@@ -974,7 +1075,7 @@ public void setThumbRadiusResource(@DimenRes int radius) {
9741075
* @see #getThumbStrokeColor()
9751076
*/
9761077
public void setThumbStrokeColor(@Nullable ColorStateList thumbStrokeColor) {
977-
thumbDrawable.setStrokeColor(thumbStrokeColor);
1078+
defaultThumbDrawable.setStrokeColor(thumbStrokeColor);
9781079
postInvalidate();
9791080
}
9801081

@@ -1003,7 +1104,7 @@ public void setThumbStrokeColorResource(@ColorRes int thumbStrokeColorResourceId
10031104
* @see #setThumbStrokeColorResource(int)
10041105
*/
10051106
public ColorStateList getThumbStrokeColor() {
1006-
return thumbDrawable.getStrokeColor();
1107+
return defaultThumbDrawable.getStrokeColor();
10071108
}
10081109

10091110
/**
@@ -1016,7 +1117,7 @@ public ColorStateList getThumbStrokeColor() {
10161117
* @see #getThumbStrokeWidth()
10171118
*/
10181119
public void setThumbStrokeWidth(float thumbStrokeWidth) {
1019-
thumbDrawable.setStrokeWidth(thumbStrokeWidth);
1120+
defaultThumbDrawable.setStrokeWidth(thumbStrokeWidth);
10201121
postInvalidate();
10211122
}
10221123

@@ -1044,7 +1145,7 @@ public void setThumbStrokeWidthResource(@DimenRes int thumbStrokeWidthResourceId
10441145
* @see #setThumbStrokeWidthResource(int)
10451146
*/
10461147
public float getThumbStrokeWidth() {
1047-
return thumbDrawable.getStrokeWidth();
1148+
return defaultThumbDrawable.getStrokeWidth();
10481149
}
10491150

10501151
/**
@@ -1194,7 +1295,7 @@ public void setHaloTintList(@NonNull ColorStateList haloColor) {
11941295
*/
11951296
@NonNull
11961297
public ColorStateList getThumbTintList() {
1197-
return thumbDrawable.getFillColor();
1298+
return defaultThumbDrawable.getFillColor();
11981299
}
11991300

12001301
/**
@@ -1204,11 +1305,11 @@ public ColorStateList getThumbTintList() {
12041305
* @attr ref com.google.android.material.R.styleable#Slider_thumbColor
12051306
*/
12061307
public void setThumbTintList(@NonNull ColorStateList thumbColor) {
1207-
if (thumbColor.equals(thumbDrawable.getFillColor())) {
1308+
if (thumbColor.equals(defaultThumbDrawable.getFillColor())) {
12081309
return;
12091310
}
12101311

1211-
thumbDrawable.setFillColor(thumbColor);
1312+
defaultThumbDrawable.setFillColor(thumbColor);
12121313
invalidate();
12131314
}
12141315

@@ -1634,23 +1735,35 @@ private void maybeDrawTicks(@NonNull Canvas canvas) {
16341735
}
16351736

16361737
private void drawThumbs(@NonNull Canvas canvas, int width, int top) {
1637-
// Clear out the track behind the thumb if we're in a disable state since the thumb is
1638-
// transparent.
1639-
if (!isEnabled()) {
1640-
for (Float value : values) {
1641-
canvas.drawCircle(
1642-
trackSidePadding + normalizeValue(value) * width, top, thumbRadius, thumbPaint);
1738+
for (int i = 0; i < values.size(); i++) {
1739+
float value = values.get(i);
1740+
if (customThumbDrawable != null) {
1741+
drawThumbDrawable(canvas, width, top, value, customThumbDrawable);
1742+
} else if (i < customThumbDrawablesForValues.size()) {
1743+
drawThumbDrawable(canvas, width, top, value, customThumbDrawablesForValues.get(i));
1744+
} else {
1745+
// Clear out the track behind the thumb if we're in a disable state since the thumb is
1746+
// transparent.
1747+
if (!isEnabled()) {
1748+
canvas.drawCircle(
1749+
trackSidePadding + normalizeValue(value) * width, top, thumbRadius, thumbPaint);
1750+
}
1751+
drawThumbDrawable(canvas, width, top, value, defaultThumbDrawable);
16431752
}
16441753
}
1754+
}
16451755

1646-
for (Float value : values) {
1647-
canvas.save();
1648-
canvas.translate(
1649-
trackSidePadding + (int) (normalizeValue(value) * width) - thumbRadius,
1650-
top - thumbRadius);
1651-
thumbDrawable.draw(canvas);
1652-
canvas.restore();
1653-
}
1756+
private void drawThumbDrawable(
1757+
@NonNull Canvas canvas, int width, int top, float value, @NonNull Drawable thumbDrawable) {
1758+
canvas.save();
1759+
canvas.translate(
1760+
trackSidePadding
1761+
+ (int) (normalizeValue(value) * width)
1762+
- (thumbDrawable.getBounds().width() / 2f),
1763+
top
1764+
- (thumbDrawable.getBounds().height() / 2f));
1765+
thumbDrawable.draw(canvas);
1766+
canvas.restore();
16541767
}
16551768

16561769
private void maybeDrawHalo(@NonNull Canvas canvas, int width, int top) {
@@ -2121,8 +2234,8 @@ protected void drawableStateChanged() {
21212234
label.setState(getDrawableState());
21222235
}
21232236
}
2124-
if (thumbDrawable.isStateful()) {
2125-
thumbDrawable.setState(getDrawableState());
2237+
if (defaultThumbDrawable.isStateful()) {
2238+
defaultThumbDrawable.setState(getDrawableState());
21262239
}
21272240
haloPaint.setColor(getColorForState(haloColor));
21282241
haloPaint.setAlpha(HALO_ALPHA);

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@
2020

2121
import android.content.Context;
2222
import android.content.res.TypedArray;
23+
import android.graphics.drawable.Drawable;
2324
import android.os.Parcel;
2425
import android.os.Parcelable;
2526
import android.util.AttributeSet;
2627
import android.view.AbsSavedState;
2728
import androidx.annotation.Dimension;
29+
import androidx.annotation.DrawableRes;
2830
import androidx.annotation.NonNull;
2931
import androidx.annotation.Nullable;
3032
import com.google.android.material.internal.ThemeEnforcement;
@@ -123,6 +125,38 @@ public List<Float> getValues() {
123125
return super.getValues();
124126
}
125127

128+
/**
129+
* {@inheritDoc}
130+
*/
131+
@Override
132+
public void setCustomThumbDrawable(@DrawableRes int drawableResId) {
133+
super.setCustomThumbDrawable(drawableResId);
134+
}
135+
136+
/**
137+
* {@inheritDoc}
138+
*/
139+
@Override
140+
public void setCustomThumbDrawable(@NonNull Drawable drawable) {
141+
super.setCustomThumbDrawable(drawable);
142+
}
143+
144+
/**
145+
* {@inheritDoc}
146+
*/
147+
@Override
148+
public void setCustomThumbDrawablesForValues(@NonNull @DrawableRes int... drawableResIds) {
149+
super.setCustomThumbDrawablesForValues(drawableResIds);
150+
}
151+
152+
/**
153+
* {@inheritDoc}
154+
*/
155+
@Override
156+
public void setCustomThumbDrawablesForValues(@NonNull Drawable... drawables) {
157+
super.setCustomThumbDrawablesForValues(drawables);
158+
}
159+
126160
private static List<Float> convertToFloat(TypedArray values) {
127161
List<Float> ret = new ArrayList<>();
128162
for (int i = 0; i < values.length(); ++i) {

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020

2121
import android.content.Context;
2222
import android.content.res.TypedArray;
23+
import android.graphics.drawable.Drawable;
2324
import android.util.AttributeSet;
25+
import androidx.annotation.DrawableRes;
2426
import androidx.annotation.NonNull;
2527
import androidx.annotation.Nullable;
2628
import com.google.android.material.slider.Slider.OnChangeListener;
@@ -104,6 +106,22 @@ public void setValue(float value) {
104106
setValues(value);
105107
}
106108

109+
/**
110+
* {@inheritDoc}
111+
*/
112+
@Override
113+
public void setCustomThumbDrawable(@DrawableRes int drawableResId) {
114+
super.setCustomThumbDrawable(drawableResId);
115+
}
116+
117+
/**
118+
* {@inheritDoc}
119+
*/
120+
@Override
121+
public void setCustomThumbDrawable(@NonNull Drawable drawable) {
122+
super.setCustomThumbDrawable(drawable);
123+
}
124+
107125
@Override
108126
protected boolean pickActiveThumb() {
109127
if (getActiveThumbIndex() != -1) {

0 commit comments

Comments
 (0)