Skip to content

Commit 441877f

Browse files
gsajithafohrman
authored andcommitted
Add checked state to MaterialButton
PiperOrigin-RevId: 234214200
1 parent 8dcee7d commit 441877f

File tree

6 files changed

+141
-3
lines changed

6 files changed

+141
-3
lines changed

lib/java/com/google/android/material/button/MaterialButton.java

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@
4949
import androidx.appcompat.widget.AppCompatButton;
5050
import android.util.AttributeSet;
5151
import android.util.Log;
52+
import android.view.accessibility.AccessibilityNodeInfo;
53+
import android.widget.Checkable;
5254
import java.lang.annotation.Retention;
5355
import java.lang.annotation.RetentionPolicy;
5456

@@ -88,7 +90,20 @@
8890
* <p>Specify the radius of all four corners of the button using the {@link R.attr#cornerRadius
8991
* app:cornerRadius} attribute.
9092
*/
91-
public class MaterialButton extends AppCompatButton {
93+
public class MaterialButton extends AppCompatButton implements Checkable {
94+
95+
/** Interface definition for a callback to be invoked when the button checked state changes. */
96+
public interface OnCheckedChangeListener {
97+
/**
98+
* Called when the checked state of a MaterialButton has changed.
99+
*
100+
* @param button The MaterialButton whose state has changed.
101+
* @param isChecked The new checked state of MaterialButton.
102+
*/
103+
void onCheckedChanged(MaterialButton button, boolean isChecked);
104+
}
105+
106+
private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};
92107

93108
/**
94109
* Gravity used to position the icon at the start of the view.
@@ -123,6 +138,9 @@ public class MaterialButton extends AppCompatButton {
123138
private Drawable icon;
124139
@Px private int iconSize;
125140
@Px private int iconLeft;
141+
private boolean checked = false;
142+
private boolean broadcasting = false;
143+
@Nullable private OnCheckedChangeListener onCheckedChangeListener;
126144

127145
@IconGravity private int iconGravity;
128146

@@ -168,6 +186,14 @@ public MaterialButton(Context context, AttributeSet attrs, int defStyleAttr) {
168186
updateIcon();
169187
}
170188

189+
@Override
190+
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
191+
super.onInitializeAccessibilityNodeInfo(info);
192+
info.setClassName(MaterialButton.class.getName());
193+
info.setCheckable(isCheckable());
194+
info.setClickable(isClickable());
195+
}
196+
171197
/**
172198
* This should be accessed via {@link
173199
* androidx.core.view.ViewCompat#setBackgroundTintList(android.view.View, ColorStateList)}
@@ -748,6 +774,84 @@ public int getIconGravity() {
748774
public void setIconGravity(@IconGravity int iconGravity) {
749775
this.iconGravity = iconGravity;
750776
}
777+
778+
@Override
779+
protected int[] onCreateDrawableState(int extraSpace) {
780+
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
781+
if (isChecked()) {
782+
mergeDrawableStates(drawableState, CHECKED_STATE_SET);
783+
}
784+
785+
return drawableState;
786+
}
787+
788+
/**
789+
* Register a callback to be invoked when the checked state of this MaterialButton changes.
790+
*
791+
* @param listener the callback to call on checked state change
792+
*/
793+
public void setOnCheckedChangeListener(@Nullable OnCheckedChangeListener listener) {
794+
onCheckedChangeListener = listener;
795+
}
796+
797+
@Override
798+
public void setChecked(boolean checked) {
799+
if (isCheckable() && isEnabled() && this.checked != checked) {
800+
this.checked = checked;
801+
refreshDrawableState();
802+
803+
// Avoid infinite recursions if setChecked() is called from a listener
804+
if (broadcasting) {
805+
return;
806+
}
807+
808+
broadcasting = true;
809+
if (onCheckedChangeListener != null) {
810+
onCheckedChangeListener.onCheckedChanged(this, this.checked);
811+
}
812+
813+
broadcasting = false;
814+
}
815+
}
816+
817+
@Override
818+
public boolean isChecked() {
819+
return checked;
820+
}
821+
822+
@Override
823+
public void toggle() {
824+
setChecked(!checked);
825+
}
826+
827+
@Override
828+
public boolean performClick() {
829+
toggle();
830+
831+
return super.performClick();
832+
}
833+
834+
/**
835+
* Returns whether this MaterialButton is checkable.
836+
*
837+
* @see #setCheckable(boolean)
838+
* @attr ref com.google.android.material.R.styleable#MaterialButton_android_checkable
839+
*/
840+
public boolean isCheckable() {
841+
return materialButtonHelper != null && materialButtonHelper.isCheckable();
842+
}
843+
844+
/**
845+
* Sets whether this MaterialButton is checkable.
846+
*
847+
* @param checkable Whether this button is checkable.
848+
* @attr ref com.google.android.material.R.styleable#MaterialButton_android_checkable
849+
*/
850+
public void setCheckable(boolean checkable) {
851+
if (isUsingOriginalBackground()) {
852+
materialButtonHelper.setCheckable(checkable);
853+
}
854+
}
751855

752856
private boolean isUsingOriginalBackground() {
753857
return materialButtonHelper != null && !materialButtonHelper.isBackgroundOverwritten();

lib/java/com/google/android/material/button/MaterialButtonHelper.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class MaterialButtonHelper {
6565
private boolean shouldDrawSurfaceColorStroke = false;
6666
private boolean backgroundOverwritten = false;
6767
private boolean cornerRadiusSet = false;
68+
private boolean checkable;
6869
private LayerDrawable rippleDrawable;
6970

7071
MaterialButtonHelper(MaterialButton button, ShapeAppearanceModel shapeAppearanceModel) {
@@ -102,6 +103,7 @@ void loadFromAttributes(TypedArray attributes) {
102103
MaterialResources.getColorStateList(
103104
materialButton.getContext(), attributes, R.styleable.MaterialButton_rippleColor);
104105

106+
checkable = attributes.getBoolean(R.styleable.MaterialButton_android_checkable, false);
105107
int elevation = attributes.getDimensionPixelSize(R.styleable.MaterialButton_elevation, 0);
106108

107109
// Store padding before setting background, since background overwrites padding values
@@ -384,6 +386,14 @@ MaterialShapeDrawable getMaterialShapeDrawable() {
384386
return getMaterialShapeDrawable(false);
385387
}
386388

389+
void setCheckable(boolean checkable) {
390+
this.checkable = checkable;
391+
}
392+
393+
boolean isCheckable() {
394+
return checkable;
395+
}
396+
387397
@Nullable
388398
private MaterialShapeDrawable getSurfaceColorStrokeDrawable() {
389399
return getMaterialShapeDrawable(true);

lib/java/com/google/android/material/button/res/color/mtrl_btn_stroke_color_selector.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@
1616
-->
1717

1818
<selector xmlns:android="http://schemas.android.com/apk/res/android">
19-
<item android:alpha="0.12" android:color="?attr/colorOnSurface"/>
19+
<item android:alpha="0.24" android:color="?attr/colorPrimary" android:state_checked="true"/>
20+
<item android:alpha="0.12" android:color="?attr/colorOnSurface" android:state_checked="false"/>
2021
</selector>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
Copyright 2019 The Android Open Source Project
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
-->
17+
18+
<selector xmlns:android="http://schemas.android.com/apk/res/android">
19+
<item android:alpha="0.12" android:color="?attr/colorPrimary" android:state_checked="true"/>
20+
<item android:color="@android:color/transparent" android:state_checked="false"/>
21+
</selector>

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
<attr name="materialButtonOutlinedStyle" format="reference"/>
2525

2626
<declare-styleable name="MaterialButton">
27+
<!-- Whether the button can be checked. -->
28+
<attr name="android:checkable"/>
2729
<attr name="android:insetLeft"/>
2830
<attr name="android:insetRight"/>
2931
<attr name="android:insetTop"/>

lib/java/com/google/android/material/button/res/values/styles.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
<item name="android:paddingRight">@dimen/mtrl_btn_text_btn_padding_right</item>
6161
<item name="iconTint">@color/mtrl_text_btn_text_color_selector</item>
6262
<item name="iconPadding">@dimen/mtrl_btn_text_btn_icon_padding</item>
63-
<item name="backgroundTint">@android:color/transparent</item>
63+
<item name="backgroundTint">@color/mtrl_btn_text_btn_bg_color_selector</item>
6464
<item name="rippleColor">@color/mtrl_btn_text_btn_ripple_color</item>
6565
</style>
6666

0 commit comments

Comments
 (0)