Skip to content

Commit 6654514

Browse files
committed
[Switch] Make thumb icon visibility configurable
Resolves material-components#2966
1 parent 64fd72f commit 6654514

9 files changed

Lines changed: 188 additions & 26 deletions

File tree

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!--
2-
~ Copyright (C) 2023 The Android Open Source Project
2+
~ Copyright (C) 2026 The Android Open Source Project
33
~
44
~ Licensed under the Apache License, Version 2.0 (the "License");
55
~ you may not use this file except in compliance with the License.
@@ -14,14 +14,12 @@
1414
~ limitations under the License.
1515
-->
1616
<vector xmlns:android="http://schemas.android.com/apk/res/android"
17-
xmlns:tools="http://schemas.android.com/tools"
18-
android:width="48dp"
19-
android:height="48dp"
20-
android:tint="?attr/colorControlNormal"
21-
android:viewportHeight="960"
22-
android:viewportWidth="960"
23-
tools:ignore="NewApi">
17+
android:width="24dp"
18+
android:height="24dp"
19+
android:viewportWidth="24"
20+
android:viewportHeight="24"
21+
android:tint="?attr/colorControlNormal">
2422
<path
25-
android:fillColor="@android:color/white"
26-
android:pathData="M378,714L154,490L197,447L378,628L762,244L805,287L378,714Z" />
23+
android:fillColor="@android:color/white"
24+
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41L9,16.17z"/>
2725
</vector>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<!--
2+
~ Copyright (C) 2026 The Android Open Source Project
3+
~
4+
~ Licensed under the Apache License, Version 2.0 (the "License");
5+
~ you may not use this file except in compliance with the License.
6+
~ You may obtain a copy of the License at
7+
~
8+
~ http://www.apache.org/licenses/LICENSE-2.0
9+
~
10+
~ Unless required by applicable law or agreed to in writing, software
11+
~ distributed under the License is distributed on an "AS IS" BASIS,
12+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
~ See the License for the specific language governing permissions and
14+
~ limitations under the License.
15+
-->
16+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
17+
android:width="24dp"
18+
android:height="24dp"
19+
android:viewportWidth="24"
20+
android:viewportHeight="24"
21+
android:tint="?attr/colorControlNormal">
22+
<path
23+
android:fillColor="@android:color/white"
24+
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12 19,6.41z"/>
25+
</vector>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<!--
2+
~ Copyright (C) 2026 The Android Open Source Project
3+
~
4+
~ Licensed under the Apache License, Version 2.0 (the "License");
5+
~ you may not use this file except in compliance with the License.
6+
~ You may obtain a copy of the License at
7+
~
8+
~ http://www.apache.org/licenses/LICENSE-2.0
9+
~
10+
~ Unless required by applicable law or agreed to in writing, software
11+
~ distributed under the License is distributed on an "AS IS" BASIS,
12+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
~ See the License for the specific language governing permissions and
14+
~ limitations under the License.
15+
-->
16+
<selector xmlns:android="http://schemas.android.com/apk/res/android">
17+
<item android:state_checked="true" android:drawable="@drawable/mtrl_ic_check" />
18+
<item android:drawable="@drawable/mtrl_ic_close" />
19+
</selector>

catalog/java/io/material/catalog/materialswitch/res/layout/cat_material_switch.xml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,18 +86,18 @@
8686
<com.google.android.material.materialswitch.MaterialSwitch
8787
android:layout_width="match_parent"
8888
android:layout_height="match_parent"
89-
android:checked="true"
89+
android:checked="false"
9090
android:enabled="true"
91-
app:thumbIcon="@drawable/mtrl_ic_check"
91+
app:thumbIcon="@drawable/mtrl_switch_icon"
9292
android:text="@string/cat_switch_enabled" />
9393

9494
<com.google.android.material.materialswitch.MaterialSwitch
9595
android:layout_width="match_parent"
9696
android:layout_height="match_parent"
97-
android:checked="true"
97+
android:checked="false"
9898
android:enabled="true"
99-
app:thumbIcon="@drawable/mtrl_ic_check"
100-
app:thumbIconSize="24dp"
99+
app:thumbIcon="@drawable/mtrl_switch_icon"
100+
app:thumbIconVisibility="whenChecked"
101101
android:text="@string/cat_switch_enabled" />
102102
</GridLayout>
103103

