How can I make my ArrayAdapter follow the ViewHolder pattern?
The ViewHolder is basically a static class instance that you associate with a view when it's created, caching the child views you're looking up at runtime. If the view already exists, retrieve the holder instance and use its fields instead of calling findViewById
.
In your case:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
ViewHolder holder; // to reference the child views for later actions
if (v == null) {
LayoutInflater vi =
(LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.mainrow, null);
// cache view fields into the holder
holder = new ViewHolder();
holder.nameText = (TextView) v.findViewById(R.id.nameText);
holder.priceText = (TextView) v.findViewById(R.id.priceText);
holder.changeText = (TextView) v.findViewById(R.id.changeText);
// associate the holder with the view for later lookup
v.setTag(holder);
}
else {
// view already exists, get the holder instance from the view
holder = (ViewHolder) v.getTag();
}
// no local variables with findViewById here
// use holder.nameText where you were
// using the local variable nameText before
return v;
}
// somewhere else in your class definition
static class ViewHolder {
TextView nameText;
TextView priceText;
TextView changeText;
}
caveat: I didn't try to compile this, so take with a grain of salt.
Android: ViewHolder pattern and different types of rows?
Yes, though it is far better to override getViewTypeCount()
and getItemViewType()
in your Adapter. That will teach Android's object pool to only hand you a row of the proper type back in getView()
.
ViewHolder pattern - How to correctly update views
What you are doing
The currently visible rows are at let's say position 0,1,2. You are updating a particular ViewHolder that is already been set as the tag of a particular (for use of a better word) - view. This view is then re-used later on when you call holder = (ViewHolder)rowview.getTag()
. Let's assume for the purpose of this explanation that you are updating view at position 1.
What happens when you scroll a listView
As you scroll a listview, the old view is re-used by getting the holder from the tag. This means that the view that you changed earlier is now being re-used.
What you need to do instead
So when you need to update your view, first of all update a relevant variable. For example:
Create an attribute in your CustomCommunityQuestion class and implement a getter and setter like this:
boolean isBlue;
public void setIsBlue(booelan isblue){
this.isBlue = isblue;
}
public boolean isBlue(){
return isBlue;
}
Now in your button onClickListener, you can set the variable isBlue to yes, and call notifyDataSetChanged()
. Moreover, handle the attribute isBlue like so:
Just check the isBlue state and set the color accordingly
if(customCommunityQuestions.get(position).isBlue()){
holder.buttonFollow.setBackgroundColor(R.color.holo_blue_bright);
}else{
holder.buttonFollow.setBackgroundColor(R.color.black);
}
Hope it helps!
ListView with ArrayAdapter and ViewHolder adding icons to the wrong item
Update: ViewHolder
is only meant to hold references to the component views inside the item layout. This helps to avoid the overhead of calling findViewById
for rendering each component inside complex item layouts with multiple components(Like the TextView
, and ImageView
in this case).
I fixed it by using a routine (called getSex
) to retrieve the sex data and setting all the view data including icons outside the if-else
blocks.
The working code now looks like this:
if (null == convertView) {
Log.i("ANDY","Position not previously used, so inflating");
convertView = inflater.inflate(R.layout.player_simple_list, null);
// Creates a ViewHolder and store references to the two children views
// we want to bind data to.
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.label);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
convertView.setTag(holder);
} else {
// Get the ViewHolder back to get fast access to the TextView
// and the ImageView.
holder = (ViewHolder) convertView.getTag();
}
// Bind the data efficiently with the holder.
holder.text.setText(getItem(position));
// Change icon depending is the sexmale variable is true or false.
if (getSex (getItem(position)) == true) {
holder.icon.setImageBitmap(maleicon);
}
else {
holder.icon.setImageBitmap(femaleicon);
}
return convertView;
ViewHolder cache pattern on ArrayAdapter has ripple effects
The color of the items is a type of state. When using any kind of caching pattern, such as the View Holder pattern, you must make sure to represent that state when setting up your views.
The problem here is not that you change the drawable to R.drawable.x_ok
on click, the problem is that you don't account for the unclicked state in your initial setup.
First, save the state in your Holder object
public void onClick(View v) {
removeItem(item, v);
holder.setLessClicked(true);
holder.less.setImageResource(R.drawable.x_ok);
dialog.getDialog().cancel();
}
Now, whenever you go through the getView method, apply that state correctly:
final ViewHolder holder = (ViewHolder) rowView.getTag();
final StoreItem item = mItems.get(position);
holder.title.setText(item.getTitle());
if(holder.isLessClicked()){
holder.less.setImageResource(R.drawable.x_ok);
}
else{
holder.less.setImageResource(R.drawable.x_normal);
}
The important part to remember is the else condition. You are now correctly setting both states, clicked and unclicked for every possible row. Whereas before, you were not properly handling the unclicked state for all other cells besides your clicked one.
Custom adapter getting wrong position on getView metod in ListView
The array adapter will only create one object per visible row in your list view. After this, the adapter will recycle that already created view. This is the purpose of the:
if(lineView==null)
line in your adapter.
You will want to put in an else section that sets up the row using the recycled view. This other article may be helpful:
How can I make my ArrayAdapter follow the ViewHolder pattern?
What is the benefit of ViewHolder pattern in android?
Understand how listview recycling works
How ListView's recycling mechanism works
You cannot recycle a row that is presently in use. The above link explains how listview recycling mechanism works
So What is the benefit of using ViewHolder?
Quoting docs
Your code might call findViewById()
frequently during the scrolling of ListView, which can slow down performance. Even when the Adapter returns an inflated view for recycling, you still need to look up the elements and update them. A way around repeated use of findViewById()
is to use the "view holder" design pattern.
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) { // if convertView is null
convertView = mInflater.inflate(R.layout.mylayout,
parent, false);
holder = new ViewHolder();
// initialize views
convertView.setTag(holder); // set tag on view
} else {
holder = (ViewHolder) convertView.getTag();
// if not null get tag
// no need to initialize
}
//update views here
return convertView;
}
You missed the important part convertView.setTag(holder)
and holder = (ViewHolder) ConvertView.getTag()
http://developer.android.com/training/improving-layouts/smooth-scrolling.html
Related Topics
When to Use Generic Methods and When to Use Wild-Card
Replace String With Another in Java
Looking for a CSS Parser in Java
Android Recyclerview Addition & Removal of Items
Firebase No Properties to Serialize Found on Class
Java Jsch Changing User on Remote MAChine and Execute Command
How to Avoid Java.Net.Bindexception: Address Already in Use
Java 8 with Jetty on Linux Memory Issue
Linux Start-Up Script for Java Application
How to Cast a List of Supertypes to a List of Subtypes
Scanner Double Value - Inputmismatchexception
Spring Security Does Not Allow CSS or Js Resources to Be Loaded
How to Unzip Files Programmatically in Android
How to Use Intent.Flag_Activity_Clear_Top to Clear the Activity Stack
How to Make My Arrayadapter Follow the Viewholder Pattern
Why This Javac: File Not Found Error
Error:Execution Failed for Task ':App:Dexdebug'. Com.Android.Ide.Common.Process.Processexception