I facing below issue with item back ground on scrolling.
In my application I have a listview which require multi-selection. Also this is a custom list where selection needs to be represented by change in list item color instead of check-box based approach.
For this: In the OnClick I'm checking if the position is selected or not and then set the background for the item. However this has issue when I scroll the list. Taking an example:
suppose the list has 50 items. And 10 are visible at a time. I select say 5th item [thus changing the background]. And then I scroll the list. After scroll the visible part of the list corresponding to earlier 5th item ,say 15th item in item of the list but 5th index in visible portion,still has background corresponding to selected state. Whereas it should not have been set since I have not selected 15th item yet.
I tried:
a-In the getView method of adapter, if the item is not one of selected items I'm setting one background else different.Tried - setBackgroundColor as well setBackgrounddrawable.
b- In the xml have set the cacheColorHint to transparent
c- Have selector attached to items and the items responding to state [pressed,selected] in onlcick.
However still I'm not able to get rid of unwanted background color for item on scrolling.
Any help. I tried various suggestion mentioned in various post in SO but not succcessful yet.
I tried
thanks
pradeep
this is a normal behavior of ListView adapter in android, its getView() called on every scroll and for every new list item it call getView, if listview item currently not visible on UI then its convertView is equals to null: At a time listview take load of only visible list items, if it showing at a time 10 element out of 50, then listView.getChildCount() will return only 10 not 50.
In your case when you select 5, it reflected selection for 5+10(visible items count) = 15, 25, 35, 45 too.
To solve this problem you should have a flag associate with your each listItem data, for example if you have string array itemData[50] as array, then take an array of boolean isSelected[50] with initial value false for each.
Take a look for getView(), in adapter class:
public View getView(int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
string text = itemData[position]
if (convertView == null) {
rowLayout = (RelativeLayout) LayoutInflater.from(context)
.inflate(R.layout.list_view_item, parent, false);
holder = new ViewHolder();
holder.txtString= (TextView) rowLayout
.findViewById(R.id.txtTitle);
rowLayout.setTag(holder);
} else {
rowLayout = (RelativeLayout) convertView;
holder = (ViewHolder) rowLayout.getTag();
}
if(isSelected[position] == true){
holder.txtString.setText("Selected")
rowLayout.setBackGround(selected)
}else{
holder.txtString.setText("Not Selected")
rowLayout.setBackGround(notSelected)
}
public class ViewHolder {
public TextView txtString;
}
and in your Activity class on listView.setOnItemClickListener():
listView.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> arg0, View arg1,
int position, long arg3) {
// TODO Auto-generated method stub
isSelected[position] = true // on selection
RelativeLayout rowLayout = (RelativeLayout) view;
rowLayout.setBackGround(Selected);
// also set here background selected for view by getting layout refference
}
});
Related
Could someone please help before this drives me completely insane!
Imagine you have a list view. It has in it 9 items, but there is only space to display 6 without scrolling. If an item is selected the background colour will change to indicate this.
If you select any item from 2 to 8 inclusive all is well in the world.
If you select item 1 it also selects item 9 and vica versa. Also with this selection if you scroll up and down a random number of times, the selection will change. If you continue to scroll up and down, the selection changes back to 1 and 9. The value of the selected item is always the actual item you selected.
This is my code from my adapter :
public class AvailableJobAdapter extends ArrayAdapter<JobDto> {
private Context context;
private ArrayList<JobDto> items;
private LayoutInflater vi;
public AvailableJobAdapter(Context context, ArrayList<JobDto> items) {
super(context, 0, items);
this.context = context;
this.items = items;
vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
JobDto jh = getItem(position);
if (convertView == null) {
convertView = vi.inflate(R.layout.inflator_job_list, null);
holder = new ViewHolder();
holder.numberText = (TextView) convertView.findViewById(R.id.txtNumber);
holder.descriptionText = (TextView) convertView.findViewById(R.id.txtDescription);
holder.statusText = (TextView) convertView.findViewById(R.id.txtStatus);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.numberText.setText(jh.getJobNumber());
holder.descriptionText.setText(jh.getDescription());
holder.statusText.setText(jh.getStatus());
return convertView;
}
public static class ViewHolder {
public TextView numberText;
public TextView descriptionText;
public TextView statusText;
}
}
and this is the code from my click listener :
jobs.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Button btnOk = (Button) findViewById(R.id.btnOk);
view.setSelected(true);
int selected = position;
int pos = val.get(selected);
int firstItem = jobs.getFirstVisiblePosition();
int viewIndex = selected - firstItem;
if (pos == 0) {
jobs.getChildAt(viewIndex).setBackgroundColor(getResources().getColor(R.color.selected));
val.set(selected, 1);
selectedCount ++;
} else {
jobs.getChildAt(viewIndex).setBackgroundColor(getResources().getColor(R.color.unselected));
val.set(selected, 0);
selectedCount --;
}
if (selectedCount > 0 ){
btnOk.setEnabled(true);
} else {
btnOk.setEnabled(false);
}
}
});
I have spent hours researching this and trying various suggestions.
Any help will be greatly appreciated.
****EDIT****
After playing with some suggestion I tested it with a HUGE list. This is exactly what the behaviour is :-
If your screen has space for 10 items, if you select item 1 it also highlights 11, 21, 31, 41 etc.
Anything in between these values behaves correctly.
From this guide:
When your ListView is connected to an adapter, the adapter will instantiate rows until the ListView has been fully populated with enough items to fill the full height of the list. At that point, no additional row items are created in memory.
Instead, as the user scroll through the list, items that leave the screen are kept in memory for later use and then every new row that enters the screen reuses an older row kept around in memory. In this way, even for a list of 1000 items, only ~7 item view rows are ever instantiated or held in memory.
That's the root of the problem you are facing. You are changing the background color of the views in your click listener. But once a selected item is scrolled out of the screen, its view will be reused for the new item that is swiping in. As the reused view had its background color changed, the new item will consequently have that same color.
You need to take in account that views are recycled, so they might be "dirty" when you get them in getView(). I recommend you to take a look at the guide from where I got the quotes above, it's a nice and important read.
One possible way to fix that is to add a boolean field to your JobDto class and use it to track if an item is selected or not. Then in getView(), you could update the background color accordingly. You'll also probably need to add the item root view(convertView) to your ViewHolder in order to change its background color.
In setOnItemClickListener() just update setSelected() true of false for clicked position and notifydataset. In getView put a condition
if(jh.isSelelcted())
{
// background is selected color
}else{
// background is non selected color
}
note : handle else condition.
The Problem
My way to add the view makes every fifth item to add the view when i only want one position to have this "Mängd" row.
Why Can i only edit listitems when they are visible on the screen.
The child will be static at 5 items even though i got like 20 item....
Is there any way to only say that item 1 will have this and not
position - firstvisibleposition
i think this is the problem with the listview
My code is not understandable at the time because of other things so i hope you get my problem anyways.
This is my main question
It seems like the thing i add to position 0 also happens to 6 and 12 Why is ListView this wierd ?
It's on swedish, but this is what i got with list view.
Every listview item has a empty Linearlayout that i add a view to when i press the down arrow button. the problem is that every fifth item gets this or i can only click on the first 5.
I dont get why they make ListView so complicated. i want to be able to get a child that is in the list without seening it!
CODE getView
public View getView (int position, View convertView, ViewGroup parent){
if (convertView == null)
{
convertView = LayoutInflater.from(getContext()).inflate(R.layout.view_meal_item_editable, null);
}
convertView.setOnTouchListener(new ItemSwipeListener(position,
getResources().getDisplayMetrics().density));
convertView.setClickable(true);
// Lookup view for data population
TextView food_item_name = (TextView) convertView.findViewById(R.id.food_item_name);
food_item_name.setHint("hello");
}
Where i add the view
View view = searchResultList.getAdapter().getView(position, searchResultList.getChildAt(position - searchResultList.getFirstVisiblePosition()), searchResultList);
LinearLayout extendedView = (LinearLayout)view.findViewById(R.id.extended_food_information);
View convertExtendedView = LayoutInflater.from(this).inflate(R.layout.change_amount_on_food_view, null);
extendedView.addView(convertExtendedView);
It's recommended to use a header view if you do this stuff only for the first element.
Otherwise it will be better if you add your extra view in getView() method, something like:
if(position==0){
// add extra view
} else {
// remove extra view if exist
}
Or you can remove the IF condition: if (convertView == null), so you will inflate a new layout each time, it will solve your problem but this is not good for list performance
I use even and odd rows to set backgrond to my listview rows. In my efficientAdapter I set the row background as follows:
public View getView(int position, View convertView, ViewGroup parent) {
vi = convertView;
if (convertView == null) {
vi = inflater.inflate(R.layout.ecran_multiple_row, null);
holder = new ViewHolder();
holder.txIndex = (TextView) vi.findViewById(R.id.txIndex);
holder.txSTitle = (TextView) vi.findViewById(R.id.txSTitle);
holder.btOnOFF = (ImageView) vi.findViewById(R.id.btOnOFF);
vi.setTag(holder);
} else
holder = (ViewHolder) vi.getTag();
/*
* CHANGE ROW COLOR 0 WHITE 1 GRAY
*/
if ( position % 2 == 0) //0 even 1 odd..
vi.setBackgroundResource(R.drawable.listview_selector_odd);
else
vi.setBackgroundResource(R.drawable.listview_selector_even);
/*
* ONE ITEM IN ARRAY
*/
if (data.toArray().length==1){
holder.btOnOFF.setBackgroundResource(R.drawable.air_radio_button_rouge);
}else {
holder.btOnOFF.setBackgroundResource(R.drawable.air_deezer_check);
}
return vi;
}
and in my MainActivity.Class. I select an item using on itemclicklistener() as shown below:
**lvRMultiple.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
imgview = (ImageView) view.findViewById(R.id.btOnOFF);
//And change its background here
imgview.setBackgroundResource(R.drawable.air_radio_button_rouge);
}
});**
When i clicked on an item btnOff image change successfully but when i scroll down it change to default background. Secondly when i click on one item after the other both becomes the new image but i want only the row clicked by the user to change to new image and the previous image are set to default.
All row view of a ListView created by the getView() method of BaseAdpter class. When ever we scroll the ListView all, new viable row create by getView() using recycle. So getView() called again and again when new row is viable on scroll.
There are two solution of your question:-\
You can save the status of ListView
// Save ListView state
Parcelable state = listView.onSaveInstanceState();
// Set new items
listView.setAdapter(adapter);
// Restore previous state (including selected item index and scroll position)
listView.onRestoreInstanceState(state)
And other solution is create RowView at runtime and add it on a Parent Layout by using addView() method.
LayoutInflater inflater = LayoutInflater.from(context);
// You should use the LinerLayout instead of the listview, and parent Layout should be inside of the ScrollView
parentView = (LinerLayout)this.findViewById(R.id.parentView);
for(int i = 0; i<=numberOfRow;i++){
LinearLayout rowView = (LinerLayout)inflater.inflate(R.layout.rowView);
ImageView rowImageView = (ImageView)rowView.findViewById(R.id.rowImage);
rowImageView.setOnClickListener(new View.onClickListListener(){
#Override
public void onClick(){
rowImageView.setImageBitmap(onClickBitmapImage);
}
});
parentView.addView(rowView);
}
Please check this answer Maintain/Save/Restore scroll position when returning to a ListView
More Reference
http://developer.android.com/reference/android/widget/Adapter.html#getView(int,android.view.View, android.view.ViewGroup)
The item changes back to the default background because the view gets recycled. This is the same problems of checkboxes losing their checked state
Check out this answer too see how to handle it:
CheckBox gets unchecked on scroll in a custom listview
As for your second problem, I believe it's already answered here:
highlighting the selected item in the listview in android
Hope it helps
I have a custom ListView Adapter, with which I'm creating rows for a list. My problem is though, that it doesn't seem to be distinguishing the ImageViews, from each other. It seems to be randomly picking ImageViews to chuck into place as I scroll up and down. The text information (omitted from this snippet) does not break. It works as one would expect.
Here is the relevant method of my Adapter:
public View getView( int position, View convertView, ViewGroup parent )
{
View v = convertView;
if( v == null )
{
LayoutInflater vi = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate( R.layout.generic_row, null );
}
// find the image
ImageView favImage = (ImageView)v.findViewById( R.id.toggle_favorite );
// when clicked...
favImage.setOnClickListener( new OnClickListener() {
#Override
public void onClick( View v )
{
// make the gray star a yellow one
int newImage = R.drawable.ic_star_yellow_embossed;
((ImageView)v).setImageBitmap(BitmapFactory.decodeResource(getContext().getResources(), newImage));
}
});
return v;
}
That behavior appears because the ListView recycles the row views as you scroll the list up and down, and because of this you get rows that were acted on by the user(the image was changed) in position were the image should be unmodified. To avoid this you'll have to somehow hold the status of the ImageView for every row in the list and use this status to set up the correct image in the getView() method. Because you didn't say how exactly did you implement your adapter I will show you a simple example.
First of all you should store your the statuses of the ImageView. I used an ArrayList<Boolean> as a member of the custom adapter, if the position(corresponding to the row's position in the list) in this list is false then the image is the default one, otherwise if it is true then the user clicked it and we should put the new image:
private ArrayList<Boolean> imageStatus = new ArrayList<Boolean>();
In your custom adapter constructor initialize this list. For example if you put in your adapter a list of something then you should make your imageStatus as big as that list and filled with false(the default/start status):
//... initialize the imageStatus, objects is the list on which your adapter is based
for (int i = 0; i < objects.size(); i++) {
imageStatus.add(false);
}
Then in your getView() method:
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.adapters_adapter_with_images, null);
}
// find the image
ImageView favImage = (ImageView) v
.findViewById(R.id.toggle_favorite);
// Set the image bitmap. If the imageStatus flag for this position is TRUE then we
// show the new image because it was previously clicked by the user
if (imageStatus.get(position)) {
int newImage = R.drawable.ic_star_yellow_embossed;
favImage.setImageBitmap(BitmapFactory.decodeResource(
getContext().getResources(), newImage));
} else {
// If the imageStatus is FALSE then we explicitly set the image
// back to the default because we could be dealing with a
// recycled ImageView that has the new image set(there is no need to set a default drawable in the xml layout)
int newImage = R.drawable.basket_empty; //the default image
favImage.setImageBitmap(BitmapFactory.decodeResource(
getContext().getResources(), newImage));
}
// when clicked... we get the real position of the row and set to TRUE
// that position in the imageStatus flags list. We also call notifyDataSetChanged
//on the adapter object to let it know that something has changed and to update!
favImage.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Integer realPosition = (Integer) v.getTag(); //get the position from the view's tag
imageStatus.set(realPosition, true); //this position has been clicked be the user
adapter.notifyDataSetChanged(); //notify the adapter
}
});
// set the position to the favImage as a tag, we later retrieve it
// in the onClick method
favImage.setTag(new Integer(position));
return v;
}
This should work well if you don't plan to dynamically modify the list(remove/add rows), otherwise you'll have to take care of also modifying that list of imageStatus to reflect the changes. You didn't say what was your row data, another approach(and the right one if you plan to do something if the user clicks that image(besides changing it)) is to incorporate the status of the image in the row's data model. Regarding this here are some tutorials:
Android ListView Advanced Interactive
or Commonsware-Android Excerpt (Interactive rows)
you need to define the default image right after finding its reference:
// find the image
ImageView favImage = (ImageView)v.findViewById( R.id.toggle_favorite );
//setting to default
favImage.setImageResource(R.drawable.default_image);
// when clicked...
favImage.setOnClickListener....
you need to do this because once the image is changed, and you scroll the ListView , it reappears because ListView recycles the item views. So you need to define it in getView to use a default image when the list is scrolled
Can someone explain this issue to me ?
I have a listview that holds more rows than the screen can show, so scrolling.
If I click on one item, I replace an icon that is part of each row. That all works.
The issue I have is that when I click on lets say the first item, I change the icon for that first row. When I now scroll down I see that the first row outside the visible viewport also changed the icon.
Why is that happening and how can I avoid this issue ?
Thanks in advance,
Mozzak
Just to make sure, you are using a class that implements ListAdapter or extends some other sort of adapter right?
When using an adapter, you will have to keep in mind that the views in the ListView are recycled to save memory. Because of this, you will need to store the state in a separate variable.
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null)
{
LayoutInflater inflator = (LayoutInflater)parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflator.inflate(R.layout.listitem, null);
}
// Retreive my image that may or may not change
ImageView myIcon = (ImageView) convertView.findViewById(R.id.iconView);
// Checking my stored boolean for this position to see if I need to use icon2 or icon
if (myItem[position].needsIconChanged)
{
// I have set my boolean, so use icon2
myIcon.setImageResource(R.drawable.icon2);
}
else
{
// I have not set my boolean, or set it to false so set it to icon
myIcon.setImageResource(R.drawable.icon);
}
return convertView;
}
You will also have to remember to set that boolean in your onItemCLick
public void onItemClick(AdapterView<?> myAdapter, View myView, int position, long arg3) {
// Retreive your item and set a boolean or icon state (depending on what you do)
myAdapter.getItemAtPosition(position).needsIconChanged = true;
}