catalog/java/io/material/catalog/materialswitch/res/values/strings.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
</string>
3636
<string name="cat_switch_icon_guide"
3737
description="The description of how switches look like with icon. [CHAR LIMIT=NONE]">
38-
Switch elements with an icon, default size (16dp) and 24dp.
38+
Switch elements with an icon: one shows the icon in all states, the other only when checked.
3939
</string>
4040
<string name="cat_switch_enabled"
4141
description="The label of enabled switches. [CHAR LIMIT=NONE]">

docs/components/Switch.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,12 @@ You can add an optional icon to enhance the on/off indication of your custom
5959
switch by assigning `app:thumbIcon`. This icon will be centered and displayed on
6060
top of the thumb drawable.
6161

62-
Element | Attribute | Related method(s) | Default value
63-
--------- | ------------------- | ------------------------------------------------- | -------------
64-
**Icon** | `app:thumbIcon` | `setThumbIconDrawable`<br/>`getThumbIconDrawable` | `null`
65-
**Size** | `app:thumbIconSize` | `setThumbIconSize`<br/>`getThumbIconSize` | `16dp`
66-
**Color** | `app:thumbIconTint` | `setThumbIconTintList`<br/>`getThumbIconTintList` | `?attr/colorSurfaceContainerHighest` (unchecked)<br/>`?attr/colorOnPrimaryContainer` (checked)
62+
Element | Attribute | Related method(s) | Default value
63+
------------------------ |-------------------------------|---------------------------------------------------------------| -------------
64+
**Icon** | `app:thumbIcon` | `setThumbIconDrawable`<br/>`getThumbIconDrawable` | `null`
65+
**Size** | `app:thumbIconSize` | `setThumbIconSize`<br/>`getThumbIconSize` | `16dp`
66+
**Color** | `app:thumbIconTint` | `setThumbIconTintList`<br/>`getThumbIconTintList` | `?attr/colorSurfaceContainerHighest` (unchecked)<br/>`?attr/colorOnPrimaryContainer` (checked)
67+
**Visibility** | `app:thumbIconVisibility` | `setThumbIconVisibility`<br/>`getThumbIconVisibility` | `always`
6768

6869
### Track attributes
6970

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

