Skip to content

Commit

Permalink
[Checkbox] Added indeterminate state support to the checkbox.
Browse files Browse the repository at this point in the history
A MaterialCheckBox can now be checked, unchecked, or indeterminate.

Resolves #857

PiperOrigin-RevId: 465598224
(cherry picked from commit 8f149b1)
  • Loading branch information
leticiarossi committed Aug 11, 2022
1 parent 81c2ded commit 4da7ce1
Show file tree
Hide file tree
Showing 18 changed files with 874 additions and 22 deletions.
Expand Up @@ -24,15 +24,19 @@
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.checkbox.MaterialCheckBox;
import com.google.android.material.checkbox.MaterialCheckBox.OnCheckedStateChangedListener;
import io.material.catalog.feature.DemoFragment;
import io.material.catalog.feature.DemoUtils;
import java.util.List;

/** A fragment that displays the main Checkbox demos for the Catalog app. */
public class CheckBoxMainDemoFragment extends DemoFragment {

private boolean isUpdatingChildren = false;

@Override
public View onCreateDemoView(
LayoutInflater layoutInflater, @Nullable ViewGroup viewGroup, @Nullable Bundle bundle) {
Expand All @@ -57,6 +61,68 @@ public View onCreateDemoView(
cb.setErrorShown(isChecked);
}
});

CheckBox firstChild = view.findViewById(R.id.checkbox_child_1);
firstChild.setChecked(true);
ViewGroup indeterminateContainer = view.findViewById(R.id.checkbox_indeterminate_container);
List<CheckBox> childrenCheckBoxes =
DemoUtils.findViewsWithType(indeterminateContainer, CheckBox.class);
MaterialCheckBox checkBoxParent = view.findViewById(R.id.checkbox_parent);
OnCheckedStateChangedListener parentOnCheckedStateChangedListener =
(checkBox, state) -> {
boolean isChecked = checkBox.isChecked();
if (state != MaterialCheckBox.STATE_INDETERMINATE) {
isUpdatingChildren = true;
for (CheckBox child : childrenCheckBoxes) {
child.setChecked(isChecked);
}
isUpdatingChildren = false;
}
};
checkBoxParent.addOnCheckedStateChangedListener(parentOnCheckedStateChangedListener);

OnCheckedStateChangedListener childOnCheckedStateChangedListener =
(checkBox, state) -> {
if (isUpdatingChildren) {
return;
}
setParentState(checkBoxParent, childrenCheckBoxes, parentOnCheckedStateChangedListener);
};

for (CheckBox child : childrenCheckBoxes) {
((MaterialCheckBox) child)
.addOnCheckedStateChangedListener(childOnCheckedStateChangedListener);
}

setParentState(checkBoxParent, childrenCheckBoxes, parentOnCheckedStateChangedListener);

return view;
}

private void setParentState(
@NonNull MaterialCheckBox checkBoxParent,
@NonNull List<CheckBox> childrenCheckBoxes,
@NonNull OnCheckedStateChangedListener parentOnCheckedStateChangedListener) {
boolean allChecked = true;
boolean noneChecked = true;
for (CheckBox child : childrenCheckBoxes) {
if (!child.isChecked()) {
allChecked = false;
} else {
noneChecked = false;
}
if (!allChecked && !noneChecked) {
break;
}
}
checkBoxParent.removeOnCheckedStateChangedListener(parentOnCheckedStateChangedListener);
if (allChecked) {
checkBoxParent.setChecked(true);
} else if (noneChecked) {
checkBoxParent.setChecked(false);
} else {
checkBoxParent.setCheckedState(MaterialCheckBox.STATE_INDETERMINATE);
}
checkBoxParent.addOnCheckedStateChangedListener(parentOnCheckedStateChangedListener);
}
}
Expand Up @@ -16,10 +16,10 @@
limitations under the License.
-->

<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">

<LinearLayout
android:id="@+id/main_viewGroup"
Expand All @@ -31,13 +31,13 @@
<GridLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:columnCount="2"
android:columnCount="3"
android:useDefaultMargins="true">

<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_columnSpan="2"
android:layout_columnSpan="3"
android:text="@string/cat_checkbox_intro" />

