Skip to content

Commit 573efa4

Browse files
ymarianhunterstich
authored andcommitted
Add selection required option to ChipGroup
Resolves #651 PiperOrigin-RevId: 281274404
1 parent 7823e88 commit 573efa4

File tree

4 files changed

+77
-7
lines changed

4 files changed

+77
-7
lines changed

lib/java/com/google/android/material/chip/ChipGroup.java

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
import java.util.ArrayList;
3939
import java.util.List;
4040

41-
4241
/**
4342
* A ChipGroup is used to hold multiple {@link Chip}s. By default, the chips are reflowed across
4443
* multiple lines. Set the {@link R.attr#singleLine app:singleLine} attribute to constrain the chips
@@ -88,6 +87,7 @@ public LayoutParams(MarginLayoutParams source) {
8887
@Dimension private int chipSpacingHorizontal;
8988
@Dimension private int chipSpacingVertical;
9089
private boolean singleSelection;
90+
private boolean selectionRequired;
9191

9292
@Nullable private OnCheckedChangeListener onCheckedChangeListener;
9393

@@ -126,6 +126,7 @@ public ChipGroup(Context context, AttributeSet attrs, int defStyleAttr) {
126126
a.getDimensionPixelOffset(R.styleable.ChipGroup_chipSpacingVertical, chipSpacing));
127127
setSingleLine(a.getBoolean(R.styleable.ChipGroup_singleLine, false));
128128
setSingleSelection(a.getBoolean(R.styleable.ChipGroup_singleSelection, false));
129+
setSelectionRequired(a.getBoolean(R.styleable.ChipGroup_selectionRequired, false));
129130
int checkedChip = a.getResourceId(R.styleable.ChipGroup_checkedChip, View.NO_ID);
130131
if (checkedChip != View.NO_ID) {
131132
checkedId = checkedChip;
@@ -442,6 +443,27 @@ public void setSingleSelection(@BoolRes int id) {
442443
setSingleSelection(getResources().getBoolean(id));
443444
}
444445

446+
/**
447+
* Sets whether we prevent all child chips from being deselected.
448+
*
449+
* @attr ref R.styleable#ChipGroup_selectionRequired
450+
* @see #setSingleSelection(boolean)
451+
*/
452+
public void setSelectionRequired(boolean selectionRequired) {
453+
this.selectionRequired = selectionRequired;
454+
}
455+
456+
/**
457+
* Returns whether we prevent all child chips from being deselected.
458+
*
459+
* @attr ref R.styleable#ChipGroup_selectionRequired
460+
* @see #setSingleSelection(boolean)
461+
* @see #setSelectionRequired(boolean)
462+
*/
463+
public boolean isSelectionRequired() {
464+
return selectionRequired;
465+
}
466+
445467
private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {
446468
@Override
447469
public void onCheckedChanged(@NonNull CompoundButton buttonView, boolean isChecked) {
@@ -450,17 +472,22 @@ public void onCheckedChanged(@NonNull CompoundButton buttonView, boolean isCheck
450472
return;
451473
}
452474

475+
List<Integer> checkedChipIds = getCheckedChipIds();
476+
if (checkedChipIds.isEmpty() && selectionRequired) {
477+
setCheckedStateForView(buttonView.getId(), true);
478+
setCheckedId(buttonView.getId());
479+
return;
480+
}
481+
453482
int id = buttonView.getId();
454483

455484
if (isChecked) {
456485
if (checkedId != View.NO_ID && checkedId != id && singleSelection) {
457486
setCheckedStateForView(checkedId, false);
458487
}
459488
setCheckedId(id);
460-
} else {
461-
if (checkedId == id) {
462-
setCheckedId(View.NO_ID);
463-
}
489+
} else if (checkedId == id) {
490+
setCheckedId(View.NO_ID);
464491
}
465492
}
466493
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@
150150
<!-- Whether only a single chip in this group is allowed to be checked at any time. By default,
151151
this is false and multiple chips in this group are allowed to be checked at once. -->
152152
<attr name="singleSelection"/>
153+
<!-- Whether we prevent all child chips from being deselected.
154+
It's false by default. -->
155+
<attr name="selectionRequired"/>
153156
<!-- The id of the child chip that should be checked by default within this chip group. -->
154157
<attr name="checkedChip" format="reference"/>
155158

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
<attr name="checkedIcon" format="reference" />
2020
<attr name="minTouchTargetSize" format="dimension"/>
2121
<attr name="singleSelection" format="boolean"/>
22+
<attr name="selectionRequired" format="boolean"/>
2223
<attr name="strokeColor" format="color"/>
2324
<attr name="strokeWidth" format="dimension"/>
2425
<attr name="ensureMinTouchTargetSize" format="boolean"/>

lib/javatests/com/google/android/material/chip/ChipGroupTest.java

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,9 @@
2727
import org.junit.runner.RunWith;
2828
import org.robolectric.Robolectric;
2929
import org.robolectric.RobolectricTestRunner;
30-
import org.robolectric.annotation.internal.DoNotInstrument;
3130

3231
/** Tests for {@link com.google.android.material.chip.ChipGroup}. */
3332
@RunWith(RobolectricTestRunner.class)
34-
@DoNotInstrument
3533
public class ChipGroupTest {
3634

3735
private static final int CHIP_GROUP_SPACING = 4;
@@ -86,4 +84,45 @@ public void testSingleCheckedChip() {
8684
int checkedId2 = chipgroup.getCheckedChipId();
8785
assertThat(checkedId1).isEqualTo(checkedId2);
8886
}
87+
88+
@Test
89+
public void singleSelection_withSelectionRequired_doesNotUnSelect() {
90+
chipgroup.setSelectionRequired(true);
91+
chipgroup.setSingleSelection(true);
92+
93+
View chip = chipgroup.getChildAt(0);
94+
chip.performClick();
95+
chip.performClick();
96+
97+
assertThat(((Chip) chip).isChecked()).isTrue();
98+
}
99+
100+
@Test
101+
public void singleSelection_withoutSelectionRequired_unSelects() {
102+
chipgroup.setSingleSelection(true);
103+
chipgroup.setSelectionRequired(false);
104+
105+
View chip = chipgroup.getChildAt(0);
106+
chip.performClick();
107+
chip.performClick();
108+
109+
assertThat(((Chip) chip).isChecked()).isFalse();
110+
}
111+
112+
@Test
113+
public void multiSelection_withSelectionRequired_unSelectsIfTwo() {
114+
chipgroup.setSingleSelection(false);
115+
chipgroup.setSelectionRequired(true);
116+
117+
View first = chipgroup.getChildAt(0);
118+
View second = chipgroup.getChildAt(1);
119+
first.performClick();
120+
121+
second.performClick();
122+
second.performClick();
123+
124+
// first button is selected
125+
assertThat(((Chip) first).isChecked()).isTrue();
126+
assertThat(((Chip) second).isChecked()).isFalse();
127+
}
89128
}

0 commit comments

Comments
 (0)