Reputation: 8944
My listview reorders when I scroll through it..this is very confusing.
Here's the custom adapter I'm using:
public class LoadExpenseList extends BaseAdapter{
List<Expense> expenses;
Context context;
public LoadExpenseList(Context context, int textViewResourceId,
List<Expense> expenses) {
super();
this.expenses = expenses;
this.context = context;
}
public View getView(final int position, View convertView, ViewGroup parent){
//View v = convertView;
AvailableExpenseView btv;
if (convertView == null) {
btv = new AvailableExpenseView(context, expenses.get(position));
} else {
btv = (AvailableExpenseView) convertView;
}
btv.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.i("Expense_Availables", "Item Selected!!");
Intent intent = new Intent(getActivity(), ItemDetailActivity.class);
int id = expenses.get(position).getExpenseItemId();
intent.putExtra("id", id);
startActivity(intent);
}
});
btv.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View arg0) {
// TODO Auto-generated method stub
return false;
}
});
registerForContextMenu(btv);
return btv;
}
@Override
public int getCount() {
return expenses.size();
}
@Override
public Object getItem(int position) {
return expenses.get(position);
}
@Override
public long getItemId(int position) {
return expenses.get(position).getExpenseItemId();
}
}
Upvotes: 3
Views: 699
Reputation: 38168
You should setExpenses to expenses.get(position) for recycled AvailableExpenseView
as well as you do it for new instances.
when instances are recycled that just display the wrong at expense at the wrong line (the line they where instanciated at and not the line you are reusing them at).
To be more precise, you don't give your code for AvailableExpenseView
but it may looke like
public class AvailableExpenseView {
private Expense expense = null;
public class AvailableExpenseView( Context context ) {
super( context );
}//cons
/*
Just add this method and use it.
*/
public void setExpense( Expense expense ) {
this.expense = expense;
}//met
}//class
Then, in your adapter do this :
if (convertView == null) {
btv = new AvailableExpenseView(context);
}
btv = (AvailableExpenseView) convertView;
btv.setExpense( expenses.get( expenses.get(position) ) );
It's nice to have components with a loose constructor. Think about examples inside the JVM where nothing is needed to create a button. Then later you can customize through orthogonal methods : "setters" of properties. Design your components this way to make them easy to use, and more polyvalent.
Upvotes: 0
Reputation: 25761
Because your view (AvailableExpenseView
) is constructed with an item, then when the adapter tries to reuse views through convertView, you get a view that is already tied to another item.
Don't construct your view with your model item, instead call something like convertView.setExpense(expenses.get(position))
.
ListView will try to reuse views to improve performance. So what will happen is that the first items in the list are displayed with newly created views and later on as you scroll it will try to reuse views previously created giving you the view through convertView
. Notice these lines:
if (convertView == null) {
// You create a view using the proper item
btv = new AvailableExpenseView(context, expenses.get(position));
} else {
// You don't override the item that was previously assigned
// when the view was created
btv = (AvailableExpenseView) convertView;
}
If convertView is null you are creating a new view, but you are constructing your view with an item. So let's say this get called with position 0. You create a view using the expense which is first at the list. Later on, listView wants to get the view for let's say position 20, and says "ok lets reuse the view that we used for position 0", so it passes this view as convertView
but this view was already created with the item in position 0 and you don't override this. So you end up using a view that has the first item to represent the 20th item.
To solve this you can easily do something like this:
AvailableExpenseView btv;
if (convertView == null) {
// dont create your view with an item
btv = new AvailableExpenseView(context);
} else {
btv = (AvailableExpenseView) convertView;
}
// Assign the expense wether it is a newly created view or
// a view that is reused
btv.setExpense(expenses.get(position));
Of course you will have to edit AvailableExpenseView
and create a setExpense()
method to populate your view.
Upvotes: 4
Reputation: 2924
Your problem is here:
if (convertView == null) {
btv = new AvailableExpenseView(context, expenses.get(position));
} else {
btv = (AvailableExpenseView) convertView;
}
if convertView is null, then you'll create a new AvailableExpenseView with the Expense at position. This is fine.
But if convertView is not null, then you'll reference an existing AvailableExpenseView. This has previously been initialized (in the previous case) to a different Expense than the one that you want to show at the current position.
You have two options: set the Expense for btv AFTER this if block, so that the correct Expense is used whether you're creating a new AvailableExpenseView or recycling one --
or: in the else block, set the correct Expense object for the recycled view.
Upvotes: 0