<CheckBox
Expand All @@ -54,10 +54,17 @@
android:enabled="true"
android:text="@string/cat_checkbox_enabled" />

<CheckBox
android:layout_width="match_parent"
android:layout_height="match_parent"
app:checkedState="indeterminate"
android:enabled="true"
android:text="@string/cat_checkbox_enabled" />

<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_columnSpan="2"
android:layout_columnSpan="3"
android:text="@string/cat_checkbox_disabled_guide" />

<CheckBox
Expand All @@ -73,6 +80,13 @@
android:checked="false"
android:enabled="false"
android:text="@string/cat_checkbox_disabled" />

<CheckBox
android:layout_width="match_parent"
android:layout_height="match_parent"
app:checkedState="indeterminate"
android:enabled="false"
android:text="@string/cat_checkbox_disabled" />
</GridLayout>

<com.google.android.material.divider.MaterialDivider
Expand Down Expand Up @@ -111,6 +125,53 @@
android:text="@string/cat_checkbox_toggle" />
</LinearLayout>

<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/cat_checkbox_line_separator_margin"
android:layout_marginBottom="@dimen/cat_checkbox_line_separator_margin" />

<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/cat_indeterminate_description" />

<CheckBox
android:id="@+id/checkbox_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:checkedState="indeterminate"
android:text="@string/cat_checkbox_indeterminate" />

<LinearLayout
android:id="@+id/checkbox_indeterminate_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/cat_checkbox_indent"
android:orientation="vertical">

<CheckBox
android:id="@+id/checkbox_child_1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:checked="true"
android:text="@string/cat_checkbox_indeterminate_child" />

<CheckBox
android:id="@+id/checkbox_child_2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:checked="false"
android:text="@string/cat_checkbox_indeterminate_child" />

<CheckBox
android:id="@+id/checkbox_child_3"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:checked="false"
android:text="@string/cat_checkbox_indeterminate_child" />
</LinearLayout>

<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content"
Expand Down
Expand Up @@ -31,6 +31,14 @@
Check this checkbox to control the enabled state of the boxes below.</string>
<string name="cat_checkbox_toggle" translatable="false">
Controlled by a checkbox</string>
<string name="cat_indeterminate_description" translatable="false">
The checkboxes below have a parent/child relationship. The parent\'s state
depends on the state of the children.
</string>
<string name="cat_checkbox_indeterminate" translatable="false">
Parent checkbox</string>
<string name="cat_checkbox_indeterminate_child" translatable="false">
Child checkbox</string>
<string name="cat_checkbox_no_text" translatable="false">
Below is a checkbox with no text.</string>
<string name="cat_checkbox_toggle_error" translatable="false">
Expand Down
28 changes: 24 additions & 4 deletions docs/components/Checkbox.md
Expand Up @@ -73,14 +73,37 @@ checkbox.errorShown = true
// Optional listener:
checkbox.addOnErrorChangedListener { checkBox, errorShown ->
// Responds to when the checkbox enters/leaves error state
}
}

// To set a custom accessibility label:
checkbox.errorAccessibilityLabel = "Error: custom error announcement."

```

### Making a checkbox indeterminate

In the layout:

```xml
<CheckBox
...
app:checkedState="indeterminate"/>
```

In code:

```kt
// You can set the state of the checkbox (STATE_CHECKED, STATE_UNCHECKED,
// or STATE_INDETERMINATE) via setCheckedState.
checkBox.setCheckedState(MaterialCheckbox.STATE_INDETERMINATE);

// Checkbox state listener.
checkbox.addOnCheckedStateChangedListener { checkBox, state ->
// Responds to when the checkbox changes state.
}
```


## Checkbox

A checkbox is a square button with a check to denote its current state.
Expand Down Expand Up @@ -193,9 +216,6 @@ enabled, disabled, hover, focused, and pressed states.
pressed. Rows are selected, unselected, or
indeterminite](assets/checkbox/checkbox_states.png)

**Note:** `MaterialCheckBox` does not support the indeterminate state. Only
selected and unselected states are supported.

### Styles

Element | Style
Expand Down

0 comments on commit 4da7ce1

Please sign in to comment.