0

I am using recyclerView to build a list of toggle buttons (like a numpad), I would like to achieve:

  • when user 'turns on' a button by clicking it, 'turns off' all other buttons

I have tried to add an onClickListener inside the view holder class, and call notifyDataSetChanged() such that onBindViewHolder(final ViewHolder holder, int position) is called and then change the state of the button.

But it doesn't work, the button clicked does not change its state.

ViewHolder

class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{

    ToggleButton button;
    String id;

    public ViewHolder(View itemView) {
        super(itemView);
        button = (ToggleButton) itemView.findViewById(R.id.toggle);
        button.setOnclickListener(this);
    }

    @Override
    public void onClick(View view) {
        ((ToggleButton) view).setChecked(true);
        int pos = getAdapterPosition();
        if (pos != RecyclerView.NO_POSITION) {
            selected = pos;  //store the position of the user clicked button
            notifyDataSetChanged();
        }
    }

onBindViewHolder() in my adpater class

public void onBindViewHolder(final ViewHolder holder, int position) {
    String data = mData.get(position);
    holder.id = data;
    holder.button.setText(data);
    holder.button.setTextOn(data);
    holder.button.setTextOff(data);

    if (position != selected) { //if the button is not the user chosen one
        if (holder.button.isChecked()) { // and it's in 'ON' state
            holder.button.toggle(); // toggle it to switch 'OFF'
        }
    }
mike
  • 373
  • 3
  • 14
  • so do you need to switch on a button at a time. is it? – darwin Jun 13 '17 at 12:14
  • avoid using notifyDataSetChanged(), instead use notifyItemChanged(pos) if possible, I think notifyItemChanged(int pos) is suitable for ur requirement,bcz a single item is in active state at a time. – darwin Jun 13 '17 at 12:18

3 Answers3

2

There are a couple of things you are doing incorrectly. The ViewHolder should contain only views and not click handlers or strings etc. (The clue is in the name). You add the handlers and manipulate the views when you bind the ViewHolder to your data class.

From what I can determine, you want a simple list of toggle buttons. When one button is on, the others should turn off. The test adapter I created for you below demonstrates that.

Ideally, you would avoid notifyDataSetChanged and use the row based versions but as each time the toggle is pressed, it affects every other row, you do not really have a choice in this use case unless you keep track of the selected row.

public class TestAdapter extends RecyclerView.Adapter<TestAdapter.VH> {

public static class MyData {
    public boolean Selected = false;
    public String Text;

    public MyData(String text) {
        Text = text;
    }
}

public List<MyData> items = new ArrayList<>();

public TestAdapter() {
    this.items.add(new MyData("Item 1"));
    this.items.add(new MyData("Item 2"));
    this.items.add(new MyData("Item 3"));
}

@Override
public TestAdapter.VH onCreateViewHolder(ViewGroup parent, int viewType) {
    return new VH((
            LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.test_layout, parent, false))
    );
}

@Override
public void onBindViewHolder(TestAdapter.VH holder, int position) {
    final MyData itm = items.get(position);

    holder.button.setChecked(itm.Selected);
    holder.text.setText(itm.Text);

    holder.button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            for (MyData x: items){
                x.Selected=false;
            }
            itm.Selected = true;
            notifyDataSetChanged();
        }
    });

}

@Override
public int getItemCount() {
    return items.size();
}

public class VH extends RecyclerView.ViewHolder {

    ToggleButton button;
    TextView text;

    public VH(View itemView) {
        super(itemView);
        button = itemView.findViewById(R.id.toggle);
        text = itemView.findViewById(R.id.text1);
    }
}
}

test_layout.xml

<?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="wrap_content"
          android:orientation="horizontal">

<ToggleButton
    android:id="@+id/toggle"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

<TextView
    android:id="@+id/text1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>
</LinearLayout>
Kuffs
  • 35,581
  • 10
  • 79
  • 92
  • I tried you code and I get this exception java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling. You cannot call notifyDataSetChanged when the bindingView is already running. You can use a flag to indicate if on bind is running or use a Handler to run notifyDataSetChanged on different thread. – Nick Zisis Aug 03 '17 at 11:28
  • This is the most basic demo of a recyclerview adapter that worked fine when I wrote it. Maybe your own code is causing a side effect. Try looking at this: https://stackoverflow.com/questions/27070220/android-recyclerview-notifydatasetchanged-illegalstateexception/32373999#32373999 – Kuffs Aug 03 '17 at 11:52
0

Try setting OnCheckedChangeListener instead of 'OnclickListener`

`button.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        if (isChecked) {
            // The toggle is enabled
        } else {
            // The toggle is disabled
        }
    }
});`

Android Doc : https://developer.android.com/guide/topics/ui/controls/togglebutton.html

VishnuSP
  • 599
  • 5
  • 16
0

set on click listener in the onBindView and In the onClick method, first "turn off" all the toggle button by setting a parameter in the list model and then "turn on" the selected one if it is not selected.

@Override
public void onClick(View view) {

    // clear all model values to isSelected false in the list 

}