I have a ListView lv, and i have a Button delete at the bottom of the Listview (the button is outside of the listview). There is a problem that how i can deal with the button (such as change background or set text for the button) from inside the listview. I have my own adapter, and i think that i have to handle the button's changing at the public void getView(int pos, View convertView, ViewGroup parent) function. Does anyone have any idea?
P/s: the main point of this question is: "How to deal with other elements from inside a list view?".
Any suggestion is welcome!
In the activity ,
Button delete = (Button) findViewById(R.id.delete);
CustomAdapter adapter = new CustomAdapter(delete);//add your argumente here
Now in the adapter you can set OnClickListener for the button.
Assuming you don't just save a reference to the Button from the Activity or Fragment's onCreate method, then inside getView you would need to implement a loop that walks up the ViewParent chain searching for the actual view you want. It could look something like this:
ViewParent nv = parent.getParent();
while(nv != null){
if(View.class.isInstance(nv)){
View button = findViewById(R.id.button_id);
if(button != null){
// FOUND IT!
// do something, then break;
break;
}
}
nv = nv.getParent();
}
Didn't compile or test that, but...something like that should do what you want. See the ViewParent.getParent() docs for the details.
I am guessing that you are using a custom arrayadapter. If so you could pass a reference to the button to the arrayadapter class.
I am not sure if this will let you edit the button though. If not you could send the ArrayAdapter a handler from the Activity. So in the activity create a handler with something like this:
protected Handler updateButtonHandler = new Handler() {
#Override
public void handleMessage(Message msg){
//Update button
}
};
And then in the arrayadapter have a reference and:
updateButtonHandler.sendEmptyMsg(0);
Related
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);
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.
I am trying to set one button to enabled and a different one to disabled once it is clicked on the listView. There are 2 buttons and I am trying to change them in the onClick in the getView function of my adapter.
Here is the onCLick of one of them.
holder.upButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d("Event", "Up");
View parentRow = (View) v.getParent();
ImageButton uButton = (ImageButton) parentRow.findViewById(R.id.upImageView);
ListView listView = (ListView) parentRow.getParent();
final int position = listView.getPositionForView(parentRow);
if (activity.equals("Host"))
{
} else {
}
uButton.setEnabled(false);
}
});
thank you
If you try to change the view inside an onClick() function, it will not work.
You didn't post a lot of code, so I will just describe the correct code.
First, your adapter needs to model the enabled state of the two buttons. In other words, for each list item you need two booleans, one for each button.
In your view binding method, you need to access the two booleans and set the enabled state of each button based on its model boolean value.
In your onClick(), you need to flip the state of the two booleans and call notifyDataSetChanged() to refresh the list with the new button states.
Just wondering if what I'm trying to do is possible. So i have a custom adapter for a listview. It contains a textview and two buttons. I would like one of the listview buttons to remain hidden unless a specific button is pressed on the main activity.
So far I have the listview buttons performing their intended function but I have no idea how I would even begin to get what I'm wanting.
Sorry, for clarification, I have one button completely separate from the listview that is just always there. When I press this button I would like to toggle the visibility of a button that is on each listview item all together. The best example of this that I can think of is having a list of items and a button that can toggle off the 1. 2. 3. 4. that comes in front of each item.
Create a method in your adapter for knowing you have clicked the button from your main activity like this
public void buttonIsClicked(){ //in your adapter
buttonhide.setVisibility(visibility?View.VISIBLE:View.GONE);
}
And call this method from your activity on btnclick.
like
yourAdapter.buttonIsClicked();
and call this method for notifying the adapter about the change.
yourAdapter.notifyDatasetChanged().
or
You can use an interface for listening to the clicks in main activity and implement that listener in your adapter
Set visibility gone to the button you want to hide by calling code
buttonhide.setVisibilty(VIEW.GONE);
hide it in oncreate() of your activity and make it shown on the button click event by calling code buttonhide.setVisibilty(VIEW.VISIBLE);
Below is the code
btnView.setVisibility(View.GONE);
yes it is possible to do that.
First have a Model class to back the listview data and keep a flag in that model which indicates whether to show the button in that row's data model. On certain condition change that model's flag and call notifyDataSetChanged() on adapter.
Ex:
class Model{
String label;
boolean showBtn;
}
in adapter's getView()
Model model = list.get(position)
if(model.showBtn){
btn.setVisibility(View.VISIBLE);
}else{
btn.setVisibility(View.GONE);
}
in Activity
disableButton(){
modelList.get(0).setShowBtn(false);
adapter.notifyDataSetChanged();
}
This code will hide button in first row
Add a Boolean value in your dataset which represent the Visibility state of the button.
public class Dataset {
private boolean visible;
public boolean isVisible() {
return this.visible;
}
public void setVisible(boolean visible) {
this.visible = visible;
}
//..more items
}
Then in your getView method of the Adapter check this Boolean value to show/hide the button.
boolean visibility = yourDataset.get(position).isVisible();
yourButton.setVisibility(visibility?View.VISIBLE:View.GONE);
And when the Button outside of your listview is clicked Update your dataset. And call yourAdapter.notifyDatasetChanged().
What you are attempting is: manipulating the visibility of the button declared in the Adapter from the containing activity. Simple, put a controlling variable in the activity and pass it a parameter to adapter.
Boolean mShowButton; //a controlling variable
void onCreate(Bundle savedInstanceState) {
...
mAdapter=new MyAdapter(...,mShowButton);
mButton.setOnClickListener(actionShow );
}
OnClickListener actionShow = new OnClickListener() {
#Override
public void onClick(View button) {
mShowButton=true;
mAdapter.notifyDataSetChanged();
mListView.invalidateViews();
}
};
And do this in your adapter,
Boolean showButton;
public MyAdapter(Context context, List<String> myList, Boolean showButton) {
...
this.showButton=showButton;
}
public View getView(int position, View rowView, ViewGroup parent) {
...
if(showButton){
mButtonTwo.setVisibility(View.GONE);
}else{
mButtonTwo.setVisibility(View.VISIBLE);
}
}
I have an activity which has a TextView button (btnChangeMode) that toggles the mode from "admin" to "guest". Depending on the mode chosen, I need to hide/show a button (btnAddListItems) within my listview row. The code i have, currently doesn't seem to be cutting it.
Code speaks easier, so here's the gist of my code:
My activity layout:
<FrameLayout>
<ListView> ... </ListView> <!-- which has its items populated from myCustomAdapter -->
<TextView> ... </TextView> <!-- this is my btnChangeMode -->
<FrameLayout>
nothing fancy in my activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_activity);
List listItems = ... // some method that gets objects from Database
ListView listView = (ListView) findViewById(R.id.list_view_id);
listView.setAdapter(new MyCustomAdapter(this, listItems));
}
I have a custom adapter that basically has two types of rows header & item. In the "header" row, I have button "btnAddListItems" that allows me to add items into the list view. I want this button to be visible only in admin mode.
I've overridden the necessary methods in myCustomAdapter (getviewTypeCount, getItemViewType, getCount and getView). here's the getView method:
#Override
public View getView(int position, View row, ViewGroup parent) {
if (row == null) {
if (getItemViewType(position) == ITEM_VIEW_TYPE_HEADER) {
return getHeaderRow();
} else {
return getItemRow();
}
}
if (getItemViewType(position) == ITEM_VIEW_TYPE_ROW) {
MyHolder holder = (MyHolder) row.getTag();
holder.populateNewContent();
}
return row;
}
....
private View getHeaderRow() {
View lRow = LayoutInflater.from(getContext()).inflate(R.layout.my_header, null);
mViewMode = new DetailsViewMode((Activity) getContext(), getChangeViewModeListener(lRow));
return lRow;
}
So mViewMode here is a convenience POJO class i wrote that contains btnChangeMode, a boolean variable that indicates current mode (isAdmin) and the caller activity. I don't believe there's anything specific to my problem, so i'm not including that code here. Will be glad to if someone thinks that'll help.
private ChangeViewModeFragment.ChangeViewModeListener getChangeViewModeListener(final View headerRow) {
return new ChangeViewModeFragment.ChangeViewModeListener() {
#Override
public void onViewModeChanged(boolean isViewModeAdmin) {
mViewMode.changeViewModeButtonText(isViewModeAdmin);
toggleAdminFeatures(isViewModeAdmin, headerRow);
}
};
}
private void toggleAdminFeatures(boolean isViewModeAdmin, View headerRow) {
TextView btnAddListItems = (TextView) headerRow.findViewById(R.id.add_button_id);
if (isViewModeAdmin) {
btnAddListItems.setVisibility(View.VISIBLE);
} else {
btnAddListItems.setVisibility(View.GONE);
}
}
This is the part that's not working as it should. btnAddListItems is always visible within my listview.
btnChangeMode is within my activity so to speak, while the btnAddListItems is within my Adapter (ListView). But my requirement necessitates this behavior of having the listner of an activity, change the row state of my listview.
I suspect that when i change the visibility of my header row's button, I don't have hold of the correct header row instance, if that makes sense :P.
NitroNbg's suggestion of having a private button didn't work, which leads me to believe that maybe the ListView just needs a kickstart to get refreshed?
But I've tried calling notifyDataSetChanged() at the end of my toggleAdminFeatures method but that doesn't seem to be doing the trick.
I'd try the following - create a private Button within your adapter class and within your getView() method, put a reference to btnAddListItems to it.
private Button buttonToHide;
//...
public View getView(...) {
//...
buttonToHide = (Button) row.findViewById(R.id.add_button_id);
//...
}
Then, inside your ChangeViewModeListener() simply refer to a method of your adapter class (of course you'll have to write it) that sets the buttonToHide.setVisibility(View.INVISIBLE)
Hopefully, since it's within the adapter it would be accessible.
EDIT: Just to point out if it isn't obvious - only refer to the button that's in the header row.
You don't need to notifyDataSetChanged(), coz dataset hasn't changed, try setting the visiblity to INVISIBLE