Changing ListView Item background bug - android

When I'm using parent.getChildAt(position).setBackgroundColor(Color.GRAY); in my public void onItemClick(AdapterView<?> parent, View view, int position, long id) it colorizes, but it works strange.
When I click first or second item it colorizes it... and every item away ~five records. Sometimes I've got NullPointerException. Completely weird, because position is unique and it should recieve me appropriate View, but it doesn't.
I saw solution with overriding getView method, but I'm using this adapter in different places. I just want to color clicked item. How to get reference to selected view?
EDIT:
In my adapter class I created:
public static int selectedItem = -1;
I added this to my overrided getView method:
if(selectedItem == position){
parent.getChildAt(position).setBackgroundColor(Color.GRAY);
}
In my activity I added that:
myAdapter.selectedItem = position;
myAdapter.notifyDataSetChanged();
And It doesn't work. Where I do a mistake?

It's not a bug - it's the way ListView re-uses the views to save resources.
So to avoid this behavior you should on every getView() set all used attributes for all your views.
Updated - to be quite clearIn your particular case it means that you should set color like this:
1) In onItemClick() - in your actitivity - you should remember given position as selected:
myAdapter.selectedItem = position
2) In getView() - in your adapter:
if(selectedItem == position)
parent.getChildAt(position).setBackgroundColor(Color.GRAY);
else
parent.getChildAt(position).setBackgroundColor(0);//or whatever defauld color
Update 2
If you want to select many items you should use some structure (like HashSet) to hold all the selected items:
1) In your activity class add member:
public static HashSet<Integer> mSelectedItems = new HashSet<Integer>();
2) In onItemClick() use following to flip selected state:
if(mSelectedItems.contains(position))
mSelectedItems.remove(position);
else
mSelectedItems.add(position);
3) In getView():
if(MainActivity.mSelectedItems.contains(position))
parent.getChildAt(position).setBackgroundColor(Color.GRAY);
else
parent.getChildAt(position).setBackgroundColor(0);//or whatever defauld color

At first read this article;
Then use ViewHolder pattern;
And try to setBackgroundColor() in onItemClick() like this:
view.setBackgroundColor(0);//or whatever defauld color

Related

OnClickListener for Buttons in GridView

I have a GridView adapter displaying a grid of Buttons. Now I want to set up an OnClickListener for my buttons but of course they don't have their own R.id I can access as they are added to the grid via the adapter, rather than a layout.xml.
I tried to use OnItemClickListener as follows:
m_onItemClickListener = new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int pos, long l) {
switch(pos) {
case MyConstants.POS_OF_BUTTON_1:
// Do stuff...
break;
case MyConstants.POS_OF_BUTTON_2:
// Do stuff...
break;
}
}
};
But to my understanding you can't use a clickable or focusable item with OnItemClickListener. How do I get round this? Thanks!
There are more elegant ways to do this whole thing (starting from using a RecyclerView with a GridLayoutManager instead of a GridView), but if you're looking for the quick and easy solution to use with what you already have, this is what you can do:
First of all, you should set some ID on your buttons, they don't have to come from R.id (although it would be preferable if you inflated the views from a layout, with an ID defined there, and used a ViewHolder).
Worst case, you can just define constants in your adapter for the IDs you want to use for each kind of button (e.g. static final int DELETE_BUTTON = 1;), and then set these IDs on the buttons manually, in code.
Then you can pass a simple OnClickListener (not OnItemClickListener), which handles clicks of all these different buttons in a single item, to your adapter, and make the adapter set the listener on each of these buttons, for each of the item views in the grid.
You will also need to set the position of the item as a tag on the button view itself, so that when the click happens, you can determine for which item the click happened.
Sample code as follows:
View.OnClickListener listener = new View.OnClickListener() {
#Override
public void onClick(View v) {
Object tag = v.getTag();
if (!(tag instanceof Integer)) {
// Show error message or just throw an exception.
}
int position = (Integer) tag;
// We get the item at this position, to know which one to use
Item item = adapter.getItem(position);
switch (v.getId()) {
case DELETE_BUTTON:
// Delete stuff here
break;
case EDIT_BUTTON:
// Edit stuff here
break;
...
}
}
};
adapter.setOnClickListener(listener);
Then, in the getView method of the adapter, you need to set this listener on each of the buttons and also set the position of the item as a tag on the buttons. This way, you will be able to figure out to which item the button belongs to, in the listener code above.
#Override
public View getView(int i, View view, ViewGroup viewGroup) {
...
deleteButton.setId(DELETE_BUTTON);
deleteButton.setOnClickListener(listener);
deleteButton.setTag(i);
...
}
In general, I sincerely urge you to also look into the ViewHolder pattern, and RecyclerView and GridLayoutManager when you have time. Most of this will translate there as well.
EDIT
In order to make multiple Views clickable/focusable inside a list/grid item, you need to set the descendantFocusability attribute to blocksDescendants on the root view of the item, either simply in the XML, or in code via:
viewGroup.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);

How do I retain selected item highlighting on gridview when numColumns changes?

