The key to a recycler view or any adapter view in Android is to have the adapter adapt your models to the view. In your case your view is a TextView
plus a Switch
, so your adapter must adapt some model to this view. In this case I'd choose a simple model like this:
class ItemModel {
String text;
boolean on;
}
I've omitted getters and setters for simplicity
This model contains an string text
which reflects the text in your text view and a boolean on
that reflects the state of the switch. When true the switch is checked and when false it's unchecked.
There's tons of ways to represent this model. I've chosen this one, you may choose a different one. The point is, you need to save the state somewhere and this is what I mean by model - the view model.
Now let's build an adapter that can do 2 things - Update the models when the switch is clicked and tell the activity that the switch changed state. Here's one way to do this:
public class ItemsAdapter extends
RecyclerView.Adapter<ItemsAdapter.ViewHolder> {
@NonNull
private final List<ItemModel> itemModels;
@Nullable
private OnItemCheckedChangeListener onItemCheckedChangeListener;
ItemsAdapter(@NonNull List<ItemModel> itemModels) {
this.itemModels = itemModels;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.item, parent, false));
}
@Override
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
ItemModel item = itemModels.get(position);
holder.text.setText(item.text);
holder.switchCompat.setChecked(item.on);
// Make sure we update the model if the user taps the switch
holder.switchCompat.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
int adapterPosition = holder.getAdapterPosition();
ItemModel tapped = itemModels.get(adapterPosition);
itemModels.set(adapterPosition, new ItemModel(tapped.text, isChecked));
if (onItemCheckedChangeListener != null) {
onItemCheckedChangeListener.onItemCheckedChanged(adapterPosition, isChecked);
}
}
});
}
@Override
public void onViewRecycled(@NonNull ViewHolder holder) {
super.onViewRecycled(holder);
holder.switchCompat.setOnCheckedChangeListener(null);
}
@Override
public int getItemCount() {
return itemModels.size();
}
public void setOnItemCheckedChangeListener(@Nullable OnItemCheckedChangeListener onItemCheckedChangeListener) {
this.onItemCheckedChangeListener = onItemCheckedChangeListener;
}
interface OnItemCheckedChangeListener {
/**
* Fired when the item check state is changed
*/
void onItemCheckedChanged(int position, boolean isChecked);
}
class ViewHolder extends RecyclerView.ViewHolder {
TextView text;
SwitchCompat switchCompat;
ViewHolder(View itemView) {
super(itemView);
text = itemView.findViewById(R.id.item_text);
switchCompat = itemView.findViewById(R.id.item_switch);
}
}
}
There's a lot to digest, but let's focus on the important bits - the method onBindViewHolder
. The first 3 lines are the classic recycling of the view. We grab the model at the correct position and set the elements in the view that correspond to model's attributes.
Then it gets more interesting. We set a OnCheckedChangeListener
to update the model and the activity every time the switch changes state. The first 3 lines change the model in the adapter and the rest uses the custom interface OnItemCheckedChangeListener
to notify the listener about the switch change. It's important to notice that inside the method OnCheckedChangeListener
you should no longer use position
, but rather use holder.getAdapterPosition
. This will give you the correct position in the adapter's data list.
Since now the adapter has always the correct models inside the data list, every time the method onBindViewHolder
is called the adapter knows exactly how to setup the view. This means that while scrolling and recycling the views, it will preserve the state of each item within the models inside the data
list.
It's important to remove the OnCheckedChangeListener
when the view gets recycled - onViewRecycled
. This avoids messing the count when the adapter is setting the value of switchCompat
in the onBindViewHolder
.
Here's an example of how the activity could look like:
public class MainActivity extends AppCompatActivity {
private int count = 0;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
List<ItemModel> data = new ArrayList<>();
for (int i = 1; i <= 100; i++)
data.add(new ItemModel("Item " + i, false));
ItemsAdapter adapter = new ItemsAdapter(data);
((RecyclerView) findViewById(R.id.recyclerview)).setAdapter(adapter);
final TextView countTextView = findViewById(R.id.count);
drawCount(countTextView);
adapter.setOnItemCheckedChangeListener(new ItemsAdapter.OnItemCheckedChangeListener() {
@Override
public void onItemCheckedChanged(int position, boolean isChecked) {
if (isChecked)
count++;
else
count--;
drawCount(countTextView);
}
});
}
private void drawCount(TextView countTextView) {
countTextView.setText(String.valueOf(count));
}
}
This code is meant to demonstrate the idea, not to follow :) In any case, we setup all the initial state and then set up the custom listener OnItemCheckedChangeListener
to update the text view in the activity.
The layout files shouldn't be relevant here, but as you can imagine the activity has a text view with id count
and there's a recycler view with the id recyclerview
.
Hope this helps