Skip to content

Commit a3ca744

Browse files
drchenafohrman
authored andcommittedMay 23, 2022
[Switch] Create icon style for the new switch design
PiperOrigin-RevId: 450037491
1 parent 30bb583 commit a3ca744

File tree

3 files changed

+281
-30
lines changed

3 files changed

+281
-30
lines changed
 

‎lib/java/com/google/android/material/materialswitch/MaterialSwitch.java

+211-17
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import androidx.appcompat.widget.SwitchCompat;
3737
import androidx.appcompat.widget.TintTypedArray;
3838
import android.util.AttributeSet;
39+
import android.view.Gravity;
3940
import androidx.annotation.DrawableRes;
4041
import androidx.annotation.NonNull;
4142
import androidx.annotation.Nullable;
@@ -52,14 +53,20 @@
5253
*/
5354
public class MaterialSwitch extends SwitchCompat {
5455
private static final int DEF_STYLE_RES = R.style.Widget_Material3_CompoundButton_MaterialSwitch;
56+
private static final int[] STATE_SET_WITH_ICON = { R.attr.state_with_icon };
5557

5658
@NonNull private final SwitchWidth switchWidth = SwitchWidth.create(this);
5759
@NonNull private final ThumbPosition thumbPosition = new ThumbPosition();
5860

61+
@Nullable private Drawable thumbDrawable;
62+
@Nullable private Drawable thumbIconDrawable;
63+
5964
@Nullable private Drawable trackDrawable;
6065
@Nullable private Drawable trackDecorationDrawable;
6166

6267
@Nullable private ColorStateList thumbTintList;
68+
@Nullable private ColorStateList thumbIconTintList;
69+
@NonNull private PorterDuff.Mode thumbIconTintMode;
6370
@Nullable private ColorStateList trackTintList;
6471
@Nullable private ColorStateList trackDecorationTintList;
6572
@NonNull private PorterDuff.Mode trackDecorationTintMode;
@@ -77,6 +84,7 @@ public MaterialSwitch(@NonNull Context context, @Nullable AttributeSet attrs, in
7784
// Ensure we are using the correctly themed context rather than the context that was passed in.
7885
context = getContext();
7986

87+
thumbDrawable = super.getThumbDrawable();
8088
thumbTintList = super.getThumbTintList();
8189
super.setThumbTintList(null); // Always use our custom tinting logic
8290

@@ -88,6 +96,12 @@ public MaterialSwitch(@NonNull Context context, @Nullable AttributeSet attrs, in
8896
ThemeEnforcement.obtainTintedStyledAttributes(
8997
context, attrs, R.styleable.MaterialSwitch, defStyleAttr, DEF_STYLE_RES);
9098

99+
thumbIconDrawable = attributes.getDrawable(R.styleable.MaterialSwitch_thumbIcon);
100+
thumbIconTintList = attributes.getColorStateList(R.styleable.MaterialSwitch_thumbIconTint);
101+
thumbIconTintMode =
102+
DrawableUtils.parseTintMode(
103+
attributes.getInt(R.styleable.MaterialSwitch_thumbIconTintMode, -1), Mode.SRC_IN);
104+
91105
trackDecorationDrawable =
92106
attributes.getDrawable(R.styleable.MaterialSwitch_trackDecoration);
93107
trackDecorationTintList =
@@ -98,6 +112,7 @@ public MaterialSwitch(@NonNull Context context, @Nullable AttributeSet attrs, in
98112

99113
attributes.recycle();
100114

115+
refreshThumbDrawable();
101116
refreshTrackDrawable();
102117
}
103118

@@ -119,6 +134,17 @@ public void invalidate() {
119134
super.invalidate();
120135
}
121136

137+
@Override
138+
protected int[] onCreateDrawableState(int extraSpace) {
139+
int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
140+
141+
if (thumbIconDrawable != null) {
142+
mergeDrawableStates(drawableState, STATE_SET_WITH_ICON);
143+
}
144+
145+
return drawableState;
146+
}
147+
122148
// TODO(b/227338106): remove this workaround and move to use setEnforceSwitchWidth(false) after
123149
// AppCompat 1.6.0-stable is released.
124150
@Override
@@ -146,9 +172,21 @@ public int getCompoundPaddingRight() {
146172
}
147173

148174
@Override
149-
public void setThumbTintList(@Nullable ColorStateList tint) {
150-
thumbTintList = tint;
151-
invalidate();
175+
public void setThumbDrawable(@Nullable Drawable drawable) {
176+
thumbDrawable = drawable;
177+
refreshThumbDrawable();
178+
}
179+
180+
@Override
181+
@Nullable
182+
public Drawable getThumbDrawable() {
183+
return thumbDrawable;
184+
}
185+
186+
@Override
187+
public void setThumbTintList(@Nullable ColorStateList tintList) {
188+
thumbTintList = tintList;
189+
refreshThumbDrawable();
152190
}
153191

154192
@Override
@@ -157,6 +195,96 @@ public ColorStateList getThumbTintList() {
157195
return thumbTintList;
158196
}
159197

198+
@Override
199+
public void setThumbTintMode(@Nullable PorterDuff.Mode tintMode) {
200+
super.setThumbTintMode(tintMode);
201+
refreshThumbDrawable();
202+
}
203+
204+
/**
205+
* Sets the drawable used for the thumb icon that will be drawn upon the thumb.
206+
*
207+
* @param resId Resource ID of a thumb icon drawable
208+
*
209+
* @attr ref com.google.android.material.R.styleable#MaterialSwitch_thumbIcon
210+
*/
211+
public void setThumbIconResource(@DrawableRes int resId) {
212+
setThumbIconDrawable(AppCompatResources.getDrawable(getContext(), resId));
213+
}
214+
215+
/**
216+
* Sets the drawable used for the thumb icon that will be drawn upon the thumb.
217+
*
218+
* @param icon Thumb icon drawable
219+
*
220+
* @attr ref com.google.android.material.R.styleable#MaterialSwitch_thumbIcon
221+
*/
222+
public void setThumbIconDrawable(@Nullable Drawable icon) {
223+
thumbIconDrawable = icon;
224+
refreshThumbDrawable();
225+
}
226+
227+
/**
228+
* Gets the drawable used for the thumb icon that will be drawn upon the thumb.
229+
*
230+
* @attr ref com.google.android.material.R.styleable#MaterialSwitch_thumbIcon
231+
*/
232+
@Nullable
233+
public Drawable getThumbIconDrawable() {
234+
return thumbIconDrawable;
235+
}
236+
237+
/**
238+
* Applies a tint to the thumb icon drawable. Does not modify the current
239+
* tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
240+
* <p>
241+
* Subsequent calls to {@link #setThumbIconDrawable(Drawable)} will
242+
* automatically mutate the drawable and apply the specified tint and tint
243+
* mode using {@link DrawableCompat#setTintList(Drawable, ColorStateList)}.
244+
*
245+
* @param tintList the tint to apply, may be {@code null} to clear tint
246+
*
247+
* @attr ref com.google.android.material.R.styleable#MaterialSwitch_thumbIconTint
248+
*/
249+
public void setThumbIconTintList(@Nullable ColorStateList tintList) {
250+
thumbIconTintList = tintList;
251+
refreshThumbDrawable();
252+
}
253+
254+
/**
255+
* Returns the tint applied to the thumb icon drawable
256+
*
257+
* @attr ref com.google.android.material.R.styleable#MaterialSwitch_thumbIconTint
258+
*/
259+
@Nullable
260+
public ColorStateList getThumbIconTintList() {
261+
return thumbIconTintList;
262+
}
263+
264+
/**
265+
* Specifies the blending mode used to apply the tint specified by
266+
* {@link #setThumbIconTintList(ColorStateList)}} to the thumb icon drawable.
267+
* The default mode is {@link PorterDuff.Mode#SRC_IN}.
268+
*
269+
* @param tintMode the blending mode used to apply the tint
270+
271+
* @attr ref com.google.android.material.R.styleable#MaterialSwitch_thumbIconTintMode
272+
*/
273+
public void setThumbIconTintMode(@NonNull PorterDuff.Mode tintMode) {
274+
thumbIconTintMode = tintMode;
275+
refreshThumbDrawable();
276+
}
277+
278+
/**
279+
* Returns the blending mode used to apply the tint to the thumb icon drawable
280+
*
281+
* @attr ref com.google.android.material.R.styleable#MaterialSwitch_thumbIconTintMode
282+
*/
283+
@NonNull
284+
public PorterDuff.Mode getThumbIconTintMode() {
285+
return thumbIconTintMode;
286+
}
287+
160288
@Override
161289
public void setTrackDrawable(@Nullable Drawable track) {
162290
trackDrawable = track;
@@ -170,8 +298,8 @@ public Drawable getTrackDrawable() {
170298
}
171299

172300
@Override
173-
public void setTrackTintList(@Nullable ColorStateList tint) {
174-
trackTintList = tint;
301+
public void setTrackTintList(@Nullable ColorStateList tintList) {
302+
trackTintList = tintList;
175303
refreshTrackDrawable();
176304
}
177305

@@ -228,12 +356,12 @@ public Drawable getTrackDecorationDrawable() {
228356
* automatically mutate the drawable and apply the specified tint and tint
229357
* mode using {@link DrawableCompat#setTintList(Drawable, ColorStateList)}.
230358
*
231-
* @param tint the tint to apply, may be {@code null} to clear tint
359+
* @param tintList the tint to apply, may be {@code null} to clear tint
232360
*
233361
* @attr ref com.google.android.material.R.styleable#MaterialSwitch_trackDecorationTint
234362
*/
235-
public void setTrackDecorationTintList(@Nullable ColorStateList tint) {
236-
trackDecorationTintList = tint;
363+
public void setTrackDecorationTintList(@Nullable ColorStateList tintList) {
364+
trackDecorationTintList = tintList;
237365
refreshTrackDrawable();
238366
}
239367

@@ -277,6 +405,64 @@ private float getThumbPos() {
277405
return thumbPosition.get();
278406
}
279407

408+
private void refreshThumbDrawable() {
409+
thumbDrawable =
410+
createTintableDrawableIfNeeded(thumbDrawable, thumbTintList, getThumbTintMode());
411+
thumbIconDrawable =
412+
createTintableDrawableIfNeeded(thumbIconDrawable, thumbIconTintList, thumbIconTintMode);
413+
414+
updateDrawableTints();
415+
416+
super.setThumbDrawable(compositeThumbAndIconDrawable(thumbDrawable, thumbIconDrawable));
417+
418+
refreshDrawableState();
419+
}
420+
421+
@Nullable
422+
private static Drawable compositeThumbAndIconDrawable(
423+
@Nullable Drawable thumbDrawable, @Nullable Drawable thumbIconDrawable) {
424+
if (thumbDrawable == null) {
425+
return thumbIconDrawable;
426+
}
427+
if (thumbIconDrawable == null) {
428+
return thumbDrawable;
429+
}
430+
LayerDrawable drawable = new LayerDrawable(new Drawable[]{thumbDrawable, thumbIconDrawable});
431+
int iconNewWidth;
432+
int iconNewHeight;
433+
if (thumbIconDrawable.getIntrinsicWidth() <= thumbDrawable.getIntrinsicWidth()
434+
&& thumbIconDrawable.getIntrinsicHeight() <= thumbDrawable.getIntrinsicHeight()) {
435+
// If the icon is smaller than the thumb in both its width and height, keep icon's size.
436+
iconNewWidth = thumbIconDrawable.getIntrinsicWidth();
437+
iconNewHeight = thumbIconDrawable.getIntrinsicHeight();
438+
} else {
439+
float thumbIconRatio =
440+
(float) thumbIconDrawable.getIntrinsicWidth() / thumbIconDrawable.getIntrinsicHeight();
441+
float thumbRatio =
442+
(float) thumbDrawable.getIntrinsicWidth() / thumbDrawable.getIntrinsicHeight();
443+
if (thumbIconRatio >= thumbRatio) {
444+
// If the icon is wider in ratio than the thumb, shrink it according to its width.
445+
iconNewWidth = thumbDrawable.getIntrinsicWidth();
446+
iconNewHeight = (int) (iconNewWidth / thumbIconRatio);
447+
} else {
448+
// If the icon is taller in ratio than the thumb, shrink it according to its height.
449+
iconNewHeight = thumbDrawable.getIntrinsicHeight();
450+
iconNewWidth = (int) (thumbIconRatio * iconNewHeight);
451+
}
452+
}
453+
// Centers the icon inside the thumb. Before M there's no layer gravity support, we need to use
454+
// layer insets to adjust the icon position manually.
455+
if (VERSION.SDK_INT >= VERSION_CODES.M) {
456+
drawable.setLayerSize(1, iconNewWidth, iconNewHeight);
457+
drawable.setLayerGravity(1, Gravity.CENTER);
458+
} else {
459+
int horizontalInset = (thumbDrawable.getIntrinsicWidth() - iconNewWidth) / 2;
460+
int verticalInset = (thumbDrawable.getIntrinsicHeight() - iconNewHeight) / 2;
461+
drawable.setLayerInset(1, horizontalInset, verticalInset, horizontalInset, verticalInset);
462+
}
463+
return drawable;
464+
}
465+
280466
private void refreshTrackDrawable() {
281467
trackDrawable =
282468
createTintableDrawableIfNeeded(trackDrawable, trackTintList, getTrackTintMode());
@@ -302,7 +488,10 @@ private void refreshTrackDrawable() {
302488
}
303489

304490
private void updateDrawableTints() {
305-
if (thumbTintList == null && trackTintList == null && trackDecorationTintList == null) {
491+
if (thumbTintList == null
492+
&& thumbIconTintList == null
493+
&& trackTintList == null
494+
&& trackDecorationTintList == null) {
306495
// Early return to avoid heavy operation.
307496
return;
308497
}
@@ -313,24 +502,29 @@ private void updateDrawableTints() {
313502
int[] currentStateUnchecked = getUncheckedState(currentState);
314503
int[] currentStateChecked = getCheckedState(currentState);
315504

316-
if (trackTintList != null) {
505+
if (thumbTintList != null) {
317506
setInterpolatedDrawableTintIfPossible(
318-
trackDrawable, trackTintList, currentStateUnchecked, currentStateChecked, thumbPosition);
507+
thumbDrawable, thumbTintList, currentStateUnchecked, currentStateChecked, thumbPosition);
319508
}
320509

321-
if (trackDecorationTintList != null) {
510+
if (thumbIconTintList != null) {
322511
setInterpolatedDrawableTintIfPossible(
323-
trackDecorationDrawable,
324-
trackDecorationTintList,
512+
thumbIconDrawable,
513+
thumbIconTintList,
325514
currentStateUnchecked,
326515
currentStateChecked,
327516
thumbPosition);
328517
}
329518

330-
if (thumbTintList != null) {
519+
if (trackTintList != null) {
520+
setInterpolatedDrawableTintIfPossible(
521+
trackDrawable, trackTintList, currentStateUnchecked, currentStateChecked, thumbPosition);
522+
}
523+
524+
if (trackDecorationTintList != null) {
331525
setInterpolatedDrawableTintIfPossible(
332-
getThumbDrawable(),
333-
thumbTintList,
526+
trackDecorationDrawable,
527+
trackDecorationTintList,
334528
currentStateUnchecked,
335529
currentStateChecked,
336530
thumbPosition);

‎lib/java/com/google/android/material/materialswitch/res/drawable/mtrl_switch_thumb.xml

+17-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
limitations under the License.
1515
-->
1616

17-
<animated-selector
18-
xmlns:android="http://schemas.android.com/apk/res/android"
17+
<animated-selector xmlns:android="http://schemas.android.com/apk/res/android"
18+
xmlns:app="http://schemas.android.com/apk/res-auto"
1919
xmlns:tools="http://schemas.android.com/tools"
2020
android:width="@dimen/mtrl_switch_thumb_size"
2121
android:height="@dimen/mtrl_switch_thumb_size"
@@ -31,6 +31,11 @@
3131
android:drawable="@drawable/mtrl_switch_thumb_checked"
3232
android:state_checked="true" />
3333

34+
<item
35+
android:id="@+id/with_icon"
36+
android:drawable="@drawable/mtrl_switch_thumb_checked"
37+
app:state_with_icon="true" />
38+
3439
<item
3540
android:id="@+id/unchecked"
3641
android:drawable="@drawable/mtrl_switch_thumb_unchecked" />
@@ -40,6 +45,11 @@
4045
android:toId="@+id/checked"
4146
android:drawable="@drawable/mtrl_switch_thumb_pressed_checked" />
4247

48+
<transition
49+
android:fromId="@+id/pressed"
50+
android:toId="@+id/with_icon"
51+
android:drawable="@drawable/mtrl_switch_thumb_pressed_checked" />
52+
4353
<transition
4454
android:fromId="@+id/pressed"
4555
android:toId="@+id/unchecked"
@@ -55,6 +65,11 @@
5565
android:toId="@+id/unchecked"
5666
android:drawable="@drawable/mtrl_switch_thumb_checked_unchecked" />
5767

68+
<transition
69+
android:fromId="@+id/with_icon"
70+
android:toId="@+id/pressed"
71+
android:drawable="@drawable/mtrl_switch_thumb_checked_pressed" />
72+
5873
<transition
5974
android:fromId="@+id/unchecked"
6075
android:toId="@+id/pressed"

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

+53-11
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,60 @@
1717
<!-- Style to use for MaterialSwitch in the theme. -->
1818
<attr name="materialSwitchStyle" type="reference"/>
1919

20-
<!-- Drawable used for the track decoration that will be drawn upon the track.
21-
By default it will draw an outline on the track in the unchecked state. -->
22-
<attr name="trackDecoration" type="reference"/>
23-
<!-- Tint that will be applied to the track decoration drawable.. -->
24-
<attr name="trackDecorationTint" type="reference"/>
25-
<!-- The blending mode used to apply the tint specified by trackDecorationTint
26-
to trackDecoration. The default mode is SRC_IN if not specified. -->
27-
<attr name="trackDecorationTintMode" type="reference"/>
20+
<!-- MaterialSwitch-specific state to represent presence of a thumb icon. -->
21+
<attr name="state_with_icon" format="boolean" />
2822

2923
<declare-styleable name="MaterialSwitch">
30-
<attr name="trackDecoration"/>
31-
<attr name="trackDecorationTint"/>
32-
<attr name="trackDecorationTintMode"/>
24+
<!-- Drawable used for the thumb icon that will be drawn upon the thumb. -->
25+
<attr name="thumbIcon" format="reference"/>
26+
<!-- Tint that will be applied to the thumb icon drawable.. -->
27+
<attr name="thumbIconTint" format="color"/>
28+
<!-- The blending mode used to apply the tint specified by thumbIconTint
29+
to thumbIcon. The default mode is SRC_IN if not specified. -->
30+
<attr name="thumbIconTintMode">
31+
<!-- The tint is drawn on top of the drawable.
32+
[Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->
33+
<enum name="src_over" value="3" />
34+
<!-- The tint is masked by the alpha channel of the drawable. The drawable’s
35+
color channels are thrown out. [Sa * Da, Sc * Da] -->
36+
<enum name="src_in" value="5" />
37+
<!-- The tint is drawn above the drawable, but with the drawable’s alpha
38+
channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->
39+
<enum name="src_atop" value="9" />
40+
<!-- Multiplies the color and alpha channels of the drawable with those of
41+
the tint. [Sa * Da, Sc * Dc] -->
42+
<enum name="multiply" value="14" />
43+
<!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->
44+
<enum name="screen" value="15" />
45+
<!-- Combines the tint and drawable color and alpha channels, clamping the
46+
result to valid color values. Saturate(S + D) -->
47+
<enum name="add" value="16" />
48+
</attr>
49+
<!-- Drawable used for the track decoration that will be drawn upon the track.
50+
By default it will draw an outline on the track in the unchecked state. -->
51+
<attr name="trackDecoration" format="reference"/>
52+
<!-- Tint that will be applied to the track decoration drawable.. -->
53+
<attr name="trackDecorationTint" format="color"/>
54+
<!-- The blending mode used to apply the tint specified by trackDecorationTint
55+
to trackDecoration. The default mode is SRC_IN if not specified. -->
56+
<attr name="trackDecorationTintMode">
57+
<!-- The tint is drawn on top of the drawable.
58+
[Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->
59+
<enum name="src_over" value="3" />
60+
<!-- The tint is masked by the alpha channel of the drawable. The drawable’s
61+
color channels are thrown out. [Sa * Da, Sc * Da] -->
62+
<enum name="src_in" value="5" />
63+
<!-- The tint is drawn above the drawable, but with the drawable’s alpha
64+
channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->
65+
<enum name="src_atop" value="9" />
66+
<!-- Multiplies the color and alpha channels of the drawable with those of
67+
the tint. [Sa * Da, Sc * Dc] -->
68+
<enum name="multiply" value="14" />
69+
<!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->
70+
<enum name="screen" value="15" />
71+
<!-- Combines the tint and drawable color and alpha channels, clamping the
72+
result to valid color values. Saturate(S + D) -->
73+
<enum name="add" value="16" />
74+
</attr>
3375
</declare-styleable>
3476
</resources>

0 commit comments

Comments
 (0)
Please sign in to comment.