diff --git a/lib/java/com/google/android/material/materialswitch/MaterialSwitch.java b/lib/java/com/google/android/material/materialswitch/MaterialSwitch.java index 9ed6113c076..c76666770fc 100644 --- a/lib/java/com/google/android/material/materialswitch/MaterialSwitch.java +++ b/lib/java/com/google/android/material/materialswitch/MaterialSwitch.java @@ -36,6 +36,7 @@ import androidx.appcompat.widget.SwitchCompat; import androidx.appcompat.widget.TintTypedArray; import android.util.AttributeSet; +import android.view.Gravity; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -52,14 +53,20 @@ */ public class MaterialSwitch extends SwitchCompat { private static final int DEF_STYLE_RES = R.style.Widget_Material3_CompoundButton_MaterialSwitch; + private static final int[] STATE_SET_WITH_ICON = { R.attr.state_with_icon }; @NonNull private final SwitchWidth switchWidth = SwitchWidth.create(this); @NonNull private final ThumbPosition thumbPosition = new ThumbPosition(); + @Nullable private Drawable thumbDrawable; + @Nullable private Drawable thumbIconDrawable; + @Nullable private Drawable trackDrawable; @Nullable private Drawable trackDecorationDrawable; @Nullable private ColorStateList thumbTintList; + @Nullable private ColorStateList thumbIconTintList; + @NonNull private PorterDuff.Mode thumbIconTintMode; @Nullable private ColorStateList trackTintList; @Nullable private ColorStateList trackDecorationTintList; @NonNull private PorterDuff.Mode trackDecorationTintMode; @@ -77,6 +84,7 @@ public MaterialSwitch(@NonNull Context context, @Nullable AttributeSet attrs, in // Ensure we are using the correctly themed context rather than the context that was passed in. context = getContext(); + thumbDrawable = super.getThumbDrawable(); thumbTintList = super.getThumbTintList(); super.setThumbTintList(null); // Always use our custom tinting logic @@ -88,6 +96,12 @@ public MaterialSwitch(@NonNull Context context, @Nullable AttributeSet attrs, in ThemeEnforcement.obtainTintedStyledAttributes( context, attrs, R.styleable.MaterialSwitch, defStyleAttr, DEF_STYLE_RES); + thumbIconDrawable = attributes.getDrawable(R.styleable.MaterialSwitch_thumbIcon); + thumbIconTintList = attributes.getColorStateList(R.styleable.MaterialSwitch_thumbIconTint); + thumbIconTintMode = + DrawableUtils.parseTintMode( + attributes.getInt(R.styleable.MaterialSwitch_thumbIconTintMode, -1), Mode.SRC_IN); + trackDecorationDrawable = attributes.getDrawable(R.styleable.MaterialSwitch_trackDecoration); trackDecorationTintList = @@ -98,6 +112,7 @@ public MaterialSwitch(@NonNull Context context, @Nullable AttributeSet attrs, in attributes.recycle(); + refreshThumbDrawable(); refreshTrackDrawable(); } @@ -119,6 +134,17 @@ public void invalidate() { super.invalidate(); } + @Override + protected int[] onCreateDrawableState(int extraSpace) { + int[] drawableState = super.onCreateDrawableState(extraSpace + 1); + + if (thumbIconDrawable != null) { + mergeDrawableStates(drawableState, STATE_SET_WITH_ICON); + } + + return drawableState; + } + // TODO(b/227338106): remove this workaround and move to use setEnforceSwitchWidth(false) after // AppCompat 1.6.0-stable is released. @Override @@ -146,9 +172,21 @@ public int getCompoundPaddingRight() { } @Override - public void setThumbTintList(@Nullable ColorStateList tint) { - thumbTintList = tint; - invalidate(); + public void setThumbDrawable(@Nullable Drawable drawable) { + thumbDrawable = drawable; + refreshThumbDrawable(); + } + + @Override + @Nullable + public Drawable getThumbDrawable() { + return thumbDrawable; + } + + @Override + public void setThumbTintList(@Nullable ColorStateList tintList) { + thumbTintList = tintList; + refreshThumbDrawable(); } @Override @@ -157,6 +195,96 @@ public ColorStateList getThumbTintList() { return thumbTintList; } + @Override + public void setThumbTintMode(@Nullable PorterDuff.Mode tintMode) { + super.setThumbTintMode(tintMode); + refreshThumbDrawable(); + } + + /** + * Sets the drawable used for the thumb icon that will be drawn upon the thumb. + * + * @param resId Resource ID of a thumb icon drawable + * + * @attr ref com.google.android.material.R.styleable#MaterialSwitch_thumbIcon + */ + public void setThumbIconResource(@DrawableRes int resId) { + setThumbIconDrawable(AppCompatResources.getDrawable(getContext(), resId)); + } + + /** + * Sets the drawable used for the thumb icon that will be drawn upon the thumb. + * + * @param icon Thumb icon drawable + * + * @attr ref com.google.android.material.R.styleable#MaterialSwitch_thumbIcon + */ + public void setThumbIconDrawable(@Nullable Drawable icon) { + thumbIconDrawable = icon; + refreshThumbDrawable(); + } + + /** + * Gets the drawable used for the thumb icon that will be drawn upon the thumb. + * + * @attr ref com.google.android.material.R.styleable#MaterialSwitch_thumbIcon + */ + @Nullable + public Drawable getThumbIconDrawable() { + return thumbIconDrawable; + } + + /** + * Applies a tint to the thumb icon drawable. Does not modify the current + * tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default. + *
+ * Subsequent calls to {@link #setThumbIconDrawable(Drawable)} will
+ * automatically mutate the drawable and apply the specified tint and tint
+ * mode using {@link DrawableCompat#setTintList(Drawable, ColorStateList)}.
+ *
+ * @param tintList the tint to apply, may be {@code null} to clear tint
+ *
+ * @attr ref com.google.android.material.R.styleable#MaterialSwitch_thumbIconTint
+ */
+ public void setThumbIconTintList(@Nullable ColorStateList tintList) {
+ thumbIconTintList = tintList;
+ refreshThumbDrawable();
+ }
+
+ /**
+ * Returns the tint applied to the thumb icon drawable
+ *
+ * @attr ref com.google.android.material.R.styleable#MaterialSwitch_thumbIconTint
+ */
+ @Nullable
+ public ColorStateList getThumbIconTintList() {
+ return thumbIconTintList;
+ }
+
+ /**
+ * Specifies the blending mode used to apply the tint specified by
+ * {@link #setThumbIconTintList(ColorStateList)}} to the thumb icon drawable.
+ * The default mode is {@link PorterDuff.Mode#SRC_IN}.
+ *
+ * @param tintMode the blending mode used to apply the tint
+
+ * @attr ref com.google.android.material.R.styleable#MaterialSwitch_thumbIconTintMode
+ */
+ public void setThumbIconTintMode(@NonNull PorterDuff.Mode tintMode) {
+ thumbIconTintMode = tintMode;
+ refreshThumbDrawable();
+ }
+
+ /**
+ * Returns the blending mode used to apply the tint to the thumb icon drawable
+ *
+ * @attr ref com.google.android.material.R.styleable#MaterialSwitch_thumbIconTintMode
+ */
+ @NonNull
+ public PorterDuff.Mode getThumbIconTintMode() {
+ return thumbIconTintMode;
+ }
+
@Override
public void setTrackDrawable(@Nullable Drawable track) {
trackDrawable = track;
@@ -170,8 +298,8 @@ public Drawable getTrackDrawable() {
}
@Override
- public void setTrackTintList(@Nullable ColorStateList tint) {
- trackTintList = tint;
+ public void setTrackTintList(@Nullable ColorStateList tintList) {
+ trackTintList = tintList;
refreshTrackDrawable();
}
@@ -228,12 +356,12 @@ public Drawable getTrackDecorationDrawable() {
* automatically mutate the drawable and apply the specified tint and tint
* mode using {@link DrawableCompat#setTintList(Drawable, ColorStateList)}.
*
- * @param tint the tint to apply, may be {@code null} to clear tint
+ * @param tintList the tint to apply, may be {@code null} to clear tint
*
* @attr ref com.google.android.material.R.styleable#MaterialSwitch_trackDecorationTint
*/
- public void setTrackDecorationTintList(@Nullable ColorStateList tint) {
- trackDecorationTintList = tint;
+ public void setTrackDecorationTintList(@Nullable ColorStateList tintList) {
+ trackDecorationTintList = tintList;
refreshTrackDrawable();
}
@@ -277,6 +405,64 @@ private float getThumbPos() {
return thumbPosition.get();
}
+ private void refreshThumbDrawable() {
+ thumbDrawable =
+ createTintableDrawableIfNeeded(thumbDrawable, thumbTintList, getThumbTintMode());
+ thumbIconDrawable =
+ createTintableDrawableIfNeeded(thumbIconDrawable, thumbIconTintList, thumbIconTintMode);
+
+ updateDrawableTints();
+
+ super.setThumbDrawable(compositeThumbAndIconDrawable(thumbDrawable, thumbIconDrawable));
+
+ refreshDrawableState();
+ }
+
+ @Nullable
+ private static Drawable compositeThumbAndIconDrawable(
+ @Nullable Drawable thumbDrawable, @Nullable Drawable thumbIconDrawable) {
+ if (thumbDrawable == null) {
+ return thumbIconDrawable;
+ }
+ if (thumbIconDrawable == null) {
+ return thumbDrawable;
+ }
+ LayerDrawable drawable = new LayerDrawable(new Drawable[]{thumbDrawable, thumbIconDrawable});
+ int iconNewWidth;
+ int iconNewHeight;
+ if (thumbIconDrawable.getIntrinsicWidth() <= thumbDrawable.getIntrinsicWidth()
+ && thumbIconDrawable.getIntrinsicHeight() <= thumbDrawable.getIntrinsicHeight()) {
+ // If the icon is smaller than the thumb in both its width and height, keep icon's size.
+ iconNewWidth = thumbIconDrawable.getIntrinsicWidth();
+ iconNewHeight = thumbIconDrawable.getIntrinsicHeight();
+ } else {
+ float thumbIconRatio =
+ (float) thumbIconDrawable.getIntrinsicWidth() / thumbIconDrawable.getIntrinsicHeight();
+ float thumbRatio =
+ (float) thumbDrawable.getIntrinsicWidth() / thumbDrawable.getIntrinsicHeight();
+ if (thumbIconRatio >= thumbRatio) {
+ // If the icon is wider in ratio than the thumb, shrink it according to its width.
+ iconNewWidth = thumbDrawable.getIntrinsicWidth();
+ iconNewHeight = (int) (iconNewWidth / thumbIconRatio);
+ } else {
+ // If the icon is taller in ratio than the thumb, shrink it according to its height.
+ iconNewHeight = thumbDrawable.getIntrinsicHeight();
+ iconNewWidth = (int) (thumbIconRatio * iconNewHeight);
+ }
+ }
+ // Centers the icon inside the thumb. Before M there's no layer gravity support, we need to use
+ // layer insets to adjust the icon position manually.
+ if (VERSION.SDK_INT >= VERSION_CODES.M) {
+ drawable.setLayerSize(1, iconNewWidth, iconNewHeight);
+ drawable.setLayerGravity(1, Gravity.CENTER);
+ } else {
+ int horizontalInset = (thumbDrawable.getIntrinsicWidth() - iconNewWidth) / 2;
+ int verticalInset = (thumbDrawable.getIntrinsicHeight() - iconNewHeight) / 2;
+ drawable.setLayerInset(1, horizontalInset, verticalInset, horizontalInset, verticalInset);
+ }
+ return drawable;
+ }
+
private void refreshTrackDrawable() {
trackDrawable =
createTintableDrawableIfNeeded(trackDrawable, trackTintList, getTrackTintMode());
@@ -302,7 +488,10 @@ private void refreshTrackDrawable() {
}
private void updateDrawableTints() {
- if (thumbTintList == null && trackTintList == null && trackDecorationTintList == null) {
+ if (thumbTintList == null
+ && thumbIconTintList == null
+ && trackTintList == null
+ && trackDecorationTintList == null) {
// Early return to avoid heavy operation.
return;
}
@@ -313,24 +502,29 @@ private void updateDrawableTints() {
int[] currentStateUnchecked = getUncheckedState(currentState);
int[] currentStateChecked = getCheckedState(currentState);
- if (trackTintList != null) {
+ if (thumbTintList != null) {
setInterpolatedDrawableTintIfPossible(
- trackDrawable, trackTintList, currentStateUnchecked, currentStateChecked, thumbPosition);
+ thumbDrawable, thumbTintList, currentStateUnchecked, currentStateChecked, thumbPosition);
}
- if (trackDecorationTintList != null) {
+ if (thumbIconTintList != null) {
setInterpolatedDrawableTintIfPossible(
- trackDecorationDrawable,
- trackDecorationTintList,
+ thumbIconDrawable,
+ thumbIconTintList,
currentStateUnchecked,
currentStateChecked,
thumbPosition);
}
- if (thumbTintList != null) {
+ if (trackTintList != null) {
+ setInterpolatedDrawableTintIfPossible(
+ trackDrawable, trackTintList, currentStateUnchecked, currentStateChecked, thumbPosition);
+ }
+
+ if (trackDecorationTintList != null) {
setInterpolatedDrawableTintIfPossible(
- getThumbDrawable(),
- thumbTintList,
+ trackDecorationDrawable,
+ trackDecorationTintList,
currentStateUnchecked,
currentStateChecked,
thumbPosition);
diff --git a/lib/java/com/google/android/material/materialswitch/res/drawable/mtrl_switch_thumb.xml b/lib/java/com/google/android/material/materialswitch/res/drawable/mtrl_switch_thumb.xml
index 56a09b187b5..1a41f6c033a 100644
--- a/lib/java/com/google/android/material/materialswitch/res/drawable/mtrl_switch_thumb.xml
+++ b/lib/java/com/google/android/material/materialswitch/res/drawable/mtrl_switch_thumb.xml
@@ -14,8 +14,8 @@
limitations under the License.
-->
-