I have an ActionBarActivity with a GridView.
The GridView has 2 columns in portrait and 3 columns in landscape.
When I select items in portrait (starting my ActionMode) and then rotate the device, the selected item highlighting shifts one item to the left. For example, if I select the second item and rotate, the first item will be highlighted. If I select the first item and rotate, no items are highlighted.
The actual selection in the code is correct, just the highlighting is wrong.
I notice it does not do this if I keep the numColumns the same for portrait and landscape.
I believe this issue started occurring after I changed my activity to an ActionBarActivity so it could be a bug..
Anyone know why or how to fix it?
I had a similar scenario and ended up solving the issue be creating a custom grid item with a boolean field to keep track of whether the item is selected or not and then highlighting the item appropriately through the custom adapter. Below is a rough outline of what I did:
(1) I created a custom grid item with a boolean field, which we will call selectedStatus for simplicity's sake. I also added the corresponding methods to my grid item class to get the selected status:
public boolean getSelectedStatus ()
{
return selectedStatus;
}
public void setSelectedStatus (boolean paramSelectedStatus)
{
this.selectedStatus = paramSelectedStatus;
}
(2) I then created a custom Adapter that extends BaseAdapter to handle the custom grid object I created. In this Adapter I check the if the selected status of the grid object is true or false and highlight the item accordingly, shown below:
#Override
public View getView (final int position, View convertView, ViewGroup parent)
{
// rest of getView() code...
if (!yourGridObject.getSelectedStatus())
{
convertView.setBackgroundColor(Color.TRANSPARENT);
}
else
{
convertView.setBackgroundColor(Color.LTGRAY);
}
// rest of getView() code...
return convertView;
}
(3) Lastly, you add the onItemClickListener to set the selected status and the background color of the grid items when they are selected (clicked):
yourGridView.setOnItemClickListener(new OnItemClickListener()
{
#Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id)
{
YourGridObject yourGridObject = (YourGridObject) parent.getItemAtPosition(position);
if (!yourGridObject.getSelected())
{
view.setBackgroundColor(Color.LTGRAY);
yourGridObject.setSelected(true);
}
else
{
view.setBackgroundColor(Color.TRANSPARENT);
yourGridObject.setSelected(false);
}
}
});
Implementing selection this way ensures that the highlighting (selection) of the grid items will not change when the number of columns and rows swap since the selection status is contained within the grid objects themselves.
You don't need to manually handle selection of items as suggested by Willis. Android fully supports what you are asking. I will assume you are using an ArrayAdapter however this answer would apply to all adapters. Note some adapters (like CursorAdapter) won't suffer from your posted problem and don't require the following solution because it's already doing it internally.
The problem is solved in two parts. One, the adapter must enable stable Ids. Two, your adapter must actually return stable ids. You will need to extend the ArrayAdapter or which ever adapter you are using. Then ensure you have defined the following methods as shown below.
private class MyAdapter extends ArrayAdapter<YourObjects> {
#Override
public boolean hasStableIds() {
return true;
}
#Override
public long getItemId(int position) {
//Return a unique and stable id for the given position
//While unique, Returning the position number does not count as stable.
//For example:
return getItem(position).methodThatReturnsUniqueValue();
}
}
Most adapters do not enable hasStableIds. It's primarily only used when enabling a choiceMode. Which I assume you are doing here. By returning true, you are essentially telling Android to keep track of activated (highlighted) items based on their ID value instead of their position number.
Even with stable Ids enabled, you have to actually return an ID that is unique and stable across positional changes. Since most adapters do NOT enable stable IDs, they usually only return the position number as the stable id. Technically, if an item's position never changes over time then the position number "could" be used as the stable id. However, the safest way to return a stable/unique ID is to have one assigned to the class object being stored in the adapter and pull from that.

Android: get position of item in a listview given its id:

