I have a custom arrayadapter and I want to add an onclicklistener for a button in each one of its rows, when I click on the button I want the image resource to change, everything works fine except that when I click on a button the image changes but the image of another button in an other row also changes. Thanks for your help !
Here is my code:
public class Coursadapter extends ArrayAdapter<String>{
Context context;
int layoutResourceId;
ArrayList<String> data = null;
WeatherHolder holder;
public Coursadapter(Context context, int layoutResourceId, ArrayList<String> data) {
// super(context, layoutResourceId, data, coeff);
super(context, layoutResourceId, data);
this.layoutResourceId = layoutResourceId;
this.context = context;
this.data = data;
}
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
if(row == null)
{
LayoutInflater inflater = ((Activity)context).getLayoutInflater();
row = inflater.inflate(layoutResourceId, parent, false);
holder = new WeatherHolder();
holder.name = (TextView)row.findViewById(R.id.item_cours_name);
holder.b=(ImageButton) row.findViewById(R.id.button);
holder.b.setTag(holder);
row.setTag(holder);
}
else
{
holder = (WeatherHolder)row.getTag();
}
holder.b.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
WeatherHolder w = (WeatherHolder) v.getTag();
w.b.setImageResource(R.drawable.butgreen);
}
});
String name1 = data.get(position);
holder.name.setText(name1);
return row;
}
static class WeatherHolder
{
TextView name;
ImageButton b;
}
}
Few changes done check out the code
package com.example.dontpanic;
public class Coursadapter extends ArrayAdapter<String> {
Context context;
int layoutResourceId;
ArrayList<String> data = null;
WeatherHolder holder;
public Coursadapter(Context context, int layoutResourceId,
ArrayList<String> data) {
// super(context, layoutResourceId, data, coeff);
super(context, layoutResourceId, data);
this.layoutResourceId = layoutResourceId;
this.context = context;
this.data = data;
}
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
if (row == null) {
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
row = inflater.inflate(layoutResourceId, parent, false);
holder = new WeatherHolder();
holder.name = (TextView) row.findViewById(R.id.item_cours_name);
holder.b = (ImageButton) row.findViewById(R.id.button);
holder.b.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
WeatherHolder w = (WeatherHolder) v.getTag();
w.b.setImageResource(R.drawable.butgreen);
}
});
row.setTag(holder);
} else {
holder = (WeatherHolder) row.getTag();
}
holder.b.setTag(holder);
String name1 = data.get(position);
holder.name.setText(name1);
return row;
}
static class WeatherHolder {
TextView name;
ImageButton b;
}
}
put your click listener inside if condition and set tag outside of the if else condition
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null)
{
LayoutInflater inflater = ((Activity)context).getLayoutInflater();
convertView = inflater.inflate(layoutResourceId, parent, false);
}
TextView name = (TextView)convertView.findViewById(R.id.item_cours_name);
ImageButton b=(ImageButton) convertView.findViewById(R.id.button);
b.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
b.setImageResource(R.drawable.butgreen);
}
});
String name1 = data.get(position);
name.setText(name1);
return convertView;
}
There's a common misunderstanding that your question and both answers share.
First of all, make sure you understand how ListView's recycling works. For example, this post explains this matter a bit.
Regarding this specific case, let's first understand the problem. When you click on a button, the image is changed and everything looks OK. But if you scroll, you will get the same view (with button that has a changed image) in another position. Because of recycling.
To fix this problem, you have to maintain your button's image selection. This won't magically happen. You have to add some logic that tells this method which image resource should be displayed in a particular position.
For example, in your getView() method, add the following (pseudo)code:
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
if(row == null) {
// omitted..
}
else {
holder = (WeatherHolder)row.getTag();
// BUTTON's IMAGE MAINTAINING
if(condition) {
w.b.setImageResource(R.drawable.butgreen);
} else {
w.b.setImageResource(some-other-image-resource);
}
}
// omitted..
return row;
}
The reason why this happens is because the adapter recycles the views on scrolling.
For example, suppose you have a total of 15 rows and 5 rows fit on the screen at a time. The if(row == null)
part executes for the first 5 rows only. When you scroll down to the 6th row, it uses the View of the 1st row. This is why you are supposed to set the content of your View after the if-else clause. So now, when the 6th row comes into the screen, row is not null
. It holds the View of the 1st row. When it executes holder.name.setText(name1);
, it is replacing the name of the 1st row with the name of the 6th row. Similarly, the 7th row uses the view of the 2nd row and so on.
Now, suppose you click on the button in the 1st row. The View for the 1st row changes and w.b.setImageResource(R.drawable.butgreen);
sets the image resource of the button in the 1st row. When you scroll to the 6th row, it uses the View of the 1st row. But you are setting only holder.name
to the name of the 6th row. The image resource of the button in the 1st row is going to remain there, hence causing your problem.
Solution: Create another ArrayList<String>
and store the status of the button in this. So in your case, I'm assuming the buttons are either "red" or "green" in color. You could store either "red" or "green" at each position signifying what color each button is currently (you can initialize this ArrayList to red). Then, when each row loads, you can set the image resource along with the name of that row. Take a look at the code below. I have put comments to the lines I added.
public class Coursadapter extends ArrayAdapter<String> {
Context context;
int layoutResourceId;
ArrayList<String> data = null;
WeatherHolder holder;
ArrayList<String> colors; // Stores the color of each button
public Coursadapter(Context context, int layoutResourceId, ArrayList<String> data) {
// super(context, layoutResourceId, data, coeff);
super(context, layoutResourceId, data);
this.layoutResourceId = layoutResourceId;
this.context = context;
this.data = data;
this.colors = new ArrayList<>(Collections.nCopies(data.size(), "red")); // initialize all the buttons to red color
}
public View getView(final int position, View convertView, ViewGroup parent) {
View row = convertView;
if(row == null)
{
LayoutInflater inflater = ((Activity)context).getLayoutInflater();
row = inflater.inflate(layoutResourceId, parent, false);
holder = new WeatherHolder();
holder.name = (TextView)row.findViewById(R.id.item_cours_name);
holder.b=(ImageButton) row.findViewById(R.id.button);
holder.b.setTag(holder);
row.setTag(holder);
}
else
{
holder = (WeatherHolder)row.getTag();
}
if(colors.get(position).equals("red")) {
holder.b.setImageResource(R.drawable.butred); // set the colors of the button to red or whatever you need
} else {
holder.b.setImageResource(R.drawable.butgreen); // set the colors of the button to green
}
holder.b.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
WeatherHolder w = (WeatherHolder) v.getTag();
//w.b.setImageResource(R.drawable.butgreen);
colors.set(position, "green"); // update the ArrayList colors
}
});
String name1 = data.get(position);
holder.name.setText(name1);
return row;
}
static class WeatherHolder
{
TextView name;
ImageButton b;
}
}