How can I keep an item, in an MvxListView, highlighted until it is unselected or until another item is selected?
My program has an MvxListView that correctly displays a list of items. The user can select an item, by clicking it, and then click a save button. The selected item is stored in MyChosenItem until it is needed by the save button code. Currently, the selected item stays highlights for a split second before returning to the unselected color.
This is how the MvxListView is created:
<Mvx.MvxListView
android:layout_width="match_parent"
android:layout_height="260dp"
android:layout_marginTop="40dp"
android:id="#+id/MyMvxListViewControl"
local:MvxBind="ItemsSource MyItems; SelectedItem MyChosenItem"
local:MvxItemTemplate="#layout/my_item_layout" />
This is Layout/my_item_layout.xaml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res/Project.Ui.Droid"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="300.0dp"
android:layout_height="wrap_content"
android:padding="5dp"
android:textSize="20dp"
android:textColor="#000000"
local:MvxBind="Text Field1" />
<TextView
android:layout_width="250.0dp"
android:layout_height="wrap_content"
android:padding="5dp"
android:textSize="20dp"
android:textColor="#000000"
local:MvxBind="Text Field2" />
</LinearLayout>
This method provides an easy way to customize which items remain highlighted. I settled on this because it gives me full control over what is highlighted and how it is displayed in the list. (This example shows highlighting only one item, but it could easily be extended to highlight more.)
The MvxListView, in the original question, links to MyItems and MyChosenItem in the associated view model. MyItems is a collection of Item, and MyChosenItem is just a single Item. I added isItemSelected to Item. The Item class looks like this now:
public class Item : MvxViewModel
{
private string _field1;
private string _field2;
private bool _isItemSelected = false;
public string Field1
{
get { return _field1; }
set
{
_field1= value;
RaisePropertyChanged("Field1");
}
}
public string Field2
{
get { return _field2; }
set
{
_field2= value;
RaisePropertyChanged("Field2");
}
}
public bool isItemSelected
{
get { return _isItemSelected; }
set
{
_isItemSelected = value;
RaisePropertyChanged("isItemSelected");
}
}
}
Note: The Item class extends MvxViewModel so that RaisePropertyChange() can be called. This allows my_item_layout.xaml to be notified when that property changes.
Update each instance of isItemSelected from the property that the MvxListView's SelectedItem binds to. In this case, that's the MyChosenItem property in the associated view model. This is what the new code looks like:
public Item MyChosenItem
{
get { return _myChosenItem; }
set
{
if (_myChosenItem != value)
{
_myChosenItem = value;
UpdateItemSelections();
RaisePropertyChanged("MyChosenItem");
}
}
}
// Select MyChosenItem and unselect all other items
private void UpdateItemSelections()
{
if( MyItems.Count > 0)
{
for (int index = 0; index < MyItems.Count; index++)
{
// If the chosen item is the same, mark it as selected
if (MyItems[index].Field1.Equals(MyChosenItem.Field1)
&& MyItems[index].Field2.Equals(MyChosenItem.Field2))
{
MyItems[index].isItemSelected = true;
}
else
{
// Only trigger the property changed event if it needs to change
if (MyItems[index].isItemSelected)
{
MyItems[index].isItemSelected = false;
}
}
}
}
}
It would be pretty easy to modify UpdateItemSelections() to any selection behavior you want.
Make each row do something based on the isItemSelected property. I just made the background change colors by controlling the visibility property of a view. However, all sorts of things are possible. isItemSelected could even be passed to a custom control for some really interesting visuals. My new Layout/my_item_layout.xaml looks like this:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res/Project.Ui.Droid"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<!-- SELECTED BACKGROUND COLOR -->
<View
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#FF0000"
local:MvxBind="Visibility isItemSelected,Converter=BoolToViewStates" />
<TextView
android:layout_width="300.0dp"
android:layout_height="wrap_content"
android:padding="5dp"
android:textSize="20dp"
android:textColor="#000000"
local:MvxBind="Text Field1" />
<TextView
android:layout_width="250.0dp"
android:layout_height="wrap_content"
android:padding="5dp"
android:textSize="20dp"
android:textColor="#000000"
local:MvxBind="Text Field2" />
</LinearLayout>
EDIT
It might be better to use an MvxCommand instead of triggering the highlighted action when the SelectedItem is set. It seems the SelectedItem is only set if it is not already selected. Tapping an item will select it. Tapping another item will change the selection. Tapping the same item again will not unselect it. This means that once an item is selected, one item must remain selected. If you need the ability to unselect all the items in the list, follow these modifications to the original instructions:
Add an MvxCommand to the view model. Call UpdateItemSelections() from the MvxCommand instead of from MyChosenItem.
public MvxCommand ItemSelectedCommand { get; private set; }
// Constructor
public ItemSelectionViewModel()
{
ItemSelectedCommand = new MvxCommand(OnItemSelected);
}
public Item MyChosenItem
{
get { return _myChosenItem; }
set
{
if (_myChosenItem != value)
{
_myChosenItem = value;
//UpdateItemSelections(); // Move this to OnItemSelected()
RaisePropertyChanged("MyChosenItem");
}
}
}
private void OnItemSelected()
{
UpdateItemSelections();
}
Change UpdateItemSelections() to toggle the isItemSelected property instead of always setting it to true:
// Select MyChosenItem and unselect all other items
private void UpdateItemSelections()
{
if( MyItems.Count > 0)
{
for (int index = 0; index < MyItems.Count; index++)
{
// If the chosen item is the same, mark it as selected
if (MyItems[index].Field1.Equals(MyChosenItem.Field1)
&& MyItems[index].Field2.Equals(MyChosenItem.Field2))
{
// Toggle selected status
MyItems[index].isItemSelected = !MyItems[index].isItemSelected;
}
else
{
// Only trigger the property changed event if it needs to change
if (MyItems[index].isItemSelected)
{
MyItems[index].isItemSelected = false;
}
}
}
}
}
Remember to check MyChosenItem.isItemSelected == true when saving or doing anything that acts on the selected item in the list. There might be a value in MyChosenItem that is unselected in the list view the user sees.
Bind the MvxCommand to ItemClick in layout definition of the MvxListView:
<Mvx.MvxListView
android:layout_width="match_parent"
android:layout_height="260dp"
android:layout_marginTop="40dp"
android:id="#+id/MyMvxListViewControl"
local:MvxBind="ItemsSource MyItems; SelectedItem MyChosenItem; ItemClick ItemSelectedCommand"
local:MvxItemTemplate="#layout/my_item_layout" />
Related
enter image description hereI am trying to get ListView returning to the previously selected item. I have a screen split in two parts, where on the left part is a ListView and on the right part is TextView, showing selected text.
Use case: an item is selected and with Dpad right arrow is made move to the TextView, then back with Dpad left arrow to the ListView, move several items down with Dpad arrow down. Then again with Dpad right arrow move to the TextView and left arrow back. The problem is that a different item is selected, not the last one selected, which is not intuitive, moreover it is randomly selected position instead of the position of the last selected item.
Here is listing of my test ListView:
public class MainActivity extends AppCompatActivity {
private static final String LOG_TAG = MainActivity.class.getSimpleName();
private TextView m_program_description;
ListView listView;
ArrayList<String> arrayList = new ArrayList<>(Arrays.asList("C-Language", "Java", "Data Structure",
"Networking", "Operating System", "Compiler Design", "Theory Of Computation",
"Software Engineering", "Web Engineering"));
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.listView);
// simple_list_item_1 is a built in layout. It is part of Android OS, instead of creating our own
// xml layout we are using built-in layout
ArrayAdapter arrayAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, arrayList);
listView.setAdapter(arrayAdapter);
listView.setOnItemClickListener((adapterView, view, i, l) -> {
view.setSelected(true);
view.setActivated(true);
});
listView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener()
{
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
{
Log.e(LOG_TAG, "onItemSelected() called");
m_program_description = findViewById(R.id.program_description);
m_program_description.setMovementMethod(new ScrollingMovementMethod());
m_program_description.setText(arrayList.get(position));
}
#Override
public void onNothingSelected(AdapterView<?> parent)
{
}
});
}
}
and layout :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:weightSum="2">
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical">
<!--ListView to store list items-->
<ListView
android:id="#+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="#+id/program_description"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginTop="10dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="25dp"
android:focusable="true"
android:clickable="true"
android:foreground="?attr/selectableItemBackground"
android:scrollbars="vertical"
android:fadeScrollbars="false"
android:textSize="16sp"
android:gravity="start" >
</TextView>
</LinearLayout>
</LinearLayout>
I appreciate your help.
By behaviour observation (i.e.,without drilling the ListView source code from AOSP website),
I believe you shall store the selected position in an instance variable and then reselect the item in ListView if it is not focus then focus again.
In order to store the selected position:
int mItemSelected = -1; // new instance variable
...
listView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener()
{
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
{
mItemSelected = position; // store position to mItemSelected
Log.e(LOG_TAG, "onItemSelected() called position=" + position);
TextView m_program_description = findViewById(R.id.program_description);
m_program_description.setMovementMethod(new ScrollingMovementMethod());
m_program_description.setText(arrayList.get(position));
}
#Override
public void onNothingSelected(AdapterView<?> parent)
{
}
});
In order to re-select the item when focus (With hack, will be explained after the code sample):
listView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View view, boolean isFocus) {
Log.d(LOG_TAG, "onFocusChange: isFocus=" + isFocus + ", mItemSelected=" + mItemSelected);
if (isFocus && mItemSelected != -1) {
TimerTask timerTask = new TimerTask() {
#Override
public void run() {
${YOUR_ACTIVITY}.this.runOnUiThread(() -> {
listView.setItemChecked(mItemSelected, true);
listView.setSelection(mItemSelected);
listView.getChildAt(mItemSelected).requestFocus();
});
}
};
Timer timer = new Timer();
timer.schedule(timerTask, 0);
}
}
});
The related code shall be the three lines using mItemSelected as argument. Maybe any of them will work and I am just too lazy to verify it.
The concern here is not about the implementation of selection, it is more about the life cycle of ListView when focus has changed.
Originally, I hadn't use a Timer to run the code. Same issue was met then. I guess there is a life cycle event which will set the position incorrectly.
Flow
Focus ListView
Move to item position X
Trigger ListView#setOnItemSelectedListener
Set mItemSelected to X
Press DPad to move to TextView
Press DPad to move back to ListView
Trigger ListView#setOnFocusChangeListener
ListView select item X
Unknown life cycle event moves the item unintentionally
So after adding a Timer, step 9 and 8 will be swapped and therefore the result will be expected. This is the hacking done here and you can see I even didn't add a delay to the Timer.
If anyone can provide WHY step 9 occurs, please also let me know.
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
I think I have not made myself clear on what I´m trying to achieve.
I have a listView:
<ListView
android:id="#+id/list1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:choiceMode="singleChoice"
android:focusable="true"
android:focusableInTouchMode="true"
android:listSelector="#android:color/darker_gray"
android:textSize="10dp"
</ListView>
As you can see, this list view includes a listSelector that highlights the item selected on the list once clicked.
Now, Im trying to program a button to display a toast message when this list1 has no selector on it, (when no item in there has been selected and highlighted in gray) and to set the visibility of a website layout to visible when it does.
something like:
button1.setOnClickListener {
If (list1.selector == null){
toast("no item selected in list1")
} else {
website.visibility = View.VISIBLE
}
}
For reference here are my codes:
val nameofanimals = arrayOf("cat","dog","parrot")
internal lateinit var adapteranimals: ArrayAdapter<String>
my adapter:
adapteranimals = ArrayAdapter(
this#MainActivity,
R.layout.list1layout,
nameofanimals)
list1.adapter = adapteranimals
thank you in advance for your support
Regards
I would create an Extension:
fun ListView.hasSelector(): Boolean {
for (i in 0 until this.count) {
if (this.isItemChecked(i))
return true
}
return false
}
Then call it in the clickEvent:
button1.setOnClickListener {
If (!list1.hasSelector){
toast("no item selected in list1")
} else {
website.visibility = View.VISIBLE
}
}
I have a Xamarin Android application that uses a Picker to select from a list of values. I have been changing the style of the application, but run into problems with the Picker.
Although I can set the TextColor, I could not set the colour of the placeholder text.
After searching SO for help, I've implemented a custom renderer, and I now have the text and placeholder showing in the correct text. However, previously when I touched the placeholder text, the child dialog appeared and displayed all the items, allowing the user to select one. Now I have the custom renderer implemented, the child dialog only shows the top two items, and the user has to scroll through them before hitting OK.
I have two questions:
At a minimum, how can I get the child dialog displaying the full list again?
Is it possible to set the background and text colour for the items list dialog?
The XAML looks like this:
<c:CustomPicker x:Name="DivisionList" Title="{x:Static prop:Resources.PickerDivision}"
SelectedIndexChanged="DivisionList_SelectedIndexChanged">
<Picker.Behaviors>
<b:RequiredPickerValidator x:Name="DivValidator" IsValid="{Binding Path=BindingContext.IsDivisionValid, Mode=OneWayToSource, Source={x:Reference contentPage}}" />
</Picker.Behaviors>
</c:CustomPicker>
The CustomPicker class is as follows:
namespace <myapp>.Portable.Controls
{
public class CustomPicker : Picker
{
public Color PlaceholderColour
{
get { return (Color)App.Current.Resources["PlaceholderTextColour"]; }
}
public Color TextColour
{
get { return (Color)App.Current.Resources["LabelTextColour"]; }
}
public Color BackgroundColour
{
get { return (Color)App.Current.Resources["PaneBackgroundColour"]; }
}
}
}
And the customer renderer is this:
[assembly: ExportRendererAttribute(typeof(CustomPicker), typeof(CustomPickerRenderer))]
namespace <myapp>.Droid.Controls.Renderers
{
public class CustomPickerRenderer : PickerRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
Control?.SetPadding(20, 20, 20, 20);
if (e.OldElement != null || e.NewElement != null)
{
var customPicker = e.NewElement as CustomPicker;
Android.Graphics.Color phCol = customPicker.PlaceholderColour.ToAndroid();
Android.Graphics.Color textCol = customPicker.TextColour.ToAndroid();
Android.Graphics.Color bgCol = customPicker.BackgroundColour.ToAndroid();
Control.SetBackgroundColor(bgCol);
Control.SetHintTextColor(phCol);
Control.SetTextColor(textCol);
}
}
}
}
Many thanks in advance!
Picker popup before custom renderer:
Picker popup after custom renderer:
There appear to be 2 picker renderers.
Xamarin.Forms.Platform.Android.PickerRenderer &
Xamarin.Forms.Platform.Android.AppCompat.PickerRenderer
Make sure you use the last one and your layout will be the same as before!!!
The source where i got my answer:
https://forums.xamarin.com/discussion/97150/how-to-write-a-custom-renderer-for-bindable-picker-to-change-its-font-attributes-family-size
Renderers (no real indication there are 2):
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/renderers
You can add Click event on Control, and then you can custom a dialog, in this dialog, you can custom anything what you want, background, item text color, etc.
In your CustomPickerRenderer, do like this:
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
Control?.SetPadding(20, 20, 20, 20);
if (e.OldElement != null || e.NewElement != null)
{
var customPicker = e.NewElement as CustomPicker;
...
// add click event
Control.Click += Control_Click;
}
}
You can show a Dialog, and custom it's layout, for example, add a ListView in its layout, so you will get the child dialog displaying the full list:
private void Control_Click(object sender, EventArgs e)
{
Toast.MakeText(Xamarin.Forms.Forms.Context,"111111",ToastLength.Short).Show();
dialog = new Dialog(Forms.Context);
dialog.SetContentView(Resource.Layout.dialog);
Android.Widget.Button b1 = (Android.Widget.Button)dialog.FindViewById(Resource.Id.button1);
Android.Widget.Button b2 = (Android.Widget.Button)dialog.FindViewById(Resource.Id.button2);
Android.Widget.ListView listView = (Android.Widget.ListView)dialog.FindViewById(Resource.Id.lv);
listView.Adapter=new ArrayAdapter<String>(Forms.Context, Android.Resource.Layout.SimpleExpandableListItem1,
new List<String>() { "111","222","333", "444", "555", "666", "777", "888", "999", });
b1.Click += B1_Click;
b2.Click += B2_Click;
dialog.Show();
}
Below is dialog layout, you can set the background and text color for the items list dialog:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:text="123456"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<ListView
android:id="#+id/lv"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="#+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Cancel" />
<Button
android:id="#+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ok" />
</LinearLayout>
</LinearLayout>
To remove the lines and revert back to the default Picker View without modifying your custom renderer just extend this class
Xamarin.Forms.Platform.Android.AppCompat.PickerRenderer
It should look a little like this;
public class CustomPickerRenderer : Xamarin.Forms.Platform.Android.AppCompat.PickerRenderer
{
[logic]
}
instead of
public class CustomPickerRenderer : PickerRenderer
{
[logic]
}
It seems by default you extend Xamarin.Forms.Platform.Android and Class seems to be adding the different picker view.
I have a quiz app, and every question has 4 answers, 2 of them are the correct ones. I use CheckBox for the answers. What I want is when the user clicks on 2 of the answers, then the remaining 2 to get unchecked. With other words, only 2 checked CheckBox at the time, and if the user has 2 checked and checks third one, then one of the two checked to get unchecked. I write a code that it is "half-working". When I check CheckBox starting from the first to the last is working, but clicking from the last to the first, nothing happens...
public void checkBoxClicked(View view) {
switch (view.getId()) {
case R.id.checkBoxOne:
case R.id.checkBoxTwo:
case R.id.checkBoxThree:
case R.id.checkBoxFour:
if (checkBoxOne.isChecked() && checkBoxTwo.isChecked()) {
checkBoxThree.setEnabled(false);
checkBoxFour.setEnabled(false);
}else {
checkBoxThree.setEnabled(true);
checkBoxFour.setEnabled(true);
}
if (checkBoxOne.isChecked() && checkBoxThree.isChecked()) {
checkBoxTwo.setEnabled(false);
checkBoxFour.setEnabled(false);
}else {
checkBoxTwo.setEnabled(true);
checkBoxFour.setEnabled(true);
}
if (checkBoxOne.isChecked() && checkBoxFour.isChecked()) {
checkBoxTwo.setEnabled(false);
checkBoxThree.setEnabled(false);
}else {
checkBoxTwo.setEnabled(true);
checkBoxThree.setEnabled(true);
}
if (checkBoxTwo.isChecked() && checkBoxThree.isChecked()) {
checkBoxOne.setEnabled(false);
checkBoxFour.setEnabled(false);
}else {
checkBoxOne.setEnabled(true);
checkBoxFour.setEnabled(true);
}
if (checkBoxTwo.isChecked() && checkBoxFour.isChecked()) {
checkBoxOne.setEnabled(false);
checkBoxThree.setEnabled(false);
}else {
checkBoxOne.setEnabled(true);
checkBoxThree.setEnabled(true);
}
if (checkBoxThree.isChecked() && checkBoxFour.isChecked()) {
checkBoxOne.setEnabled(false);
checkBoxTwo.setEnabled(false);
}else {
checkBoxOne.setEnabled(true);
checkBoxTwo.setEnabled(true);
}
break;
I would try tracking the boxes that are checked using an array, (a queue would work perfect for this). When the user clicks on a checkbox, check if your array has two entries. If it has less than 2 entries, add the new checkbox to the list. If it has two entries, remove the first checkbox from the list, and add the new checkbox.
After your tracking code, you can simply uncheck ALL of the check boxes and only check those that you want.
Here I use an ArrayList for simplicity, but even a simple array would work. You could also use the id's instead of the actual views.
***Update Very Simple Working example.
Activity
public class TestActivity extends AppCompatActivity implements View.OnClickListener{
private ArrayList<CheckBox> mChecks;
private ArrayList<CheckBox> mSelectedChecks;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test_layout);
mChecks = new ArrayList<CheckBox>();
mSelectedChecks = new ArrayList<CheckBox>();
//Load All checkboxes
CheckBox check1 = (CheckBox)findViewById(R.id.check1);
CheckBox check2 = (CheckBox)findViewById(R.id.check2);
CheckBox check3 = (CheckBox)findViewById(R.id.check3);
CheckBox check4 = (CheckBox)findViewById(R.id.check4);
//Add to tracking list
mChecks.add(check1);
mChecks.add(check2);
mChecks.add(check3);
mChecks.add(check4);
//Add Click listener
for(CheckBox c : mChecks) {
c.setOnClickListener(this);
}
}
#Override
public void onClick(View view) {
CheckBox c = (CheckBox)view;
if(mSelectedChecks.contains(c)) {
mSelectedChecks.remove(c);
} else {
if(mSelectedChecks.size() < 2) {
mSelectedChecks.add(c);
} else {
mSelectedChecks.remove(0);
mSelectedChecks.add(c);
}
}
drawResults();
}
public void drawResults() {
for(CheckBox c : mChecks) {
c.setChecked(mSelectedChecks.contains(c));
}
}
}
Layout file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<CheckBox
android:text="Check 1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/check1"/>
<CheckBox
android:text="Check 2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/check2"/>
<CheckBox
android:text="Check 3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/check3"/>
<CheckBox
android:text="Check 4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/check4"/>
</LinearLayout>