36
36
import androidx .appcompat .widget .SwitchCompat ;
37
37
import androidx .appcompat .widget .TintTypedArray ;
38
38
import android .util .AttributeSet ;
39
+ import android .view .Gravity ;
39
40
import androidx .annotation .DrawableRes ;
40
41
import androidx .annotation .NonNull ;
41
42
import androidx .annotation .Nullable ;
52
53
*/
53
54
public class MaterialSwitch extends SwitchCompat {
54
55
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 };
55
57
56
58
@ NonNull private final SwitchWidth switchWidth = SwitchWidth .create (this );
57
59
@ NonNull private final ThumbPosition thumbPosition = new ThumbPosition ();
58
60
61
+ @ Nullable private Drawable thumbDrawable ;
62
+ @ Nullable private Drawable thumbIconDrawable ;
63
+
59
64
@ Nullable private Drawable trackDrawable ;
60
65
@ Nullable private Drawable trackDecorationDrawable ;
61
66
62
67
@ Nullable private ColorStateList thumbTintList ;
68
+ @ Nullable private ColorStateList thumbIconTintList ;
69
+ @ NonNull private PorterDuff .Mode thumbIconTintMode ;
63
70
@ Nullable private ColorStateList trackTintList ;
64
71
@ Nullable private ColorStateList trackDecorationTintList ;
65
72
@ NonNull private PorterDuff .Mode trackDecorationTintMode ;
@@ -77,6 +84,7 @@ public MaterialSwitch(@NonNull Context context, @Nullable AttributeSet attrs, in
77
84
// Ensure we are using the correctly themed context rather than the context that was passed in.
78
85
context = getContext ();
79
86
87
+ thumbDrawable = super .getThumbDrawable ();
80
88
thumbTintList = super .getThumbTintList ();
81
89
super .setThumbTintList (null ); // Always use our custom tinting logic
82
90
@@ -88,6 +96,12 @@ public MaterialSwitch(@NonNull Context context, @Nullable AttributeSet attrs, in
88
96
ThemeEnforcement .obtainTintedStyledAttributes (
89
97
context , attrs , R .styleable .MaterialSwitch , defStyleAttr , DEF_STYLE_RES );
90
98
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
+
91
105
trackDecorationDrawable =
92
106
attributes .getDrawable (R .styleable .MaterialSwitch_trackDecoration );
93
107
trackDecorationTintList =
@@ -98,6 +112,7 @@ public MaterialSwitch(@NonNull Context context, @Nullable AttributeSet attrs, in
98
112
99
113
attributes .recycle ();
100
114
115
+ refreshThumbDrawable ();
101
116
refreshTrackDrawable ();
102
117
}
103
118
@@ -119,6 +134,17 @@ public void invalidate() {
119
134
super .invalidate ();
120
135
}
121
136
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
+
122
148
// TODO(b/227338106): remove this workaround and move to use setEnforceSwitchWidth(false) after
123
149
// AppCompat 1.6.0-stable is released.
124
150
@ Override
@@ -146,9 +172,21 @@ public int getCompoundPaddingRight() {
146
172
}
147
173
148
174
@ 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 ();
152
190
}
153
191
154
192
@ Override
@@ -157,6 +195,96 @@ public ColorStateList getThumbTintList() {
157
195
return thumbTintList ;
158
196
}
159
197
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
+
160
288
@ Override
161
289
public void setTrackDrawable (@ Nullable Drawable track ) {
162
290
trackDrawable = track ;
@@ -170,8 +298,8 @@ public Drawable getTrackDrawable() {
170
298
}
171
299
172
300
@ Override
173
- public void setTrackTintList (@ Nullable ColorStateList tint ) {
174
- trackTintList = tint ;
301
+ public void setTrackTintList (@ Nullable ColorStateList tintList ) {
302
+ trackTintList = tintList ;
175
303
refreshTrackDrawable ();
176
304
}
177
305
@@ -228,12 +356,12 @@ public Drawable getTrackDecorationDrawable() {
228
356
* automatically mutate the drawable and apply the specified tint and tint
229
357
* mode using {@link DrawableCompat#setTintList(Drawable, ColorStateList)}.
230
358
*
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
232
360
*
233
361
* @attr ref com.google.android.material.R.styleable#MaterialSwitch_trackDecorationTint
234
362
*/
235
- public void setTrackDecorationTintList (@ Nullable ColorStateList tint ) {
236
- trackDecorationTintList = tint ;
363
+ public void setTrackDecorationTintList (@ Nullable ColorStateList tintList ) {
364
+ trackDecorationTintList = tintList ;
237
365
refreshTrackDrawable ();
238
366
}
239
367
@@ -277,6 +405,64 @@ private float getThumbPos() {
277
405
return thumbPosition .get ();
278
406
}
279
407
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
+
280
466
private void refreshTrackDrawable () {
281
467
trackDrawable =
282
468
createTintableDrawableIfNeeded (trackDrawable , trackTintList , getTrackTintMode ());
@@ -302,7 +488,10 @@ private void refreshTrackDrawable() {
302
488
}
303
489
304
490
private void updateDrawableTints () {
305
- if (thumbTintList == null && trackTintList == null && trackDecorationTintList == null ) {
491
+ if (thumbTintList == null
492
+ && thumbIconTintList == null
493
+ && trackTintList == null
494
+ && trackDecorationTintList == null ) {
306
495
// Early return to avoid heavy operation.
307
496
return ;
308
497
}
@@ -313,24 +502,29 @@ private void updateDrawableTints() {
313
502
int [] currentStateUnchecked = getUncheckedState (currentState );
314
503
int [] currentStateChecked = getCheckedState (currentState );
315
504
316
- if (trackTintList != null ) {
505
+ if (thumbTintList != null ) {
317
506
setInterpolatedDrawableTintIfPossible (
318
- trackDrawable , trackTintList , currentStateUnchecked , currentStateChecked , thumbPosition );
507
+ thumbDrawable , thumbTintList , currentStateUnchecked , currentStateChecked , thumbPosition );
319
508
}
320
509
321
- if (trackDecorationTintList != null ) {
510
+ if (thumbIconTintList != null ) {
322
511
setInterpolatedDrawableTintIfPossible (
323
- trackDecorationDrawable ,
324
- trackDecorationTintList ,
512
+ thumbIconDrawable ,
513
+ thumbIconTintList ,
325
514
currentStateUnchecked ,
326
515
currentStateChecked ,
327
516
thumbPosition );
328
517
}
329
518
330
- if (thumbTintList != null ) {
519
+ if (trackTintList != null ) {
520
+ setInterpolatedDrawableTintIfPossible (
521
+ trackDrawable , trackTintList , currentStateUnchecked , currentStateChecked , thumbPosition );
522
+ }
523
+
524
+ if (trackDecorationTintList != null ) {
331
525
setInterpolatedDrawableTintIfPossible (
332
- getThumbDrawable () ,
333
- thumbTintList ,
526
+ trackDecorationDrawable ,
527
+ trackDecorationTintList ,
334
528
currentStateUnchecked ,
335
529
currentStateChecked ,
336
530
thumbPosition );
0 commit comments