How to Make My Arrayadapter Follow the Viewholder Pattern

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



Leave a reply



Submit