As Adapter for ListView, I am using custom adapter. On Adapter's getView method, I am trying to change listitem's background image using setBackgroundDrawable.
public View getView(int position, View convertView, ViewGroup parent) {
View vi = convertView;
CurrencyModel currency = new CurrencyModel();
currency = currencyList.get(position);
if (convertView == null) {
vi = inflater.inflate(R.layout.listitem_currency, null);
}
TextView tvSymbol = (TextView) vi.findViewById(R.id.tvSymbol);
TextView tvSize = (TextView) vi.findViewById(R.id.tvSize);
TextView tvName = (TextView) vi.findViewById(R.id.tvName);
TextView tvRate = (TextView) vi.findViewById(R.id.tvRate);
tvSymbol.setText(currency.getSymbol());
tvSize.setText(currency.getSize());
tvName.setText(currency.getName());
tvRate.setText(currency.getRate());
if (currency.getSymbol().equals("AUD")) {
vi.setBackgroundDrawable(context.getResources().getDrawable(
R.drawable.bg_exch_high_cell));
}
return vi;
}
When activity starts, everything is working correctly - listitem with AUD has different background. All mystery starts when I scroll the listview - other listitems also get "special" background. The more scroll, the more listitems changed. I do not have idea why this is happening. How to solve this problem?
if (currency.getSymbol().equals("AUD")) {
vi.setBackgroundDrawable(context.getResources().getDrawable(
R.drawable.bg_exch_high_cell));
}
else {//restore default background}
This happens because cell views are reused by listview. You should restore default background for other items.
More about list view performance
TextView tvSymbol = (TextView) vi.findViewById(R.id.tvSymbol);
Before this line add the default background the you put to the item. The reason in your code why it happens is that android try using the the already created view(vi). Since if it is assigned bg_exch_high_cell, it will retain it. So reset it in the beginning.
vi.setBackgroundDrawable(context.getResources().getDrawable(
//default background drawable here ));
TextView tvSymbol = (TextView) vi.findViewById(R.id.tvSymbol);
Related
I have a custom layout for a Listview, each row item contains a button that when clicked it shows a small imageview in the item, however the actions i perform in one item, repeats for another item down the list, for example, if i click the button in item 1, the imageview will show up in item 1 and item 10, if i click the button in item 2, the Imageview will show up on item 2 and item 11 and while i scroll it will keep repeating in different items, heres the code for my custom adapter:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
mparent = parent;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.places_item, parent, false);
holder = new ViewHolder();
holder.placeimage = (CircularImageView) convertView.findViewById(R.id.locationimage_pilayout);
holder.addbtn = (TextView) convertView.findViewById(R.id.addbtn_pilayout);
holder.delbtn = (TextView) convertView.findViewById(R.id.delbtn_pilayout);
holder.oribtn = (TextView) convertView.findViewById(R.id.oribtn_pilayout);
holder.placename = (TextView) convertView.findViewById(R.id.locationname_pilayout);
holder.selected = (ImageView) convertView.findViewById(R.id.selected_pilayout);
holder.origin = (ImageView) convertView.findViewById(R.id.origin_pilayout);
holder.swipeLayout = (SwipeRevealLayout) convertView.findViewById(R.id.swipe_pilayout);
holder.mainLayout = (LinearLayout) convertView.findViewById(R.id.main_pilayout);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
final Place item = getItem(position);
/*
my code assigning the button click listener and assigning the views
*/
return convertView;
}
Am i missing something? im sure this could be a simple fix, but i havent found it yet. any help would be kindly appreciated.
In ListView the individual views for rows, get reused. For example, let's say the window can show up to 10 rows inside the ListView. Now when you scroll down, the 1st view gets out of the window from the top, and a new 11th view comes into the window from the bottom. In order to save memory and CPU power, Android uses the same 1st view for the 11th view. That's the purpose of convertView and the if (convertView == null) {} else {} code.
So the reason why the image is being shown in the 1st item and also in the 11th item, is that they are exactly one view object. To tackle this issue, in the getView() method, you need to reset every attribute of every view and don't rely on the defaults.
So adding a line like the one below, will get rid of the mentioned problem:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// all your code ...
holder.placeImage.setImageResource(0); //<-- This clears any previously set image.
return convertView;
}
I've implemented this sample (2. Custom Adapter example)
http://www.mkyong.com/android/android-gridview-example/
and it works.
I then wanted to "refresh" the data with a new set, which didn't work until I did this:
imageAdapter.notifyDataSetChanged();
and then removed the following check in the ImageAdapter getView method :
if (convertView == null) {
Here is my current getView method
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View gridView;
// if (convertView == null) { // stopped my GridView from updating !!
if (true)
{
gridView = new View(context);
// get layout from mobile.xml
gridView = inflater.inflate(R.layout.mobile, null);
// set value into textview
TextView textView = (TextView) gridView
.findViewById(R.id.grid_item_label);
textView.setText(mobileValues[position]);
// set image based on selected text
ImageView imageView = (ImageView) gridView
.findViewById(R.id.grid_item_image);
imageView.setImageResource(R.drawable.square);
} else {
gridView = (View) convertView;
}
return gridView;
}
I'm concerned it is now doing unnecessary processing over and over - i.e. inflating view multiple times?
Should I be creating a new GridView every time this is called ?
it didn't work because the following rows
TextView textView = (TextView) gridView
.findViewById(R.id.grid_item_label);
textView.setText(mobileValues[position]);
// set image based on selected text
ImageView imageView = (ImageView) gridView
.findViewById(R.id.grid_item_image);
imageView.setImageResource(R.drawable.square);
go outside the if/else. The check if (convertView == null) is necessary. If you don't have it you will inflate n different views, with n == getCount(). For n large it will be a problem. And you probably want to implement the android ViewHolder pattern, to give your users the best UX possible.
As correctly pointed out by #Vzsg, get rid also of gridView = new View(context);. It an additional useless allocation
I usually just update the adapter with the new results, and set it to the GridView.
So in your example, when I want to update my GridView - I do the following.
gridView.setAdapter(newAdapter);
You can have a utility method, such as that will make get a new Adapter much easier.
private ArrayAdapter getAdapter(String [] data){...}
I have a list view that changes color when you press it and at the same time a timer starts ticking.
if you press a row in the list view it changes to:
I have a onItemClickListener attached to the list view. I recycle my row views with a holder class.
THE PROBLEM is that when I press a row and i scroll somewhere further in the list view, suddenly another row view has also changed color and started counting. I know this is because the views get recycled. But I have no idea how to solve this for the background color and the timer (which is running in separate thread)
this is the getView of my Adapter:
#Override
public View getView(final int position, View view, ViewGroup parent) {
ViewHolderItem viewHolder = null;
TextView txtTitle = null;
TextView timer = null;
if(view == null){
inflater = context.getLayoutInflater();
view = inflater.inflate(R.layout.list_single, parent, false);
txtTitle = (TextView) view.findViewById(R.id.txt);
timer = (TextView) view.findViewById(R.id.timer);
viewHolder = new ViewHolderItem();
viewHolder.textViewTimer = timer;
viewHolder.textViewOrderNo = txtTitle;
viewHolder.view = view;
view.setTag(viewHolder);
}
else{
viewHolder = (ViewHolderItem) view.getTag();
}
viewHolder.textViewOrderNo.setText(web[position]);
return view;
}
you have to Set a Flag , Onclick change that flag, so that every time you can check it and change the BackGround in GetView() for that particular Position
if(checkcondition){
//change color
}else{
//change color
}
hope it help you..
Store the state for each row and according make changes it getView()..something similar to this:
if(state ==counting) {
viewHolder.textViewOrderNo.setBackgroundColour(colorCounting);
viewHolder.textViewOrderNo.setCount(count); }
You will also have to communicate with the counter to get timer status..
After looking for some answers here, I find myself in a disturbing situation where my Listview is really getting on my nerve.
Here are the questions I looked for :
Maintain ListView Item State
How to save state of CheckBox while scrolling in ListView?
I'm using a custom adapter with a custom row as below.
My Listview is simple as it is displaying a custom row made of three elements :
1) an ImageView displaying contact picture cropped in a circle ;
2) a TextViewdisplaying the contact full name as plain text ;
3) and finally an ImageView that holds the purpouse of a CheckBox.
Please focus on the last element. The ImageView CheckBox-like will have its src changed upon click.
When the user click, the ImageView will switch between a check sign and an unchecked sign according to it's previous status. Possible status are : {checked | unchecked}
So far so good.
But as soon as I scroll the ListView, any aforementioned change will disappear as Android recycle unused view.
Here comes the so-called ViewHolder pattern. Unfortunately, this pattern is failling me on two issues :
First, when scrolling, my organized-in-an-alphabetical-order listview gets disorganized.
e.g. somehow, whitout any reason, the first displayed contact name gets displayed again later on the ListView as I scrolled. That can happen with any row ! So it would seem unused view are being wrongly re-used.
Second, and in accordance to the first issue, the checked status do seem to stay, but not always and if it does stay, it may very well stay on the wrong row ... and that can happen randomly, of course. Therefore ViewHoder is not a viable solution.
Before discouvering the ViewHolder pattern, I have been using a HashMap to store the item position upon click as followed :
ContactsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public final void onItemClick(final AdapterView<?> adapterView, final View view,
final int position, final long id) {
final ImageView check = (ImageView) view.findViewById(R.id.checkImage);
final TextView name = (TextView) view.findViewById(R.id.contactName);
final Boolean isChecked = Boolean.valueOf(checkedContactsList.isChecked(position));
if (isChecked != null && isChecked.booleanValue() == true) {
check.setImageDrawable(getActivity().getResources().getDrawable(R.drawable.unchecked_sign));
checkedContactsList.(position);
} else {
check.setImageDrawable(getActivity().getResources().getDrawable(R.drawable.checked_sign));
checkedContactsList.add(position, true);
}
}
});
I tried adding a different value instead of position.
I tried with ContactsListView.getPositionForView(view)
And I also tried with the View's ID, but still it doesn't work.
I wish I could use ContactsListView.getSelectedItemPosition() but it returns -1 as there is no selection event because I'm handling a touch/click event.
And this is how my Custom Adapter looks like :
public final View getView(final int position,
final View convertView, final ViewGroup parent) {
final LayoutInflater inflater = (LayoutInflater) this.context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View contactRowView = inflater.inflate(R.layout.contact_row, parent, false);
final ImageView contactPic = (ImageView) contactRowView.findViewById(R.id.contactPic);
final TextView contactName = (TextView) contactRowView.findViewById(R.id.contactName);
final ImageView checkImage = (ImageView) contactRowView.findViewById(R.id.checkImage);
// the list is the same as above and therefore contains the exact same entries
if (this.checkedContactsList.isChecked(position))
checkImage.setImageDrawable(this.context.getResources().getDrawable(R.drawable.checked_sign));
contactPic.setImageBitmap(cropePictureInCircle(this.contacts.get(position).getPicture()));
contactName.setText(this.contacts.get(position).getName());
return contactRowView;
}
Is there a good way to keep the checked row checked and the unchecked row unchecked in the given alphabetical order ?
Thanks !
For the list position change I know the solution but for the second problem I am still searching for a solution, anyway first make a viewHolder class;
public class ViewHolder{
//put all of your textviews and image views and
//all views here like this
TextView contactName;
ImageView checkImage;
ImageView contactImage;
}
Then edit your adapter:
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
final View contactRowView = convertView;
ViewHolder holder;
if (contactRowView == null) {
final LayoutInflater inflater = (LayoutInflater)
this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE
);
contactRowView =
inflater.inflate(R.layout.contact_row, parent,
false);
holder = new ViewHolder():
holder.contactPic = (ImageView)
contactRowView.findViewById(R.id.contactPic);
holder.contactName = (TextView)
contactRowView.findViewById(R.id.contactName);
holder.checkImage = (ImageView)
contactRowView.findViewById(R.id.checkImage);
contactRowView.setTag(holder);
} else {
holder = contactRowView.getTag();
// the list is the same as above and therefore contains the exact same entries
if (this.checkedContactsList.isChecked(position))
holder.checkImage.setImageDrawable(this.context.get.
Resources().getDrawable(R.drawable.checked_sign));
holder.contactPic.setImageBitmap(cropePictureInCircle(this.contacts.get(position).getPicture()));
holder.contactName.setText(this.contacts.get(position).getName());
return contactRowView;
}
}
Hope this helps and sorry because writing code from my phone is totally awkward.
My listview contains one image,and some textview; there are two textiview which contains the price and special price,if special price is 0 than set only price textview and special price let it null,but special price is > 0 than set price value as well special price,fisrt time everything is going good but when i am scrolling the listview then blank textview set with dumyy valueenter code here
Here is my getView method code.
#Override
public View getView(final int position, View convertView, ViewGroup parent){
//public View getView(int position, View convertView, ViewGroup parent) {
/*View vi = convertView;
if (convertView == null)
{
vi = inflater.inflate(R.layout.list_row, null);
}
*/
int pos=position;
if (convertView == null) {
convertView = inflater.inflate(R.layout.list_row, null);
viewHolder=new ViewHolder();
viewHolder.txt_id = (TextView) convertView.findViewById(R.id.id); // title
viewHolder.txt_product_name = (TextView) convertView.findViewById(R.id.title); // title
viewHolder.artist = (TextView) convertView.findViewById(R.id.artist); // artist
// name
viewHolder.txt_mspecialprice_withouttax = (TextView) convertView.findViewById(R.id.duration); // duration
viewHolder.stock = (TextView) convertView.findViewById(R.id.stck);
viewHolder.txt_mprice_withouttax = (TextView) convertView.findViewById(R.id.txtmpricewithouttax);
viewHolder.thumb_image = (ImageView) convertView.findViewById(R.id.list_image); // thumb
// image
convertView.setTag(viewHolder);
}
else{
viewHolder = (ViewHolder) convertView.getTag();
}
HashMap<String, String> song = new HashMap<String, String>();
song = data.get(position);
// Setting all values in listview
String mspecialprice_str=song.get(CustomizedListView.KEY_PRODUCT_MSPECIAL_WITHOUT_TAX);
//String substr_mspecialprice_str=mspecialprice_str.substring(1,mspecialprice_str.indexOf("."));
//String substr_mspecialprice_str_replaced=substr_mspecialprice_str.replace(",", "");
String msaleprice_str=song.get(CustomizedListView.KEY_PRODUCT_MPRICE_WITHOUT_TAX);
//String substr_msaleprice_str=msaleprice_str.substring(0,msaleprice_str.indexOf("."));
//String substr_msaleprice_str_replaced=substr_msaleprice_str.replace(",", "");
viewHolder.txt_id.setText(song.get(CustomizedListView.KEY_PRODUCT_ID));
viewHolder.txt_product_name.setText(song.get(CustomizedListView.KEY_PRODUCT_NAME));
viewHolder.artist.setText(song.get(CustomizedListView.KEY_PRODUCT_DESCRIPTION));
viewHolder.stock.setText(song.get(CustomizedListView.KEY_STOCK));
if(mspecialprice_str.equals("0"))
{
//txt_mspecialprice_withouttax.setText(song.get(CustomizedListView.KEY_PRODUCT_MSPECIAL_WITHOUT_TAX));
viewHolder.txt_mprice_withouttax.setText("$"+(song.get(CustomizedListView.KEY_PRODUCT_MPRICE_WITHOUT_TAX)));
viewHolder.txt_mprice_withouttax.setTextColor(Color.parseColor("#64aef9"));
}
//if(!(mspecialprice_str.equals("0")))
//{
else
{
viewHolder.txt_mspecialprice_withouttax.setText("$"+(song.get(CustomizedListView.KEY_PRODUCT_MSPECIAL_WITHOUT_TAX)));
viewHolder.txt_mprice_withouttax.setText("$"+(song.get(CustomizedListView.KEY_PRODUCT_MPRICE_WITHOUT_TAX)));
viewHolder.txt_mprice_withouttax.setPaintFlags(viewHolder.txt_mprice_withouttax.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
viewHolder.txt_mprice_withouttax.setTextColor(Color.parseColor("#F01616"));
}
imageLoader.DisplayImage(
song.get(CustomizedListView.KEY_PRODUCT_IMAGEURL), viewHolder.thumb_image);
return convertView;
}
}
class ViewHolder {
TextView txt_id ; // title
TextView txt_product_name; // title
TextView artist ; // artist
// name
TextView txt_mspecialprice_withouttax; // duration
TextView stock ;
TextView txt_mprice_withouttax;
ImageView thumb_image ; // thumb
}
ListView, GridView reuse views used as list items/grid elements. getView() is called everytime android tries to draw next element while scrolling your view. There is no need to prevent that!
Edit - Atrix1987
From the developer docs
An Adapter object acts as a bridge between an AdapterView and the underlying data for that view. The Adapter provides access to the data items. The Adapter is also responsible for making a View for each item in the data set.
Suppose you have 10 elements which you want to show using your GridView/ListView and the maximum visible items is 5 then the same 5 views can be reused to display the rest of the 5 elements when you scroll. This is the intended behavior and is the right way to do things [keeps number of views to a minimum].
You don't have control on getView method, the framework does that for you.
The GridView widget extends AdapterView. It uses the adapter to allow the reuse of Views and improve performance. There is no way to avoid calling getView() - it is essential to the whole AdapterView idea. If you want a static layout, perhaps you should use something else.