Skip to content

Commit

Permalink
[Slider] Add support for custom thumb drawables
Browse files Browse the repository at this point in the history
Resolves #1522

PiperOrigin-RevId: 429607888
  • Loading branch information
drchen authored and hunterstich committed Feb 23, 2022
1 parent 0803a22 commit 17da000
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 30 deletions.
173 changes: 143 additions & 30 deletions lib/java/com/google/android/material/slider/BaseSlider.java
Expand Up @@ -67,6 +67,7 @@
import androidx.annotation.ColorRes;
import androidx.annotation.DimenRes;
import androidx.annotation.Dimension;
import androidx.annotation.DrawableRes;
import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
Expand Down Expand Up @@ -293,7 +294,9 @@ private interface TooltipDrawableFactory {
@NonNull private ColorStateList trackColorActive;
@NonNull private ColorStateList trackColorInactive;

@NonNull private final MaterialShapeDrawable thumbDrawable = new MaterialShapeDrawable();
@NonNull private final MaterialShapeDrawable defaultThumbDrawable = new MaterialShapeDrawable();
@Nullable private Drawable customThumbDrawable;
@NonNull private List<Drawable> customThumbDrawablesForValues = Collections.emptyList();

private float touchPosition;
@SeparationUnit private int separationUnit = UNIT_PX;
Expand Down Expand Up @@ -379,7 +382,8 @@ public TooltipDrawable createTooltipDrawable() {
setClickable(true);

// Set up the thumb drawable to always show the compat shadow.
thumbDrawable.setShadowCompatibilityMode(MaterialShapeDrawable.SHADOW_COMPAT_MODE_ALWAYS);
defaultThumbDrawable.setShadowCompatibilityMode(
MaterialShapeDrawable.SHADOW_COMPAT_MODE_ALWAYS);

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

Expand Down Expand Up @@ -435,7 +439,7 @@ private void processAttributes(Context context, AttributeSet attrs, int defStyle
context, R.color.material_slider_active_track_color));
ColorStateList thumbColor =
MaterialResources.getColorStateList(context, a, R.styleable.Slider_thumbColor);
thumbDrawable.setFillColor(thumbColor);
defaultThumbDrawable.setFillColor(thumbColor);

if (a.hasValue(R.styleable.Slider_thumbStrokeColor)) {
setThumbStrokeColor(
Expand Down Expand Up @@ -801,6 +805,96 @@ public void setStepSize(float stepSize) {
}
}

/**
* Sets the custom thumb drawable which will be used for all value positions. Note that the custom
* drawable provided will be resized to match the thumb radius set by {@link #setThumbRadius(int)}
* or {@link #setThumbRadiusResource(int)}. Be aware that the image quality may be compromised
* during resizing.
*
* @see #setCustomThumbDrawable(Drawable)
* @see #setCustomThumbDrawablesForValues(int...)
* @see #setCustomThumbDrawablesForValues(Drawable...)
*/
void setCustomThumbDrawable(@DrawableRes int drawableResId) {
setCustomThumbDrawable(getResources().getDrawable(drawableResId));
}

/**
* Sets the custom thumb drawable which will be used for all value positions. Note that the custom
* drawable provided will be resized to match the thumb radius set by {@link #setThumbRadius(int)}
* or {@link #setThumbRadiusResource(int)}. Be aware that the image quality may be compromised
* during resizing.
*
* @see #setCustomThumbDrawable(int)
* @see #setCustomThumbDrawablesForValues(int...)
* @see #setCustomThumbDrawablesForValues(Drawable...)
*/
void setCustomThumbDrawable(@NonNull Drawable drawable) {
customThumbDrawable = initializeCustomThumbDrawable(drawable);
customThumbDrawablesForValues.clear();
postInvalidate();
}

/**
* Sets custom thumb drawables. The drawables provided will be used in its corresponding value
* position - i.e., the first drawable will be used to indicate the first value, and so on. If
* the number of drawables is less than the number of values, the default drawable will be used
* for the remaining values.
*
* <p>Note that the custom drawables provided will be resized to match the thumb radius set by
* {@link #setThumbRadius(int)} or {@link #setThumbRadiusResource(int)}. Be aware that the image
* quality may be compromised during resizing.
*
* @see #setCustomThumbDrawablesForValues(Drawable...)
*/
void setCustomThumbDrawablesForValues(@NonNull @DrawableRes int... customThumbDrawableResIds) {
Drawable[] customThumbDrawables = new Drawable[customThumbDrawableResIds.length];
for (int i = 0; i < customThumbDrawableResIds.length; i++) {
customThumbDrawables[i] = getResources().getDrawable(customThumbDrawableResIds[i]);
}
setCustomThumbDrawablesForValues(customThumbDrawables);
}

/**
* Sets custom thumb drawables. The drawables provided will be used in its corresponding value
* position - i.e., the first drawable will be used to indicate the first value, and so on. If
* the number of drawables is less than the number of values, the default drawable will be used
* for the remaining values.
*
* <p>Note that the custom drawables provided will be resized to match the thumb radius set by
* {@link #setThumbRadius(int)} or {@link #setThumbRadiusResource(int)}. Be aware that the image
* quality may be compromised during resizing.
*
* @see #setCustomThumbDrawablesForValues(int...)
*/
void setCustomThumbDrawablesForValues(@NonNull Drawable... customThumbDrawables) {
this.customThumbDrawable = null;
this.customThumbDrawablesForValues = new ArrayList<>();
for (Drawable originalDrawable : customThumbDrawables) {
this.customThumbDrawablesForValues.add(initializeCustomThumbDrawable(originalDrawable));
}
postInvalidate();
}

private Drawable initializeCustomThumbDrawable(Drawable originalDrawable) {
Drawable drawable = originalDrawable.mutate().getConstantState().newDrawable();
adjustCustomThumbDrawableBounds(drawable);
return drawable;
}

private void adjustCustomThumbDrawableBounds(Drawable drawable) {
int thumbDiameter = thumbRadius * 2;
int originalWidth = drawable.getIntrinsicWidth();
int originalHeight = drawable.getIntrinsicHeight();
if (originalWidth == -1 && originalHeight == -1) {
drawable.setBounds(0, 0, thumbDiameter, thumbDiameter);
} else {
float scaleRatio = (float) thumbDiameter / Math.max(originalWidth, originalHeight);
drawable.setBounds(
0, 0, (int) (originalWidth * scaleRatio), (int) (originalHeight * scaleRatio));
}
}

/** Returns the index of the currently focused thumb */
public int getFocusedThumbIndex() {
return focusedThumbIdx;
Expand Down Expand Up @@ -898,7 +992,7 @@ public void setLabelFormatter(@Nullable LabelFormatter formatter) {
* @attr ref com.google.android.material.R.styleable#Slider_thumbElevation
*/
public float getThumbElevation() {
return thumbDrawable.getElevation();
return defaultThumbDrawable.getElevation();
}

/**
Expand All @@ -908,7 +1002,7 @@ public float getThumbElevation() {
* @attr ref com.google.android.material.R.styleable#Slider_thumbElevation
*/
public void setThumbElevation(float elevation) {
thumbDrawable.setElevation(elevation);
defaultThumbDrawable.setElevation(elevation);
}

/**
Expand Down Expand Up @@ -947,9 +1041,16 @@ public void setThumbRadius(@IntRange(from = 0) @Dimension int radius) {
thumbRadius = radius;
maybeIncreaseTrackSidePadding();

thumbDrawable.setShapeAppearanceModel(
defaultThumbDrawable.setShapeAppearanceModel(
ShapeAppearanceModel.builder().setAllCorners(CornerFamily.ROUNDED, thumbRadius).build());
thumbDrawable.setBounds(0, 0, thumbRadius * 2, thumbRadius * 2);
defaultThumbDrawable.setBounds(0, 0, thumbRadius * 2, thumbRadius * 2);

if (customThumbDrawable != null) {
adjustCustomThumbDrawableBounds(customThumbDrawable);
}
for (Drawable customDrawable : customThumbDrawablesForValues) {
adjustCustomThumbDrawableBounds(customDrawable);
}

postInvalidate();
}
Expand All @@ -974,7 +1075,7 @@ public void setThumbRadiusResource(@DimenRes int radius) {
* @see #getThumbStrokeColor()
*/
public void setThumbStrokeColor(@Nullable ColorStateList thumbStrokeColor) {
thumbDrawable.setStrokeColor(thumbStrokeColor);
defaultThumbDrawable.setStrokeColor(thumbStrokeColor);
postInvalidate();
}

Expand Down Expand Up @@ -1003,7 +1104,7 @@ public void setThumbStrokeColorResource(@ColorRes int thumbStrokeColorResourceId
* @see #setThumbStrokeColorResource(int)
*/
public ColorStateList getThumbStrokeColor() {
return thumbDrawable.getStrokeColor();
return defaultThumbDrawable.getStrokeColor();
}

/**
Expand All @@ -1016,7 +1117,7 @@ public ColorStateList getThumbStrokeColor() {
* @see #getThumbStrokeWidth()
*/
public void setThumbStrokeWidth(float thumbStrokeWidth) {
thumbDrawable.setStrokeWidth(thumbStrokeWidth);
defaultThumbDrawable.setStrokeWidth(thumbStrokeWidth);
postInvalidate();
}

Expand Down Expand Up @@ -1044,7 +1145,7 @@ public void setThumbStrokeWidthResource(@DimenRes int thumbStrokeWidthResourceId
* @see #setThumbStrokeWidthResource(int)
*/
public float getThumbStrokeWidth() {
return thumbDrawable.getStrokeWidth();
return defaultThumbDrawable.getStrokeWidth();
}

/**
Expand Down Expand Up @@ -1194,7 +1295,7 @@ public void setHaloTintList(@NonNull ColorStateList haloColor) {
*/
@NonNull
public ColorStateList getThumbTintList() {
return thumbDrawable.getFillColor();
return defaultThumbDrawable.getFillColor();
}

/**
Expand All @@ -1204,11 +1305,11 @@ public ColorStateList getThumbTintList() {
* @attr ref com.google.android.material.R.styleable#Slider_thumbColor
*/
public void setThumbTintList(@NonNull ColorStateList thumbColor) {
if (thumbColor.equals(thumbDrawable.getFillColor())) {
if (thumbColor.equals(defaultThumbDrawable.getFillColor())) {
return;
}

thumbDrawable.setFillColor(thumbColor);
defaultThumbDrawable.setFillColor(thumbColor);
invalidate();
}

Expand Down Expand Up @@ -1634,23 +1735,35 @@ private void maybeDrawTicks(@NonNull Canvas canvas) {
}

private void drawThumbs(@NonNull Canvas canvas, int width, int top) {
// Clear out the track behind the thumb if we're in a disable state since the thumb is
// transparent.
if (!isEnabled()) {
for (Float value : values) {
canvas.drawCircle(
trackSidePadding + normalizeValue(value) * width, top, thumbRadius, thumbPaint);
for (int i = 0; i < values.size(); i++) {
float value = values.get(i);
if (customThumbDrawable != null) {
drawThumbDrawable(canvas, width, top, value, customThumbDrawable);
} else if (i < customThumbDrawablesForValues.size()) {
drawThumbDrawable(canvas, width, top, value, customThumbDrawablesForValues.get(i));
} else {
// Clear out the track behind the thumb if we're in a disable state since the thumb is
// transparent.
if (!isEnabled()) {
canvas.drawCircle(
trackSidePadding + normalizeValue(value) * width, top, thumbRadius, thumbPaint);
}
drawThumbDrawable(canvas, width, top, value, defaultThumbDrawable);
}
}
}

for (Float value : values) {
canvas.save();
canvas.translate(
trackSidePadding + (int) (normalizeValue(value) * width) - thumbRadius,
top - thumbRadius);
thumbDrawable.draw(canvas);
canvas.restore();
}
private void drawThumbDrawable(
@NonNull Canvas canvas, int width, int top, float value, @NonNull Drawable thumbDrawable) {
canvas.save();
canvas.translate(
trackSidePadding
+ (int) (normalizeValue(value) * width)
- (thumbDrawable.getBounds().width() / 2f),
top
- (thumbDrawable.getBounds().height() / 2f));
thumbDrawable.draw(canvas);
canvas.restore();
}

private void maybeDrawHalo(@NonNull Canvas canvas, int width, int top) {
Expand Down Expand Up @@ -2121,8 +2234,8 @@ protected void drawableStateChanged() {
label.setState(getDrawableState());
}
}
if (thumbDrawable.isStateful()) {
thumbDrawable.setState(getDrawableState());
if (defaultThumbDrawable.isStateful()) {
defaultThumbDrawable.setState(getDrawableState());
}
haloPaint.setColor(getColorForState(haloColor));
haloPaint.setAlpha(HALO_ALPHA);
Expand Down
34 changes: 34 additions & 0 deletions lib/java/com/google/android/material/slider/RangeSlider.java
Expand Up @@ -20,11 +20,13 @@

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.AbsSavedState;
import androidx.annotation.Dimension;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.internal.ThemeEnforcement;
Expand Down Expand Up @@ -123,6 +125,38 @@ public List<Float> getValues() {
return super.getValues();
}

/**
* {@inheritDoc}
*/
@Override
public void setCustomThumbDrawable(@DrawableRes int drawableResId) {
super.setCustomThumbDrawable(drawableResId);
}

/**
* {@inheritDoc}
*/
@Override
public void setCustomThumbDrawable(@NonNull Drawable drawable) {
super.setCustomThumbDrawable(drawable);
}

/**
* {@inheritDoc}
*/
@Override
public void setCustomThumbDrawablesForValues(@NonNull @DrawableRes int... drawableResIds) {
super.setCustomThumbDrawablesForValues(drawableResIds);
}

/**
* {@inheritDoc}
*/
@Override
public void setCustomThumbDrawablesForValues(@NonNull Drawable... drawables) {
super.setCustomThumbDrawablesForValues(drawables);
}

private static List<Float> convertToFloat(TypedArray values) {
List<Float> ret = new ArrayList<>();
for (int i = 0; i < values.length(); ++i) {
Expand Down
18 changes: 18 additions & 0 deletions lib/java/com/google/android/material/slider/Slider.java
Expand Up @@ -20,7 +20,9 @@

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.slider.Slider.OnChangeListener;
Expand Down Expand Up @@ -104,6 +106,22 @@ public void setValue(float value) {
setValues(value);
}

/**
* {@inheritDoc}
*/
@Override
public void setCustomThumbDrawable(@DrawableRes int drawableResId) {
super.setCustomThumbDrawable(drawableResId);
}

/**
* {@inheritDoc}
*/
@Override
public void setCustomThumbDrawable(@NonNull Drawable drawable) {
super.setCustomThumbDrawable(drawable);
}

@Override
protected boolean pickActiveThumb() {
if (getActiveThumbIndex() != -1) {
Expand Down

1 comment on commit 17da000

@NixLeadsdoit
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this feature)

Please sign in to comment.