芯片组单选

52

如何让ChipGroupRadioGroup一样始终至少有一个选定的项目?设置setSingleSelection(true)也会使得在单击Chip两次时可以不选择任何项。

8个回答

111
为了防止取消选择所有的芯片,您可以使用方法 setSelectionRequired
chipGroup.setSelectionRequired(true)

您还可以在布局中使用app:selectionRequired属性进行定义:

<com.google.android.material.chip.ChipGroup
    app:singleSelection="true"
    app:selectionRequired="true"
    app:checkedChip="@id/..."
    ..>

注意:此要求最低版本为1.2.0


3
需要得到赞同。这个功能不再需要像其他答案那样被黑客攻击。 - smdufb

24

编辑

从版本1.2.0-alpha02开始,旧的hacky解决方案不再需要!

可以使用属性app:selectionRequired="true"


<com.google.android.material.chip.ChipGroup
            android:id="@+id/group"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:selectionRequired="true"
            app:singleSelection="true">

  (...)
</com.google.android.material.chip.ChipGroup>

或者在代码中


// Kotlin
group.isSelectionRequired = true

// Java
group.setSelectionRequired(true);


对于旧版本

需要两个步骤才能实现此功能。

第一步

我们已经内置了此支持,只需确保将app:singleSelection="true"添加到您的ChipGroup中,例如:

XML

<com.google.android.material.chip.ChipGroup
            android:id="@+id/group"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:singleSelection="true">

        <com.google.android.material.chip.Chip
                android:id="@+id/option_1"
                style="@style/Widget.MaterialComponents.Chip.Choice"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Option 1" />

        <com.google.android.material.chip.Chip
                android:id="@+id/option_2"
                style="@style/Widget.MaterialComponents.Chip.Choice"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Option 2" />
</com.google.android.material.chip.ChipGroup>

代码


// Kotlin
group.isSingleSelection = true

// Java
group.setSingleSelection(true);

步骤 2

现在要实现类似于单选框组的功能:


var lastCheckedId = View.NO_ID
chipGroup.setOnCheckedChangeListener { group, checkedId ->
    if(checkedId == View.NO_ID) {
        // User tried to uncheck, make sure to keep the chip checked          
        group.check(lastCheckedId)
        return@setOnCheckedChangeListener
    }
    lastCheckedId = checkedId

    // New selection happened, do your logic here.
    (...)

}

根据文档

ChipGroup 还支持一组芯片的多重排除范围。 当你设置 app:singleSelection 属性时,选择属于芯片组的一个芯片会取消选中同一组内之前选中的任何芯片。 行为类似于 RadioGroup。


OP要求确保至少选择一个芯片的方法。这个答案指向了他们在问题中已经知道的功能,不符合他们的需求。 - AlgoRyan
谢谢指出,但是贬低似乎有点过分了,我现在已经添加了可行的解决方案。 - Joaquim Ley
好的,既然有一个可行的解决方案,我会取消踩的。不过你在 group.check(lastCheckId) 中仍有一个拼写错误。 - AlgoRyan
现在我想起来了,调用 group.check() 不会导致这个监听器被多次冗余地调用吗?由于 ChipGroup 中的一些守卫代码,只会多出一个调用,但仍然存在。 - AlgoRyan
@JoaquimLey,你能解释一下return@setOnCheckedChangeListener的返回类型是什么吗? - Agapito Gallart Bernat
1
@AgapitoGallartiBernat 应该是 void/Unit,@ setOnCheckedChangeListener 是 Kotlin 语法,它指的是你想要在哪个语句处返回,而在这种情况下,是运行监听器 lambda 的逻辑。 - Joaquim Ley

14
一种解决方案是预设一个已点击的芯片,然后切换芯片的可点击属性。
chipGroup.setOnCheckedChangeListener((chipGroup, id) -> {
    Chip chip = ((Chip) chipGroup.getChildAt(chipGroup.getCheckedChipId()));
    if (chip != null) {
        for (int i = 0; i < chipGroup.getChildCount(); ++i) {
            chipGroup.getChildAt(i).setClickable(true);
        }
        chip.setClickable(false);
    }
});

这段代码有一个轻微的错误。chipGroup.getChildAt()需要传入索引值,而不是芯片视图的资源ID。 - Todd DeLand
@ToddDeLand:没错,我忘了在我的用例中提到,我根据其他容器编程方式地将 Chips 添加到 ChipGroup 中,所以资源 ID 对此没有帮助。在将它们添加到组之前,我使用 chip.setId(i++)。 - adriennoir
听起来很奇怪,但这是我找到的唯一解决方案(纠正使用ID而不是索引)。 - ror
2
Chip chip = chipGroup.findViewById(chipGroup.getCheckedChipId()); 芯片芯片 = chipGroup.findViewById(chipGroup.getCheckedChipId()); - Joseph Paddy
我发现的唯一问题是当我通过XML选择一个芯片来启动屏幕时。如果你点击它,它会取消选择。之后,这个解决方案就可以工作了。 - FabioR
hipGroup.getChildAt() 接受一个索引。你需要使用 Joseph 的代码 Chip chip = chipGroup.findViewById(chipGroup.getCheckedChipId()); - Mostafa Rostami