Lines changed: 111 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,12 @@
3232
import androidx.appcompat.widget.TintTypedArray;
3333
import android.util.AttributeSet;
3434
import androidx.annotation.DrawableRes;
35+
import androidx.annotation.IntDef;
3536
import androidx.annotation.NonNull;
3637
import androidx.annotation.Nullable;
3738
import androidx.annotation.Px;
39+
import java.lang.annotation.Retention;
40+
import java.lang.annotation.RetentionPolicy;
3841
import com.google.android.material.drawable.DrawableUtils;
3942
import com.google.android.material.focus.FocusRingDrawable;
4043
import com.google.android.material.internal.ThemeEnforcement;
@@ -54,9 +57,51 @@ public class MaterialSwitch extends SwitchCompat {
5457
private static final int DEF_STYLE_RES = R.style.Widget_Material3_CompoundButton_MaterialSwitch;
5558
private static final int[] STATE_SET_WITH_ICON = { R.attr.state_with_icon };
5659

60+
@IntDef({
61+
THUMB_ICON_VISIBILITY_ALWAYS,
62+
THUMB_ICON_VISIBILITY_WHEN_CHECKED,
63+
THUMB_ICON_VISIBILITY_WHEN_UNCHECKED,
64+
THUMB_ICON_VISIBILITY_NEVER
65+
})
66+
@Retention(RetentionPolicy.SOURCE)
67+
public @interface ThumbIconVisibility {}
68+
69+
/**
70+
* The thumb icon is displayed in both the checked and unchecked states.
71+
*
72+
* @see #setThumbIconVisibility(int)
73+
* @see #getThumbIconVisibility()
74+
*/
75+
public static final int THUMB_ICON_VISIBILITY_ALWAYS = 0;
76+
77+
/**
78+
* The thumb icon is displayed only in the checked state.
79+
*
80+
* @see #setThumbIconVisibility(int)
81+
* @see #getThumbIconVisibility()
82+
*/
83+
public static final int THUMB_ICON_VISIBILITY_WHEN_CHECKED = 1;
84+
85+
/**
86+
* The thumb icon is displayed only in the unchecked state.
87+
*
88+
* @see #setThumbIconVisibility(int)
89+
* @see #getThumbIconVisibility()
90+
*/
91+
public static final int THUMB_ICON_VISIBILITY_WHEN_UNCHECKED = 2;
92+
93+
/**
94+
* The thumb icon is never displayed.
95+
*
96+
* @see #setThumbIconVisibility(int)
97+
* @see #getThumbIconVisibility()
98+
*/
99+
public static final int THUMB_ICON_VISIBILITY_NEVER = 3;
100+
57101
@Nullable private Drawable thumbDrawable;
58102
@Nullable private Drawable thumbIconDrawable;
59-
@Px private int thumbIconSize = DrawableUtils.INTRINSIC_SIZE;
103+
@Px private int thumbIconSize;
104+
@ThumbIconVisibility private int thumbIconVisibility;
60105

61106
@Nullable private Drawable trackDrawable;
62107
@Nullable private Drawable trackDecorationDrawable;
@@ -99,6 +144,8 @@ public MaterialSwitch(@NonNull Context context, @Nullable AttributeSet attrs, in
99144
thumbIconDrawable = attributes.getDrawable(R.styleable.MaterialSwitch_thumbIcon);
100145
thumbIconSize = attributes.getDimensionPixelSize(
101146
R.styleable.MaterialSwitch_thumbIconSize, DrawableUtils.INTRINSIC_SIZE);
147+
thumbIconVisibility = attributes.getInt(
148+
R.styleable.MaterialSwitch_thumbIconVisibility, THUMB_ICON_VISIBILITY_ALWAYS);
102149

103150
thumbIconTintList = attributes.getColorStateList(R.styleable.MaterialSwitch_thumbIconTint);
104151
thumbIconTintMode =
@@ -142,10 +189,12 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto
142189

143190
@Override
144191
protected int[] onCreateDrawableState(int extraSpace) {
145-
int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
146-
147-
if (thumbIconDrawable != null) {
192+
final int[] drawableState;
193+
if (shouldShowThumbIcon()) {
194+
drawableState = super.onCreateDrawableState(extraSpace + 1);
148195
mergeDrawableStates(drawableState, STATE_SET_WITH_ICON);
196+
} else {
197+
drawableState = super.onCreateDrawableState(extraSpace);
149198
}
150199

151200
currentStateUnchecked = DrawableUtils.getUncheckedState(drawableState);
@@ -154,6 +203,36 @@ protected int[] onCreateDrawableState(int extraSpace) {
154203
return drawableState;
155204
}
156205

206+
private boolean shouldShowThumbIcon() {
207+
if (thumbIconDrawable == null) {
208+
return false;
209+
}
210+
211+
switch (thumbIconVisibility) {
212+
case THUMB_ICON_VISIBILITY_ALWAYS:
213+
return true;
214+
case THUMB_ICON_VISIBILITY_WHEN_CHECKED:
215+
return isChecked();
216+
case THUMB_ICON_VISIBILITY_WHEN_UNCHECKED:
217+
return !isChecked();
218+
case THUMB_ICON_VISIBILITY_NEVER:
219+
return false;
220+
default:
221+
throw new IllegalArgumentException("Unexpected thumbIconVisibility: " + thumbIconVisibility);
222+
}
223+
}
224+
225+
@Override
226+
public void setChecked(boolean checked) {
227+
boolean oldState = shouldShowThumbIcon();
228+
super.setChecked(checked);
229+
boolean newState = shouldShowThumbIcon();
230+
231+
if (oldState != newState) {
232+
refreshThumbDrawable();
233+
}
234+
}
235+
157236
@Override
158237
public void setThumbDrawable(@Nullable Drawable drawable) {
159238
thumbDrawable = drawable;
@@ -290,6 +369,32 @@ public PorterDuff.Mode getThumbIconTintMode() {
290369
return thumbIconTintMode;
291370
}
292371

372+
/**
373+
* Sets the checked state(s) in which the thumb icon will be displayed. The default value is
374+
* {@link #THUMB_ICON_VISIBILITY_ALWAYS}.
375+
*
376+
* @param thumbIconVisibility one of {@link #THUMB_ICON_VISIBILITY_ALWAYS},
377+
* {@link #THUMB_ICON_VISIBILITY_WHEN_CHECKED}, {@link #THUMB_ICON_VISIBILITY_WHEN_UNCHECKED},
378+
* or {@link #THUMB_ICON_VISIBILITY_NEVER}
379+
* @attr ref com.google.android.material.R.styleable#MaterialSwitch_thumbIconVisibility
380+
*/
381+
public void setThumbIconVisibility(@ThumbIconVisibility int thumbIconVisibility) {
382+
if (this.thumbIconVisibility != thumbIconVisibility) {
383+
this.thumbIconVisibility = thumbIconVisibility;
384+
refreshThumbDrawable();
385+
}
386+
}
387+
388+
/**
389+
* Returns the checked state(s) in which the thumb icon is displayed.
390+
*
391+
* @attr ref com.google.android.material.R.styleable#MaterialSwitch_thumbIconVisibility
392+
*/
393+
@ThumbIconVisibility
394+
public int getThumbIconVisibility() {
395+
return thumbIconVisibility;
396+
}
397+
293398
@Override
294399
public void setTrackDrawable(@Nullable Drawable track) {
295400
trackDrawable = track;
@@ -414,8 +519,9 @@ private void refreshThumbDrawable() {
414519

415520
updateDrawableTints();
416521

522+
Drawable effectiveThumbIconDrawable = shouldShowThumbIcon() ? thumbIconDrawable : null;
417523
super.setThumbDrawable(DrawableUtils.compositeTwoLayeredDrawable(
418-
thumbDrawable, thumbIconDrawable, thumbIconSize, thumbIconSize));
524+
thumbDrawable, effectiveThumbIconDrawable, thumbIconSize, thumbIconSize));
419525

420526
refreshDrawableState();
421527
}

lib/java/com/google/android/material/materialswitch/res-public/values/public.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
<public name="thumbIconSize" type="attr"/>
2222
<public name="thumbIconTint" type="attr"/>
2323
<public name="thumbIconTintMode" type="attr"/>
24+
<public name="thumbIconVisibility" type="attr"/>
2425
<public name="trackDecoration" type="attr"/>
2526
<public name="trackDecorationTint" type="attr"/>
2627
<public name="trackDecorationTintMode" type="attr"/>

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@
2323
<declare-styleable name="MaterialSwitch">
2424
<!-- Drawable used for the thumb icon that will be drawn upon the thumb. -->
2525
<attr name="thumbIcon" format="reference"/>
26+
<!-- Determines the checked state(s) in which the thumb icon will be displayed.
27+
Defaults to "always". -->
28+
<attr name="thumbIconVisibility" format="enum">
29+
<!-- The thumb icon is shown in both checked and unchecked states. -->
30+
<enum name="always" value="0" />
31+
<!-- The thumb icon is shown only in the checked state. -->
32+
<enum name="whenChecked" value="1" />
33+
<!-- The thumb icon is shown only in the unchecked state. -->
34+
<enum name="whenUnchecked" value="2" />
35+
<!-- The thumb icon is never shown. -->
36+
<enum name="never" value="3" />
37+
</attr>
2638
<!-- Tint that will be applied to the thumb icon drawable. -->
2739
<attr name="thumbIconTint" format="color"/>
2840
<!-- The blending mode used to apply the tint specified by thumbIconTint

0 commit comments

Comments
 (0)