Overview:
I have a ListView with a custom adapter/layout, every time a user adds a new row (which contains a number), I check if that number is the smallest in the list. If so, an image within that row must be set as visible while setting all other row's images as invisible.
Problem:
My ListView does not set any row's image as visible, even though I have the index of the smallest element.
How I'm doing it:
//In MainActivity
private void addProduct(float price) { //User adds product
priceList.add(price); //Add to Float list
adapter.notifyDataSetChanged();
updateView(findMinIndex(priceList)); //Find smallest val indx
}
private void updateView(int index){
View v = listView.getChildAt(index -
listView.getFirstVisiblePosition());
if(v == null)
return;
ImageView checkMark = (ImageView) v.findViewById(R.id.check_mark);
checkMark.setVisibility(View.VISIBLE); //Initially set Invisible
}
Edit, CustomAdapter:
public CustomList(Activity context,
ArrayList<Float> priceList) {
super(context, R.layout.list_single, priceList);
this.context = context;
priceList = priceList;
}
#Override
public View getView(int position, View view, ViewGroup parent) {
LayoutInflater inflater = context.getLayoutInflater();
View rowView = inflater.inflate(R.layout.list_single, null, true);
TextView price = (TextView) rowView.findViewById(R.id.new_price);
ImageView cheapest = (ImageView) rowView.findViewById(R.id.check_mark);
price.setText(priceList.get(position) + "");
return rowView;
}
Thank you
It is your priceList binded with the adapter?
First of all i would put a breakpoint to see if you are getting the right view in the updateView method.
try this way;
Create a Pojo class with imageview and it's state(Visibility) initially set all to invisible
Add your items to the ArrayList of Pojo Class type.
when user enters a new row based on your requirement set visibility state to true or false(visible or invisible) and call notifyDataSetChanged() to the adapter.
Doing this way you can have a easy track of the items.
I got it working :).
Problem is that adapter.notifyDataSetChanged(); is async, so while it's doing that, updateView(findMinIndex(priceList)); runs but doesn't find the new row as it should. Therefore, I add a runnable to the ListView object as so:
adapter.notifyDataSetChanged();
listView.post( new Runnable() {
#Override
public void run() {
updateView(findMinIdx(priceList));
}
});
Now it works perfectly!
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.
I have a listview which consists of name, text and imageview. If an user named "John" clicks the imageview in the row with user named "Bob", all the rows with name "Bob" inclusive of the currently clicked row should have their imageview changed into another image. I am trying to do this in the following code:
private class MyListAdapter extends ArrayAdapter<CommentInfo> {
public MyListAdapter()
{
super(getActivity(), R.layout.listview_xml, myComments);
}
#Override
public View getView(final int position, final View convertView, final ViewGroup parent)
{
itemView = convertView;
Bundle bundle = new Bundle();
if(itemView == null)
{
itemView = layoutInflater.inflate(R.layout.listview_xml, parent, false);
}
final CommentInfo currentComment = myComments.get(position);
List<View> viewList;
if(!ht.containsKey(currentComment.userName)){
viewList = new ArrayList<View>();
viewList.add(itemView);
Log.d("username", currentComment.userName);
Log.d("position", Integer.toString(position));
ht.put(currentComment.userName, viewList);
}
else{
((List<View>)ht.get(currentComment.userName)).add(itemView);
Log.d("username", currentComment.userName);
Log.d("position", Integer.toString(position));
}
final ImageView follows = (ImageView) itemView.findViewById(R.id.followUserBtn);
follows.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(follows_flag == 0){
follows_flag = 1;
followed_person = currentComment.userName;
follows.setImageResource(R.drawable.followusersuccesssbtn);
List<View> viewList1 = (List<View>) ht.get(currentComment.userName);
for(View view : viewList1){
ImageView follows_other = (ImageView)view.findViewById(R.id.followUserBtn);
follows_other.setImageResource(R.drawable.followusersuccesssbtn);
}
new StoreFollowed().execute();
}
else{
follows_flag = 0;
followed_person = currentComment.userName;
follows.setImageResource(R.drawable.followusericon);
List<View> viewList1 = (List<View>) ht.get(currentComment.userName);
for(View view : viewList1){
ImageView follows_other = (ImageView)view.findViewById(R.id.followUserBtn);
follows_other.setImageResource(R.drawable.followusericon);
}
new DeleteFollowed().execute();
}
}
});
return itemView;
}
}
In the above code, I store in hashtable, for each name, list of views. My issue is when John clicks on the imageview in the row with name Bob, all the rows with imageview gets its image changed rather than the rows with only Bob's name. What is the wrong I am doing here? How to resolve the issue?
First don't keep an association between your data and your view. Android ListView reuses view which means that you will have some views associated each with multiple names. I'm pretty sure your problem comes form this.
You should alway try to only modify your Model Objet and let the list view update the UI.
In you case You could have All your CommentInfo objet referencing a User with different CommentInfo from same user refrencing the User object. You can then store you image in this User and when you wan't in on click update the user's image and call notifyDataSetChanged(); so that your list update it's UI.
PS : you might wan't to take a look at ViewHolder. It has nothing to do with you problem but it's a good practice and would increase the smoothness of your list's scrolling.
I have a custom listview which has an ImageView on the left, followed by a EditText and on the right I have another ImageView which, at first, has not any source.
Then I have an onItemClickListener on this View. I would like to change the source of the ImageView on the right (the one that initially hasn't any source) on the item click.
For this I have implemented this code:
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
ImageView icone2 = (ImageView) view.findViewById(R.id.icone2_lugar);
icone2.setImageResource(R.drawable.flag_origem);
}
The ImageView is succefully inserted on the item clicked. However, when I scroll down the list, I realized that all the other elements which were at the same position of the one clicked before I scroll the list, also changed the image resource.
Why is it happening? I guess Android loads new views as scroll the list instead of loading all elements views at the same moment, so I guess I can't change the resource of a element in a list by taking the View parameter of onItemClick.
So how could I change the resource of the clicked element in a list view?
EDIT:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View linha = convertView;
ArmazenadorLugar armazenador = null;
if (linha == null) {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
linha = inflater.inflate(R.layout.linha_lugar, parent, false);
armazenador = new ArmazenadorLugar(linha);
linha.setTag(armazenador);
} else {
armazenador = (ArmazenadorLugar) linha.getTag();
}
armazenador.popularFormulario(mListaLugares.get(position), mContext);
return linha;
}
public LugarAndroid getItem(int position) {
return mListaLugares.get(position);
}
I ended up doing it in a different way. Don't know if it is the better but it works.
I created a variable (imgPosition) in my adapter which will hold the element position which has to set the image resource. Then, whenever I click onto an element, the listener calls
adapter.setImgPosition(position);
adapter.getView(position, view, parent);
I called the getView method in the listener to refresh the list, because getView calls a method (I'll explain it next) to update the list.
In the getView method of the adapter class I have the following logic (the position element comes from getView parameter):
holder.setValues(mListaLugares.get(position), mContext, imgPosition == position);
In the holder:
static class Holder{
private TextView txt1= null;
private ImageView img1 = null;
private ImageView img2 = null;
Holder(View linha) {
txt1= (TextView) linha.findViewById(R.id.txt1);
img1 = (ImageView) linha.findViewById(R.id.img1 );
img2 = (ImageView) linha.findViewById(R.id.img2);
}
void setValues(LugarAndroid lugar, Context mContext, boolean setImg) {
img2.setImageResource(0);
if(setImg) img2.setImageResource(R.drawable.flag);
// Logic to fill up the others views
}
This way I will only set the image resource of the element clicked when the boolean passed is true
If you are using an adapter with a List of Object, you can use :
getItem(position) and then change the image of the imageview of this specific object
I am trying to implement a "favorite item" feature on my listview.
When user touches the "favorite" imageview of any row, if item was not a favorite, it becomes a favorite, and if item was a favorite, it becomes a non favorite AND the row disappears from the view. (There is a separate view in my app where user can set back the favorite to true if he wants to)
For each list item I am using Sharedpreferences to store if it is a favorite or not.
I am handling the clicklistener in my listadapter, not in my list activity.
Code of my ListAdapter:
public class ListItemsAdapter extends ArrayAdapter<ListItems> {
int resource;
String response;
Context context;
//Initialize adapter
public ListItemsAdapter(Context context, int resource, List<ListItems> items) {
super(context, resource, items);
this.resource=resource;
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
LinearLayout ll;
//Get the current alert object
final ListItems i = getItem(position);
//Inflate the view
if(convertView==null)
{
ll = new LinearLayout(getContext());
String inflater = Context.LAYOUT_INFLATER_SERVICE;
LayoutInflater li;
li = (LayoutInflater)getContext().getSystemService(inflater);
li.inflate(resource, ll, true);
}
else
{
ll = (LinearLayout) convertView;
}
// to display favorite icon on each row
ImageView favo = (ImageView)ll.findViewById(R.id.favView);
SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(getContext());
if (sPrefs.getBoolean("fav"+i.id, false)==false)
favo.setBackgroundResource(R.drawable.icon_favoriteno);
if (sPrefs.getBoolean("fav"+i.id, false)==true)
favo.setBackgroundResource(R.drawable.icon_favoriteyes);
// listener of the imageview to handle the user's touch
final ImageView fav = (ImageView)ll.findViewById(R.id.favView);
fav.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(getContext());
if (sPrefs.getBoolean("fav"+i.id, false)==false) {
Toast.makeText(getContext(), R.string.addedToFavorite, Toast.LENGTH_SHORT).show();
fav.setBackgroundResource(R.drawable.icon_favoriteyes);
SharedPreferences.Editor editor = sPrefs.edit();
editor.putBoolean("fav"+i.id, true);
editor.commit();
}
else if (sPrefs.getBoolean("fav"+i.id, false)==true) {
Toast.makeText(getContext(), R.string.removedFromFavorite, Toast.LENGTH_SHORT).show();
fav.setBackgroundResource(R.drawable.icon_favoriteno);
SharedPreferences.Editor editor = sPrefs.edit();
editor.putBoolean("fav"+i.id, false);
editor.commit();
}
}
});
return ll;
}
The display of the icon wether it is set as a favorite or not works well. What I don't manage to do is to make the row disappear as soon as the user sets the favorite icon as no.
I have tried to add notifyDataSetChanged(); in the onclicklistener but it does nothing.
It is possible that I don't get what I want because I am trying to do this in my ListAdapter class and not my Activity class.
The problem is that each row has several icons each with an onclicklistener, so I think I can't use the activity class to handle the click, but maybe I am wrong
Do you actually remove it from the data set of the adapter?
Because I can't see the remove call to your ArrayAdapter in your code of the OnClickListener.
The adapter itself cares only about it's data set - not how you paint the view (which is what you do with your favorites.
According to your question, just to remove a row from list view,
As you are assigning ListItems in ListItemsAdapter's constructor,
You just need to remove the item from ListItems, and reassign the adapter to listview or call notifyDataSetChanged for the adapter,code is as below,
items.remove(location);
adapter = new ListItemsAdapter(this,1, items);//not sure about integer parameter so I just put 1
list.setAdapter(adapter);
or
items.remove(location);
adapter.notifyDataSetChanged();
either of above two options should work.
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