6
@adriennoir的答案进行简要修改(Kotlin语言)。感谢帮助!注意,getChildAt()接受一个索引。
for (i in 0 until group.childCount) {
    val chip = group.getChildAt(i)
    chip.isClickable = chip.id != group.checkedChipId
}

以下是更完整的 `setOnCheckedChangeListener`,以供参考:

intervalChipGroup.setOnCheckedChangeListener { group, checkedId ->

    for (i in 0 until group.childCount) {
        val chip = group.getChildAt(i)
        chip.isClickable = chip.id != group.checkedChipId
    }

    when (checkedId) {
        R.id.intervalWeek -> {
            view.findViewById<Chip>(R.id.intervalWeek).chipStrokeWidth = 1F
            view.findViewById<Chip>(R.id.intervalMonth).chipStrokeWidth = 0F
            view.findViewById<Chip>(R.id.intervalYear).chipStrokeWidth = 0F
            currentIntervalSelected = weekInterval
            populateGraph(weekInterval)
        }
        R.id.intervalMonth -> {
            view.findViewById<Chip>(R.id.intervalWeek).chipStrokeWidth = 0F
            view.findViewById<Chip>(R.id.intervalMonth).chipStrokeWidth = 1F
            view.findViewById<Chip>(R.id.intervalYear).chipStrokeWidth = 0F
            currentIntervalSelected = monthInterval
            populateGraph(monthInterval)

        }
        R.id.intervalYear -> {
            view.findViewById<Chip>(R.id.intervalWeek).chipStrokeWidth = 0F
            view.findViewById<Chip>(R.id.intervalMonth).chipStrokeWidth = 0F
            view.findViewById<Chip>(R.id.intervalYear).chipStrokeWidth = 1F
            currentIntervalSelected = yearInterval
            populateGraph(yearInterval)
        }
    }

}

5

大多数的回答对我来说都非常好并且很有帮助。对于@adriennoir和@Todd DeLand提出的一个小修改,以防止在setSingleSelection(true) ChipGroup中取消已经选中的chip,这是我的解决方案:

for (i in 0 until chipGroup.childCount) {
    val chip = chipGroup.getChildAt(i) as Chip
    chip.isCheckable = chip.id != chipGroup.checkedChipId
    chip.isChecked = chip.id == chipGroup.checkedChipId
}

对于我来说,我只需要防止已选中的芯片在不可点击的情况下被取消选中状态。这样,用户仍然可以单击选中的芯片并看到华丽的涟漪效果,并且不会发生任何事情。

2
如果使用动态添加的芯片,singleSelection 无法正常工作,则必须在创建它们时为每个芯片 生成id,然后将其添加到 ChipGroup 中。
val chip = inflater.inflate(
R.layout.item_crypto_currency_category_chip,
binding.chipGroupCryptoCurrencyCategory,
false) as Chip

chip.id = ViewCompat.generateViewId()

binding.chipGroupCryptoCurrencyCategory.addView(chip)

//Set default value with index 0 when ChipGroup created.
if (index == 0) binding.chipGroupCryptoCurrencyCategory.check(chip.id)

item_crypto_currency_category_chip.xml

<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.chip.Chip xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/chip_smart_contract"
style="@style/Widget.Signal.Chip"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

item_crypto_currency_tag_category.xml

<HorizontalScrollView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="@dimen/spacing_6x"
    android:scrollbars="none"
    app:layout_constraintTop_toTopOf="parent">

    <com.google.android.material.chip.ChipGroup
        android:id="@+id/chip_group_crypto_currency_category"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:singleSelection="true"
        app:singleLine="true"
        />

</HorizontalScrollView>

Result:

ChipGroup example


我正在做同样的事情,但仍然选择了多个芯片。你能帮我吗? - bhaskar

1
这是我的做法:
var previousSelection: Int = default_selection_id 
chipGroup.setOnCheckedChangeListener { chipGroup, id ->
    if (id == -1) //nothing is selected.
        chipGroup.check(previousSelection)
    else
        previousSelection = id

这不会让之前被点击的芯片每次被选中,而不是用户实际点击的那个吗? - AlgoRyan
不,当用户点击芯片时,芯片组会自动更新选择。这段代码所做的就是在没有当前选定项的情况下存储最近的选择,因为芯片组允许这样做。 - mhashim6
那么我想强调的是,与Joaquim的答案一样,这需要通过调用chipGroup.check()来进行单个重复调用onCheckedChange() - AlgoRyan
是的,我相信我的答案是所有答案中最简单的。但是没有人注意到它。 - mhashim6
这是唯一对我起作用的解决方案,特别是因为在屏幕呈现时有默认值。 - FabioR

0

这是我的工作解决方案

mChipGroup.setOnCheckedChangeListener((group, checkedId) -> {
            for (int i = 0; i < mChipGroup.getChildCount(); i++) {
                Chip chip = (Chip) mChipGroup.getChildAt(i);
                if (chip != null) {
                    chip.setClickable(!(chip.getId() == mChipGroup.getCheckedChipId()));
                }
            }
    });

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接