getItemIdAtPosition() is a function in android used to get the id of an item in a list view given its position
is there any way of doing the reverse, i.e. getting the position of an item is a list view given its id?
No. You have to do it manually. Create a public method inside the adapter you are using; in that method, loop on the adapter items and check each item id. If it is == to the method param, then return the index.
public int getItemPosition(long id)
{
for (int position=0; position<mList.size(); position++)
if (mList.get(position).getId() == id)
return position;
return 0;
}
Update: You might as well save a HashMap for position/id in your adapter, if lookup performance represents a problem for your use case.
Update: If you want to use this method outside the adapter, then use below:
private int getAdapterItemPosition(long id)
{
for (int position=0; position<mListAdapter.getCount(); position++)
if (mListAdapter.get(position).getId() == id)
return position;
return 0;
}
Not sure if the question is still open. Nevertheless, thought I will share how I did it so that it may help someone who is looking to do the same thing.
You can use the tag feature of a view to store the mapping between that view's id and its position in the list.
The documentation for tags on the android developer site explains it well:
http://developer.android.com/reference/android/view/View.html#Tags
http://developer.android.com/reference/android/view/View.html#setTag(int, java.lang.Object)
http://developer.android.com/reference/android/view/View.html#getTag(int)
Example:
In the getView method of your list adapter, you can set the mapping for that view's id and its position in the list, something like:
public View getView (int position, View convertView, ViewGroup parent)
{
if(convertView == null)
{
// create your view by inflating a xml layout resource or instantiating a
// custom view class
}
else
{
// Do whatever you want with the convertView object like
// finding a child view that you want to set some property of,etc.
}
// Here you set the position of the view in the list as its tag
convertView.setTag(convertView.getId(),position);
return convertView;
}
To retrieve the position of the view, you would do something like:
int getItemPosition(View view)
{
return view.getTag(view.getId());
}
A point to be noted is that you do need to have a reference to the View whose position you want to retrieve. How you get hold of the View's reference is dependent on your specific case.
No. Depending on what adapter you're using to back your ListView the id and position may be the same (I haven't looked at all BaseAdapter subclasses so I cannot say for sure). If you look at the source code for ArrayAdapter you will see that getItemId actually returns the position of the object in the adapter. If the position and id are the same, there is no need to use one to get the other. Otherwise you just need to search the adapter for the object your looking for - using either position or id - and you can find the other value.
If what you're talking about is getting objects using some unique key - i.e. one that you define - that can be done. What you need to do is set up your adapter to take a HashMap instead of an ArrayList or regular List of objects. Then you can create your own method to find by key by simply pulling that value from the HashMap.
it's too late to answer but for benefit...
for example if you have listview and you want to get id with click listener you can get it by >>
cListview.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
// for id or any informations
itemID = String.valueOf(catList.get(position).getItemID());
// for name or any informations
itemName = String.valueOf(catList.get(position).getItemName());

What is the best way to change drawable of all item in ListView

Let say I have my view that I use it as a toggle button. When user clicks it, I change the background via setBackgroundResource(). The number of list is around 15 items and ListView can show only around 7 items on screen.
At first, I try to use ListView.getChildAt(position) but when position is more than 7 it returns NullPointer. eventhough ListView.getCount() returns 15. But that's make sense because it show only visible child.
Then I solve it by loop through all Data that binds to this Adapter, change the boolean value, and call notifyDataSetChange()
So the number of loop will be 15 for update data + 7 show visible view.
The best way should be 15 and that's done.
Is there anyway to achieve this?
Thank
Forget your child index. You should just toggle some type of flag in your adapter.
Then when your getView method is called again it will redraw your list.
i.e.:
public class YourAdapter extends BaseAdapter {
private boolean useBackgroundTwo = false;
.. constructor ..
#Override
public View getView (int position, View convertView, ViewGroup parent) {
...
...
View background = findViewById(...);
int backgroundResource = R.drawable.one;
if(useBackgroundTwo){
backgroundResource = R.drawable.two;
}
background.setBackgroundResource(backgroundResource);
....
}
public void useNewBackground(){
this.useBackgroundTwo = true;
notifyDataSetChanged();
}
public void useOldBackground(){
this.useBackgroundTwo = false;
notifyDataSetChanged();
}
}
Then in your activity code:
((YourAdapter) listview.getAdapter()).useNewBackground();
Taking it further, you could use an enum instead of a boolean and have multiple methods setBackgroundGreen(), setBackgroundRed() or you could pass in the drawable you want to use setItemBackground(R.drawable.one); The choice is yours.
Api: Adapter

How to get the view of a ListView item?

I have two ListViews (A and B) with items of the same type (a class I created)
When I click on an item from A, it adds this object on B and if I click again it removes it.
Only that when an item is selected, I change its background using view.setBackgroundColor(myColor).
I want to be able to remove the item from list B (it works), but I want also to reset the background color. I can't figure out how to get the view of this item I'm removing.
Any ideas?
There's no guarantee that any specific ListView item will even have a view at any given time. If the item is currently off-screen, then it may not have a view. Since a specific item might not have a view, it might not make any sense to try to get the item's view.
Beyond that, because of the way ListView creates and reuses views, you'll see some odd, undesirable effects if you simply modify the views directly. As the user scrolls through the list, items that become visible will incorrectly end up with the same backgrounds as other items that have fallen outside the visible portion.
I don't know whether what follows is the best way to implement your functionality because I don't know the cost of rebuilding the list after a change. Here's the (probably naive) way I would do this:
Add another boolean member to your data object, something like isInSecondList.
Override getView() in the Adapter. In getView(), set the background to either normal or highlighted depending on the the value of the item's isInSecondList.
When an item is added or removed from the second list, update the data object to reflect the change, then call the Adapter's notifyDataSetChanged().
int position = 0;
listview.setItemChecked(position, true);
View wantedView = adapter.getView(position, null, listview);
Here is what i did
private View oldSelection;
#Override
public void onItemClick(AdapterView<?> arg0, View view, int position,
long arg3) {
highlightSelectdListItem(position);
}
public void highlightSelectdListItem(int position) {
clearPreviousSelection();
View newsItemView = mGridVIew.getChildAt(position);
oldSelection = newsItemView;
newsItemView.setBackgroundColor(Color.GRAY);
}
public void clearPreviousSelection() {
if (oldSelection != null) {
oldSelection.setBackgroundColor(Color.TRANSPARENT);
}
}

Categories

Resources