How can I force a ChipGroup to act like a RadioGroup as in having at least one selected item always? Setting setSingleSelection(true) also adds the possibility to have nothing selected if you click twice on a Chip.
To prevent all chips from being deselected you can use the method setSelectionRequired:
chipGroup.setSelectionRequired(true)
You can also define it in the layout using the app:selectionRequired attribute:
<com.google.android.material.chip.ChipGroup
app:singleSelection="true"
app:selectionRequired="true"
app:checkedChip="#id/..."
..>
Note: This requires a minimum of version 1.2.0
EDIT
With version 1.2.0-alpha02 the old hacky solution is no longer required!
Either use the attribute 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>
Or in code
// Kotlin
group.isSelectionRequired = true
// Java
group.setSelectionRequired(true);
For older versions 👇
There are two steps to achieve this
Step 1
We have this support built-in, just make sure to add app:singleSelection="true" to your ChipGroup, for example:
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>
Code
// Kotlin
group.isSingleSelection = true
// Java
group.setSingleSelection(true);
Step 2
Now to support a radio group like functionality:
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.
(...)
}
From the docs:
ChipGroup also supports a multiple-exclusion scope for a set of chips.
When you set the app:singleSelection attribute, checking one chip that
belongs to a chip group unchecks any previously checked chip within
the same group. The behavior mirrors that of RadioGroup.
A solution would be to preset a clicked chip and then toggling the clickable property of the chips:
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);
}
});
Brief modification of #adriennoir 's answer (in Kotlin). Thanks for the help!
Note that getChildAt() takes an index.
for (i in 0 until group.childCount) {
val chip = group.getChildAt(i)
chip.isClickable = chip.id != group.checkedChipId
}
Here's my larger `setOnCheckedChangeListener, for context:
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)
}
}
}
Most of the answers are great and really helpful for me. Another slight modification to #adriennoir and #Todd DeLand, to prevent unchecking already checked chip in a setSingleSelection(true) ChipGroup, here's my solution:
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
}
For me, I just need to prevent the same checked Chip to be unchecked without making it non-clickable. This way, the user can still click the checked chip and see the fancy ripple effect and nothing will happen.
If singleSelection doesn't work with added dynamically chips, you must generate id for each chip when create them and then add to 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:
This is how I did it:
var previousSelection: Int = default_selection_id
chipGroup.setOnCheckedChangeListener { chipGroup, id ->
if (id == -1) //nothing is selected.
chipGroup.check(previousSelection)
else
previousSelection = id
This is my working solution
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()));
}
}
});
Related
Good morning, community, I have the following case, I have 1 list of questions with their respective YES/NO answers, which are the checkboxes, what is complicating me is how I can apply 1 validation that only allows marking 1 answer (yes or no), in turn save that answer with its respective position and then save it in a DB.
this is my adapter(preusoadapter.kt)
class preusoadapter(
private val context : Context,
private val listpreguntaspreuso: ArrayList<epreguntas>
) : RecyclerView.Adapter<preusoadapter.PreUsoViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PreUsoViewHolder {
val layoutInflater = LayoutInflater.from(context)
return PreUsoViewHolder(layoutInflater.inflate(R.layout.activity_estructura_listapreuso, parent, false)
)
}
override fun onBindViewHolder(holder: PreUsoViewHolder, position: Int) {
val item = listpreguntaspreuso[position]
holder.render(item)
holder.displayChecked(item.answer)
if (position == 2) {
holder.displayAnswers(setOf(Answer.IVSS, Answer.DSS))
} else if (position == 6) {
holder.displayAnswers(setOf(Answer.FRESERV, Answer.FREDMANO))
} else if (position == 14) {
holder.displayAnswers(setOf(Answer.NA, Answer.SI, Answer.NO))
} else if (position == 18) {
holder.displayAnswers(setOf(Answer.NA, Answer.SI, Answer.NO))
} else if (position == 22) {
holder.displayAnswers(setOf(Answer.NA, Answer.SI, Answer.NO))
} else if (position == 25) {
holder.displayAnswers(setOf(Answer.NA, Answer.SI, Answer.NO))
}
}
override fun getItemCount(): Int = listpreguntaspreuso.size
//CLASE INTERNA PREUSOVIEWHOLDER//
inner class PreUsoViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val binding = ActivityEstructuraListapreusoBinding.bind(view)
private val idpregunta = view.findViewById<TextView>(R.id.txtidpregunta)
private val numeropregunta = view.findViewById<TextView>(R.id.txtnumeropregunta)
private val pregunta = view.findViewById<TextView>(R.id.txtpreguntas)
private val imgestado = view.findViewById<ImageView>(R.id.icosemaforo)
fun render (epreguntas: epreguntas){
idpregunta.text = epreguntas.id_pregunta
numeropregunta.text = epreguntas.num_pregunta
pregunta.text = epreguntas.pregunta
Glide.with(imgestado.context).load(epreguntas.icono_estado).into(imgestado)
}
private val checkboxAnswers = mapOf(
binding.chbksi to Answer.SI,
binding.chbkno to Answer.NO,
binding.chbkna to Answer.NA,
binding.chbkIVSS to Answer.IVSS,
binding.chbkDSS to Answer.DSS,
binding.chbkFSERV to Answer.FRESERV,
binding.chbkfmano to Answer.FREDMANO
)
init {
// set the listener on all the checkboxes
checkboxAnswers.keys.forEach { checkbox ->
checkbox.setOnClickListener { handleCheckboxClick(checkbox) }
}
}
// A function that handles all the checkboxes
private fun handleCheckboxClick(checkbox: CheckBox) {
// get the item for the position the VH is displaying
val item = listpreguntaspreuso[adapterPosition]
// update the item's checked state with the Answer associated with this checkbox
// If it's just been -unchecked-, then that means nothing is checked
checkboxAnswers[checkbox]?.let { answer ->
item.answer = if (!checkbox.isChecked) null else answer
// remember to notify the adapter (so it can redisplay and uncheck any other boxes)
notifyItemChanged(adapterPosition)
}
}
fun displayChecked(answer: Answer?) {
// set the checked state for all the boxes, checked if it matches the answer
// and unchecked otherwise.
// Setting every box either way clears any old state from the last displayed item
checkboxAnswers.forEach { (checkbox, answerType) ->
checkbox.isChecked = answerType == answer
}
}
fun displayAnswers(answers: Collection<Answer>) {
// iterate over each checkbox/answer pair, hiding or displaying as appropriate
checkboxAnswers.forEach { (checkbox, answerType) ->
checkbox.visibility = if (answerType in answers) View.VISIBLE else View.GONE
}
}
}
}
and this is my class epreguntas.kt
class epreguntas(
var id_pregunta: String,
var num_pregunta: String,
var pregunta : String,
var icono_estado: String,
var checkvalor: Boolean = false,
var answer: Answer? = null
) {
}
this is my structure i use for my recylcview
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:animateLayoutChanges="true"
app:cardCornerRadius="2dp"
app:cardElevation="4dp"
app:cardUseCompatPadding="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="#+id/icosemaforo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="#drawable/ic_android" />
</RelativeLayout>
<LinearLayout
android:id="#+id/contenedor_categoria1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:orientation="vertical"
>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<TextView
android:id="#+id/txtnumeropregunta"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_alignParentTop="true"
android:text="N°"
android:textSize="14sp"
android:textStyle="bold" />
<TextView
android:id="#+id/txtpreguntas"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="20dp"
android:layout_toStartOf="#+id/contenedorcheck"
android:textSize="14sp"
android:layout_toEndOf="#+id/txtnumeropregunta"
android:textStyle="bold"
android:text="Preguntas" />
<LinearLayout
android:id="#+id/contenedorcheck"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:paddingEnd="20dp"
>
<CheckBox
android:id="#+id/chbksi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:text="#string/check_si" />
<CheckBox
android:id="#+id/chbkIVSS"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:visibility="gone"
android:text="#string/check_IVSS" />
<CheckBox
android:id="#+id/chbkFSERV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:visibility="gone"
android:text="#string/check_freserv" />
<CheckBox
android:id="#+id/chbkno"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:text="#string/check_no" />
<CheckBox
android:id="#+id/chbkfmano"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_marginEnd="10dp"
android:text="#string/check_fredmano" />
<CheckBox
android:id="#+id/chbkDSS"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_marginEnd="10dp"
android:text="#string/check_DSS" />
<CheckBox
android:id="#+id/chbkna"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_marginEnd="10dp"
android:text="#string/check_na" />
</LinearLayout>
</RelativeLayout>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
It complies with selecting 1 box and unchecking the other, but I realize that when I check the box for question 1 (yes) another question overlaps and my question 1 is hidden, and I see that in some questions the YES/NO/ NA up to IVSS and DSS
example
Like Tenfour04 says, RadioButtons in a RadioGroup would handle the "only one thing can be selected" functionality. (You'd need a different listener to handle its button id has been selected callbacks.)
But since you're already storing the checked state and displaying it, you can handle that yourself - you just need a way to ensure when one checkbox is checked, the others are stored as unchecked.
An easy way to do that is to store an Int which represents the checkbox that's selected. Say 0 for the first, 1 for the second, and a negative number for none. Because each number represents one checked item, by changing the number you're "unchecking" the others.
If you want to store that in your data object (like with checkvaloryes) you can just do:
class epreguntas(
var id_pregunta: String,
...
var checkvaloryes: Boolean = false,
var id_answer: Int
)
Then your checkbox click listeners just have to store the appropriate value, and in onBindHolder you enable the selected checkbox and disable all the others.
This is basically what using a RadioGroup involves too - you get a button ID when the selection changes (-1 for no selection), you store that, and when you display it you fetch that stored ID and set it on the RadioGroup.
The automatic deselection of other buttons (in single-selection mode) is nice and convenient, but setting the current button in onBindViewHolder will trigger the OnCheckedChangedListener and you can get stuck in a loop, so you'd need to avoid that. One way is to only update your stored value (and notify the adapter of the change) if the current selection ID is different to the stored one.
But you can also use checkboxes and click listeners like you already are, you just have to handle their state yourself. Here's a bit of a long explanation of one way to do it, but it's partly about solving other problems you're probably going to run into.
I'm gonna complicate things a bit, because I feel like it will help you out once you understand what's happening - you have a few things to wrangle here. I'm just posting this as one way to approach that store and display problem.
First, I think it would be a good idea to define your options somewhere. Since you have a fixed set of checkboxes, there's a fixed set of answers, right? You could define those with an enum:
enum class Answer {
SI, NO, IVSS, FRESERV, FREDMANO, DSS, NA
}
And you could store that in your data:
class epreguntas(
...
var answer: Answer? = null // using null for 'no answer selected'
)
(You can use that enum elsewhere in your app too - it means your answer data is in a useful data structure, and it's not tied to some detail about your list display, like what order the checkboxes happen to be added in the layout. If you need to turn these enum constants into an Int, e.g. for storage, you can use their ordinal property)
Now you can connect those responses to the checkboxes that represent them. We could do that in the ViewHolder class:
class PreUsoViewHolder(view: View) : RecyclerView.ViewHolder(view) {
...
// create an answer type lookup for each checkbox in the ViewHolder instance
// I'm going to use your binding object since you have it!
val checkboxAnswers = mapOf(
binding.chbksi to SI,
binding.chbkno to NO,
...
)
That checkboxAnswers map acts as two things - it's a lookup that links each CheckBox in the layout to a specific answer type, and the keys act as a collection of all your CheckBox views, so you can easily do things to all of them together.
Now you can create a click listener that checks which View was clicked, get the matching Answer, and set it:
// I've made this an -inner- class, and it need to be nested inside your Adapter class
// This gives the ViewHolder access to stuff inside the adapter, i.e. listpreguntaspreuso
inner class PreUsoViewHolder(view: View) : RecyclerView.ViewHolder(view) {
init {
...
// set the listener on all the checkboxes
checkboxAnswers.keys.forEach { checkbox ->
checkbox.setOnClickListener { handleCheckboxClick(checkbox) }
}
// A function that handles all the checkboxes
private fun handleCheckboxClick(checkbox: CheckBox) {
// get the item for the position the VH is displaying
val item = listpreguntaspreuso[adapterPosition]
// update the item's checked state with the Answer associated with this checkbox
// If it's just been -unchecked-, then that means nothing is checked
checkboxAnswers[checkbox]?.let { answer ->
item.answer = if (!checkbox.isChecked) null else answer
// remember to notify the adapter (so it can redisplay and uncheck any other boxes)
notifyItemChanged(adapterPosition)
}
}
This relies on you using a click listener, not a checkedChanged listener, because setting checked state in onBindViewHolder (when you're clearing checkboxes) will trigger that checkedChanged listener. A click listener only fires when the user is the one checking or unchecking a box.
So now you have a click listener that sets the appropriate Answer value for an item. To display it, we could put another function in the ViewHolder:
fun displayChecked(answer: Answer?) {
// set the checked state for all the boxes, checked if it matches the answer
// and unchecked otherwise.
// Setting every box either way clears any old state from the last displayed item
checkboxAnswers.forEach { (checkbox, answerType) ->
checkbox.isChecked = answerType == answer
}
}
And now you can call that from onBindViewHolder:
override fun onBindViewHolder(holder: PreUsoViewHolder, position: Int) {
val item = listpreguntaspreuso[position]
holder.render(item)
holder.displayChecked(item.answer)
The other reason for doing things this way, is I think your code to make stuff visible/invisible is broken:
else if (position == 14){
holder.itemView.chbkna.visibility = View.VISIBLE
}
This kind of thing won't work - all you're doing is saying "for item 14, make this box visible" - it says nothing about which of the other boxes should be visible, and which should be hidden. You'll have stuff randomly shown or hidden depending on which item happened to be displayed in that ViewHolder before. You need to explicitly say what should be displayed, every time onBindViewHolder runs.
You can do that with a similar function to the displayChecked one we just wrote:
inner class PreUsoViewHolder(view: View) : RecyclerView.ViewHolder(view) {
...
// provide a list of the answers that should be shown
fun displayAnswers(answers: Collection<Answer>) {
// iterate over each checkbox/answer pair, hiding or displaying as appropriate
checkboxAnswers.forEach { (checkbox, answerType) ->
checkbox.visibility = if (answerType in answers) VISIBLE else GONE
}
}
Now you can easily update your displayed boxes in onBindViewHolder:
else if (position == 14){
holder.displayAnswers(setOf(NA, SI, NO))
}
And even better, you could create groups of answer types associated with the question in the data. So instead of hardcoding by position, you can just pull the required types out of the item itself
class epreguntas(
...
answerTypes: Set<Answer>
// onBindViewHolder
holder.displayAnswers(item.answerTypes)
You could even create preset lists for different types of question, like val yesNo = setOf(SI, NO) (or an enum) and reuse those when defining your questions - there are probably only a few combos you're using anyway!
I hope that wasn't too complicated, and the organisation ideas (and the benefits they can give you) make sense. A RadioGroup is simpler, but with the other stuff you'll probably have to deal with, I feel like this is a useful general approach
TL;DR - What is the correct way to programatically select a default choice Chip that is a child of a ChipGroup in Android?
--
In my android app, I am using com.google.android.material.chip.Chip styled as #style/Widget.MaterialComponents.Chip.Choice components to represent choices of activities a user can select for a given route (think walk, bike, etc)
Because a route can have different types of activities, I insert each type as a different chip programatically into a com.google.android.material.chip.ChipGroup. I also select the default chip as being the first one inserted in the list using the following code during onViewCreated() of my fragment
private fun setupTypeSelection(types: List<Type>) {
types.forEach { type ->
val chip = layoutInflater.inflate(R.layout.chip_type, viewBinding.typeChipGroup, false) as Chip
chip.tag = type
/* Init chip text and icon */
chip.setOnClickListener {
/* Update selected type */
}
if (currentType == null) {
chip.isSelected = true
currentType = type
}
viewBinding.typeChipGroup.addView(chip)
}
}
Here's the layout definition of the ChipGroup, where a I set single selection, etc
chip_group_layout.xml
<com.google.android.material.chip.ChipGroup
android:id="#+id/type_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/margin_medium"
app:chipSpacingHorizontal="#dimen/margin_medium"
app:selectionRequired="true"
app:singleLine="true"
app:singleSelection="true" />
And here is the chip layout
chip_type.xml
<com.google.android.material.chip.Chip xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="#style/Widget.MaterialComponents.Chip.Choice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipIconEnabled="true" />
The problem I'm facing is that the chip that was set programatically as selected chip.isSelected = true stays selected even after the user selects a different one through UI interaction.
What is the correct way to programatically select a default choice Chip that is a child of a ChipGroup in Android?
Found my answer.
Use View.generateViewId() and assign the new Id to the
newly create chip. Then, add
Add chip to its parent ChipGroup
Check chip view ChipGroup using viewBinding.typeChipGroup.check(id)
This is the final code:
private fun setupTypeSelection(types: List<Trail.Type>) {
types.forEach { type ->
val chip = layoutInflater.inflate(R.layout.chip_trail_type, viewBinding.typeContainer, false) as Chip
chip.id = View.generateViewId()
/* Set chip details as usual */
viewBinding.typeContainer.addView(chip)
if (currentType == null) viewBinding.typeChipGroup.check(chip.id)
}
}
currentType == null condition will be false for next selection. Are you making it null anywhere else?
Use ChipGroup's setOnCheckedChangeListener to check the state of chip selection instead of chip.setOnClickListener
val chipGroup = findViewById<ChipGroup>(R.id.chip_group)
val tileSize320 = findViewById<Chip>(R.id.tile_size320)
chipGroup.check(tileSize320)
This question already has answers here:
ChipGroup single selection
(8 answers)
Closed 3 years ago.
This is my code for setting up the chipgroup and chips inside based on list:
val chipGroup = region_list
val inflator = LayoutInflater.from(chipGroup.context)
val children = categoryList.map { categoryName ->
val chip = inflator.inflate(R.layout.region, chipGroup, false) as Chip
chip.text = categoryName.replace('_', ' ')
chip.tag = categoryName
chip.setOnCheckedChangeListener { button, isChecked ->
val s = viewModel.updateFilter(button.tag as String, isChecked)
//s is a string = {"change", "keep"}
//if s is keep that means that the same chip that was selected was pressed again, and in normal chip behaviour in android that chip would then be deselected, I don't want that
}
}
s is a string = {"change", "keep"}
if s is keep that means that the same chip that was selected was pressed again, and in normal chip behaviour in android that chip would then be deselected, I don't want that. I can't believe that android team never thought that someone would need a chipgroup where an item can't be deselected, what is the point of chips, to use it as some kind of filter to update view bellow.
Try this logic:
Your Activity:
int i=0;
String chipArr[]={"First","Second","Third","Fourth"};
int selectedChipId=0;
for (final String chipName : chipArr) {
Chip chip = (Chip) layoutInflater.inflate(R.layout.cat_chip_group_item_filter, chipGroup, false);
chip.setText(chipName);
chip.setId(i);
}
for (final String chipName : fileNameChip) {
LayoutInflater layoutInflater = getLayoutInflater();
Chip chip = (Chip) layoutInflater.inflate(R.layout.cat_chip_group_item_filter, chipGroup, false);
chip.setText(chipName);
chip.setId(i);
if (i == 0) chip.setSelected(true);
chip.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
selectedChipId=v.getId();
}
});
chipGroup.addView(chip);
i++;
}
chipGroup.setOnCheckedChangeListener(new ChipGroup.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(ChipGroup group, int checkedId) {
Log.d(TAG, "onCheckedChanged: " + checkedId);
if (checkedId == selectedChipId) {
chip.setSelected(true);
}
}
});
Activity layout:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".activity.ShowCodeActivity">
<HorizontalScrollView
android:id="#+id/horScrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:scrollbars="none">
<com.google.android.material.chip.ChipGroup
android:id="#+id/chip_group_code_files"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="#dimen/margin_4dp"
android:layout_marginEnd="#dimen/margin_4dp"
app:singleLine="true"
app:singleSelection="true" />
</HorizontalScrollView>
</RelativeLayout>
Your cat_chip_group_item_filter.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"
style="#style/Widget.MaterialComponents.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
Let me know if it worked for you or not as I haven't tested it!!
First of all, if anything can be better in my question, please let me know, it's my first one here.
I am building an app for android.
I want my users to be able to enter a time in an edittext field quickly. There usually already is a time in there, which should be selected so any input from keyboard will result in it being overwritten, while just hitting "next" (or enter on keyboard" will leave it the way it is and jump to the next field.
For all my fields this works nicely, but for some reason I cannot grasp, the text in this specific field (and its twin) won't get selected no matter what I tried.
I putandroid:selectAllOnFocus="true" in the res file, and when that didn't work I also put v.selectAll() in its onFocusChangedListener.
flighttOutStringField?.onFocusChangeListener = View.OnFocusChangeListener { v, hasFocus ->
if (hasFocus) {
with(v as TextInputEditText) {
previousText = this.text.toString()
v.selectAll()
}
} else {
with(v as TextInputEditText) {
val currentText = this.text.toString()
if (!(("([01]\\d|2[0-3]):[0-5]\\d".toRegex().containsMatchIn(currentText) && currentText.length == 5)
|| ("([01]\\d|2[0-3])[0-5]\\d".toRegex().containsMatchIn(
currentText.padStart(
4,
'0'
)
) && currentText.length <= 4))
|| (currentText == "")
)
this.setText(previousText) // not a valid new entry, so no update on flight
else {
this.setText(
if (currentText.length < 5) "${currentText.padStart(
4,
'0')
.slice(0..1)}:${currentText.padStart(4, '0').slice(2..3)}" else currentText)
flight = flight.copy (tOutString = "${date?.format(DateTimeFormatter.ISO_DATE)}T${this.text}:00", changed = 1)
}
}
}
updating the "flight" var will refill all fields through its setter.
<android.support.design.widget.TextInputLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="#+id/flighttOutStringWrapper"
app:layout_constraintStart_toEndOf="#+id/flighttOutSelector" android:layout_marginTop="4dp"
app:layout_constraintTop_toBottomOf="#+id/flightOrigWrapper"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintEnd_toStartOf="#+id/flighttInStringWrapper">
<android.support.design.widget.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="time"
android:ems="10"
android:id="#+id/flighttOutStringField"
android:textAlignment="center"
android:includeFontPadding="false"
android:hint="#string/timeOut"
android:nextFocusForward="#id/flighttInStringField" android:imeOptions="actionNext"
android:selectAllOnFocus="true"/>
</android.support.design.widget.TextInputLayout>
I would think that upon selecting the TextInputEditText, all text would be selected, but it isn't.
Then, I thought the v.selectAll() would do that for me, but it also doesn't.
Try this
v.setOnFocusChangeListener(null)
v.clearFocus();
v.requestFocus();
v.selectAll()
v.setOnFocusChangeListener(listener)
Is there an option to make the MaterialButtonToggleGroup have a required selected button when using app:singleSelection="true"?
When clicking to a different button works fine (all other buttons are deselected), but when you click the same (already selected) button it deselects it itself and I want to remain selected.
My example:
<com.google.android.material.button.MaterialButtonToggleGroup
android:layout_width="wrap"
android:layout_height="wrap_content"
app:singleSelection="true">
<com.google.android.material.button.MaterialButton
android:id="#+id/filterA"
style="#style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="A"/>
<com.google.android.material.button.MaterialButton
android:id="#+id/filterB"
style="#style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="B"/>
<com.google.android.material.button.MaterialButton
android:id="#+id/filterC"
style="#style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="C"/>
</com.google.android.material.button.MaterialButtonToggleGroup>
You can define it in the layout using the app:selectionRequired attribute:
<com.google.android.material.button.MaterialButtonToggleGroup
app:selectionRequired="true"
app:checkedButton="#id/..."
app:singleSelection="true">
You can also use the method setSelectionRequired:
buttonGroup.setSelectionRequired(true);
Note: This requires a minimum of version 1.2.0-alpha03
I got this working with the following:
toggle_group.addOnButtonCheckedListener { group, checkedId, isChecked ->
if (group.checkedButtonId == -1) group.check(checkedId)
}
If you have singleSelection enabled, the conditional will only evaluate to true when the user has clicked on the button which is already checked, making it so no button is checked. When this happens, we just need to check the button they unchecked.
I also came across this issue and I found this is working with the app:singleSelection="true"
String selectedValue = "Male";
genderBtnToggle.addOnButtonCheckedListener(new MaterialButtonToggleGroup.OnButtonCheckedListener() {
#Override
public void onButtonChecked(MaterialButtonToggleGroup group, int checkedId, boolean isChecked) {
MaterialButton btn = genderBtnToggle.findViewById(checkedId);
if (!isChecked && btn.getText().toString().equals(selectedValue)) {
genderBtnToggle.check(checkedId);
}
if (isChecked) {
selectedValue = btn.getText().toString();
}
}
});
I also came across this issue and will be waiting for a permanent fix from google. In the meantime, I did the following to make sure that at least one button is checked.
final MaterialButtonToggleGroup tGroup = view.findViewById(R.id.toggleGroup);
final MaterialButton breast = tGroup.findViewById(R.id.breast);
final MaterialButton bottle = tGroup.findViewById(R.id.bottle);
final MaterialButton solids = tGroup.findViewById(R.id.solids);
View.OnClickListener onClickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
tGroup.check(v.getId());
}
};
breast.setOnClickListener(onClickListener);
bottle.setOnClickListener(onClickListener);
solids.setOnClickListener(onClickListener);
Hope this helps.