OnClickListener for Buttons in GridView - android

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);

Related

Inflating views on listview item

I have a listview. What I've implemented in that listview is that when user clicks a list item a 2 button view is inflated to replace the content of that list item like this:
This works fine but what I want is when I click the second list item the first one should come back to its original layout. Currently, it is like this:
This is my code implemented in onClick method of listview:
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
TextView planName = view.findViewById(R.id.planNameText);
TextView planDate = view.findViewById(R.id.planDateText);
ImageView planImage = view.findViewById(R.id.homePlanImageView);
planName.setVisibility(View.INVISIBLE);
planDate.setVisibility(View.INVISIBLE);
planImage.setVisibility(View.INVISIBLE);
RelativeLayout rl_inflate = (RelativeLayout)view.findViewById(R.id.rl_inflate);
View child = getLayoutInflater().inflate(R.layout.inflate, null);
rl_inflate.addView(child);
}
});
Thanks.
Maybe you need to initialice a boolean variable to check if is clicked or not and refresh all the views. I really recommend you to use a RecyclerView and use 2 viewHolders. If you want information about this check this. If you implement a recycler with 2 viewholder it will be easier than the way that you want to implement it, and you can use notifyDataSetChanged to refresh the recycler. Whatever, you will need anyways a boolean to check if is clicked or not.

Android: setImageResource in adapter changing image 3 positions later

I am using AndroidSwipeableCardStack to do a tinder like interface with a 5 star rating system. When I click on a button that is meant to change the image. The image changes three cards later not in the current card.
This is the project I used: https://github.com/wenchaojiang/AndroidSwipeableCardStack
I assume it is to do with it pre-loading the next card as a recyclerview normally does but I am really not sure.
This my adapter below, you can see at the bottom I call setimageresource on the one star rating image button onclicklistner. I set a Log.d and the button click is registering immediately upon click but the change in the image resource appears only after 3 cards have been swiped.
Edit: So I noticed that regardless of the project I imported, my adapter is only extending ArrayAdatper. So I guessed that the problem might be in the getView method and that I might need to override something else (not sure). But the important point, I checked the position in getView by logging the position and sure enough it said position 0,1,2,3 without anything being swiped away. This is analogous to the offset of the setImageResource so I believe they are interlinked but unfortunately that doesn't bring me any closer to an answer.
Thanks for your help.
public class SongPreviewCardsDataAdapter extends ArrayAdapter<SongDatabaseMappingAdapter> {
public SongPreviewCardsDataAdapter(Context context, int resource) {
super(context, resource);
}
ImageButton oneStarRating;
#Override
public View getView(int position, final View contentView, ViewGroup parent) {
// Initialise Song Views
SongDatabaseMappingAdapter item = getItem(position);
TextView songName = (TextView) (contentView.findViewById(R.id.songNameTextView));
songName.setText(item.getSongTitle());
com.mikhaellopez.circularimageview.CircularImageView songImage = (CircularImageView) contentView.findViewById(R.id.songImageView);
String ImageURL = (item.getPictureURL());
Picasso
.with(this.getContext())
.load(ImageURL)
.into(songImage);
// Initialise Rating Buttons
oneStarRating = (ImageButton) contentView.findViewById(R.id.ratingButton1);
// Create OnClickListners for Ratings Buttons
oneStarRating.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
oneStarRating.setImageResource(R.drawable.starfull);
Log.d("Star", "Star Clicked");
}
});
return contentView;
}
}
getView can be called at any time on any item, so if you want something to stay set on a view, you have to model it.
I don't know if your SongDatabaseMappingAdapter class is something you can change or not, but I'll assume you can change it.
Add a variable to your item class, like boolean mOneStar. Getter/setter left as an exercise for the reader.
In getView() make your item final so you can refer to it in the onClick callback:
final SongDatabaseMappingAdapter item = getItem(position);
Use the variable in getView to set up your view:
oneStarRating = (ImageButton) contentView.findViewById(R.id.ratingButton1);
oneStarRating.setImageDrawable(null); // clear out recycled value
if (item.isOneStar()) {
oneStarRating.setImageResource(R.drawable.starfull);
}
In your OnClickListener, set the property on the item and call notifyDataSetChanged():
oneStarRating.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d("Star", "Star Clicked");
item.setOneStar(true);
notifyDataSetChanged();
}
});
The main point is: Never change a view from an event handler. Always change the item from the event handler then call adapter.notifyDataSetChanged(), and always change your view in getView() based on the item.

Changing ListView Item background bug

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

Get layout information of the view

I have a list view that uses different layout sheets for different rows. Each sheet has different variables on them. So, when I want to implement the click listener for my list I need to know which type of row I am clicking on so that I can try to access to the correct values. For example:
list.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
//do this for layout A
//......
// do that for layout B
//......
}
});
How can I access to the layout information and the corresponding values ?
Make sure that you have a variable in your activity where you've kept the data with which you've populated the adapter.
Then, based on the position variable, you'll be able to get the exact row which was clicked. Then based on the row, you'll be able to figure out what type of row it is, right? :)
you can use the method:
public abstract int getItemViewType (int position) of your custom 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