I am trying to get the index of a spinner that is inside my inflated view.
Although I'm successful in retrieving the index of my child view when it's inflated on a button click like this:
itineraryDetailLL = (LinearLayout) findViewById(R.id.itineraryDetailLinearlayout);
childView = getLayoutInflater().inflate(R.layout.cardview, null);
itineraryDetailLL.addView(childView);
int posValue = itineraryDetailLL.indexOfChild(childView);
Toast.makeText ( PlanItineraryDetailView.this, Integer.toString(posValue), Toast.LENGTH_SHORT ).show();
This bit of code returns me 0 for 1st inflated view, 1 for 2nd inflated view and so on...
Problem
But when I'm trying to get the index position of the spinner (by calling the setOnTouchListener event) in each of my child view it returns me the last index position for each spinner
spinnerPlanItinerary.setOnTouchListener(new View.OnTouchListener(){
#Override
public boolean onTouch(View v, MotionEvent event) {
int indexValueTown = itineraryDetailLL.indexOfChild(childView);
Toast.makeText ( PlanItineraryDetailView.this, Integer.toString(indexValueTown), Toast.LENGTH_SHORT ).show();
return true;
}
});
This returns me 1 for spinner in my 1st inflated view, 1 for spinner my 2nd inflated view (It should return 0 for spinner in my 1st inflated view, 1 for spinner in my 2nd inflated view).
I hope I'm clear with my problem.
Note I'm inflating only two views only for testing right now.
Posting as answer so someone who stumble here might get solution.
The problem in your case was your itineraryDetailLL & childView were global variables. That means whenever you add new item via button click, there value was updating to the last inflated view. now whenever you touch on Spinner, you were getting the last index as expected.
So making them local & final variable made sure that you refer to the same view when you touch the spinner.
I didn't understood your requirement or what you were trying to do fully though. There might be better way of achieving this.
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 know this question has been asked before and got an understanding roughly of whats happening but i cant seem to find a solution.
In my Custom List Adapter and inside public View getView(int position, View convertView, ViewGroup parent) { ive setup a click function for the items ImageButton.
final ImageButton bookmark = (ImageButton)
convertView.findViewById(R.id.bookmarkthis);
bookmark.setTag(position);
bookmark.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
bookmark.setImageResource(R.drawable.ic_bookmarked);
bookmark.setTag(R.drawable.ic_bookmarked);
}
});
The Imagebutton is a clear star and when Clicked it changes that same drawable to a yellow star. It works Ok.
Problem is when i click Item in Position 0 Star Icon to make yellow , it also changes for item 8 further down the list not in View Yet. if i click position 1 changes also for position 9 and so on.
I had a look around and researched the issue and even tried a holder for the Imagebutton but no go. Something is preventing the drawable change for the ImageButton to its correct Position only.
Thanks
Solution is to initially Set a flag item in the array for each Item. Then onClick set the flag to true just for that item. Then in the getView its just a case of an if statement to check the flag as the Items get Cycled.
//in array creation
items.setFlag("false");
//in getView as you set Text and what ever get the Flag state
String flag = m.getFlag();
//check the flag state and take action in this case change the icon accordingly
if (Objects.equals(flag, "true")) {
bookmark.setImageResource(R.drawable.ic_bookmarked);
bookmark.setTag(R.drawable.ic_bookmarked);
}
else {
bookmark.setImageResource(R.drawable.ic_bookmark);
}
// and in the click function
#Override
public void onClick(View v) {
bookmark.setImageResource(R.drawable.ic_bookmarked);
m.setFlag("true");
}
You need to reset your bookmark image resource & tag values a non-null convertView is passed in to getView. Your onClick handler is setting them to these values:
bookmark.setImageResource(R.drawable.ic_bookmarked);
bookmark.setTag(R.drawable.ic_bookmarked);
So you'll need to reset them to the default values when showing a new list item.
ListView reuses the views that fall out of range. You don't reset the "bookmark" icon when reusing the views (when you get a non-null convertView). Make sure to always reset all properties of your views to the correct values, and you won't have a problem.
I have a method that paint the View of selected item but if my ListView has a scroll ( i mean: if my screen can display 9 "lines" - positions - and the total size is bigger than 9 ) and i select one the first nine positions, it will select the position that i choose and one from the last positions ( a position that i need to scroll to see ).
Example: if i select select the position 0 and paint it, the view on position 0 and on position 11 will be painted.
If i try do to:
getListView().getChildAt(int)
And if this position is like '12', it will return null so i'm using the View that i get when onItemLongClick(AdapterView adapterView, View v, int position , long arg3) is called. It looks like that View is based on scroll because i have same View objects for scroll positions.
Here is how i paint the Views:
listView.setOnItemLongClickListener(new OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> adapterView, View v,
int position , long arg3) {
Log.d("", "");
View tmp = adapterView.getChildAt(position);
int oi = adapterView.getSelectedItemPosition();
if(!mPositions.contains(Integer.valueOf(position))){
v.setBackgroundColor(Color.CYAN);
mViews.add(v);
mPositions.add(Integer.valueOf(position));
} else {
v.setBackgroundColor(Color.TRANSPARENT);
mViews.remove(v);
mPositions.remove(Integer.valueOf(position));
}
return true;
}
});
How can i get distinct views independently if the list is bigger than my screen?
The problem here is that you really shouldn't be modifying the view directly. The whole point of a ListView is that it will create a view for an item, and then potentially use this same view to paint each and every one of the items in the list. (row recycling). It is possible here, that when you modify the background colour of the selected item's view, this view object is then actually being used to draw another row's items - looking like it's just doing it for the wrong row.
What you should do, is create a custom adapter with your own implementation of getView(). Inside this function you will do one of two things: If convertView is null, then create the View that you desire (possibly a TextView?), otherwise you will grab the view from convertView. Finally you will then set this view up however you would like based on it's position in the list. Ie: if it's text, set the value, and then set your background.
To respond to the longpress, simply change your data (mSeleted) and call notifyDataSetChanged() so that the list will redraw based on this new data.
This is a pretty simplistic overview, listviews can get as complicated as you want. There are many examples of how to create a custom listview on the web if you search.
I'm getting some strange behavior from a listview/the getChildAt method.
I have a HashSet, iconsToUpdate, of icons that have been changed in the database. I want to iterate over the visible rows to see if any of their icons need to be updated to reflect the new icons. I don't need to test the icons not currently in view as they will be drawn properly when rendered.
My problem is that getChildAt is returning null when it seems like it shouldn't. I know that getChildAt can only return views that are currently visible, but it is returning null for some of the visible rows.
Here is my code that iterates over the visible rows:
Logger.debug("First visible index: " + f_listView.getFirstVisiblePosition());
Logger.debug("Last visible index: " + f_listView.getLastVisiblePosition());
for (int i = f_listView.getFirstVisiblePosition(); i <= f_listView.getLastVisiblePosition(); i++) {
String tag = "asdf"; // Remove when bug is fixed.
if (f_listView == null) {
Logger.debug("f_listView is null");
} else if (f_listView.getChildAt(i) == null) {
Logger.debug("Child at index " + i + " is null");
} else {
tag = (String) f_listView.getChildAt(i).getTag();
Logger.debug("Successful at index " + i + ", tag is: " + tag);
}
if (iconsToUpdate.contains(tag)) {
setIcon(i, f_aim.getInHouseIcon(tag));
}
}
Here is the log corresponding to a run of this loop:
D/...: First visible index: 3
D/...: Last visible index: 8
D/...: Successful at index 3, tag is: ...
D/...: Successful at index 4, tag is: ...
D/...: Successful at index 5, tag is: ...
D/...: Child at index 6 is null
D/...: Child at index 7 is null
D/...: Child at index 8 is null
It should be noted that the first and last visible indexes are being correctly reported, as I am viewing rows 3-8 when I run this. Rows 6, 7, 8 are being rendered properly. How are they being displayed if they are null?
Also, I do not know if this is important, but row 5 is the last visible row when I am at the top of the listview.
Any info as to why these rows are being returned as null would be greatly appreciated.
Thanks!
listView.getChildAt(i) works where 0 is the very first visible row and (n-1) is the last visible row (where n is the number of visible views you see).
The get last/first visible return the position in the dataAdapter you have. So you since you start at position 3, with what looks like 6 visible views, that's when get for positions 6-8 you get null.
In your example getChildAt(0) would return position 3. What I usually do is store the position on my views so I can lookup on my dataAdapter later if I need values.
I think your for loop should look like this:
for (int i = 0; i <= f_listView.getLastVisiblePosition() - f_listView.getFirstVisiblePosition(); i++)
Try
f_listView.getChildAt(positionOfChildYouWantGet - f_listView.getFirstVisiblePosition());
When you call listView1.getLastVisiblePosition() and listView1.getFirstVisiblePosition(), the listview returns the positions of the items that are partially visible in the listview. For example, the first item may be half visible and the last item may be half visible. In this case, even though you can see part of the item in the listview, the adapter has not yet called the getView() function for the item and therefore the item is still considered null.
In my case i have a listView and the first item is full visible but when I do getFirstVisiblePosition() the result is 1 !
It gets more weird when I scroll and make the first item half visible then my getView show that getFirstVisiblePosition() is 0.
this my code :
public View getView(final int position, View convertView, final ViewGroup parent) {
row = convertView;
final AtomPaymentHolder holder = new AtomPaymentHolder();
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
row = inflater.inflate(layoutResourceId, parent, false);
row.setTag(items.get(position));
holder.songitem = items.get(position);
final int firstPosition = MainActivity.listviewSong.getFirstVisiblePosition() - MainActivity.listviewSong.getHeaderViewsCount();
final int lastPosition = MainActivity.listviewSong.getLastVisiblePosition();
if(MusicService.indexService>= firstPosition && MusicService.indexService<= lastPosition){
MainActivity.listviewSong.getChildAt(MusicService.indexService-firstPosition).setBackgroundColor(getContext().getResources().getColor(R.color.pressed_color));
}
(when I selected the next item and scroll, it works good)
They are partially visible maybe. But when the getView() tries to recycle the views it gets FULLY visible ones other takes them as null. for example if you scroll the list and the top item is partially visible and the bottom is partially visible so these are nulls but you still see them.
I tried by ever mean in OnListItemClickListener(),but fails. At last I made some modification in my customized adapter for listview. Here in getView(),I apply clickListener on the item which i added into the list frequently. n do all required functionality there. Here is my Code, where i add Image View in the list n so apply listener on imageview.
I Think it will help those who want to change the color when specific List Item is >selected. Go For it..
In getView() of customized Adapter
//---------------------------------code------------------------------------------
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.icon_image_layout, parent, false);
ImageView imageView = (ImageView) rowView.findViewById(R.id.Icon_ImageView);
imageView.setClickable(true);
final int pos=position;
imageView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
// TODO Auto-generated method stub
try{
if(previous_view!=null)
previous_view.setBackgroundColor(Color.WHITE);
}catch (Exception e) {
System.out.println("Exception Occurs Previous View");
}
v.setBackgroundColor(Color.RED);
MainActivity.imageView.setImageResource(MainActivity.Image_Name[pos]);
previous_view=v;
return false;
}
});
I know this is very old post. But I'm answering because people are still looking for a work around on ListView getChildAt() null pointer exception.
This is because the ArrayApdater is REMOVING and RECYCLING the views that are not visible yet on the ListView because of height. So that if you have 10 item views, and ListView can display 4 - 5 at a the time :
The Adapter REMOVE the item views at position 5 to 9, so that any attempt to adapter.getChildAt(5... to 9) will cause null pointer exception
The Adapter also RECYCLE the item view, so that any reference you made on position 3 for example will be lost when you scroll down to 5 to 9, and also any Input that you make on position 3 (EditText, Checkbox, etc.) will be recycled when you scroll down to 5 to 9 and will be reused at another position later (ex position 1, 2 or 3, etc.) with the same value
The only way I found to control this is to forget about getting the View and to have :
Attribute HashMap<Integer, ImageView> iconViews or any type you want for handling the values you want to use for each item on the list. The first type must be unique for item like item->getId() or position. Initialize it with new HashMap<>() in the Constructor;
in getViews make iconViews.put(position, iconView);
Prevent Adapter from using recycled convertView, remove condition if(convertView == null) so that adapter always inflate a brand new view instance. Because the view instance is new each time, you must set the value from HashMap each time also like if it already contains the key if(iconViews.containsKey(position)){iconView = iconViews.get(position))};. Probably in this case there is not tons of Items, so that smooth scrolling won't be a must.
And finally create public methods to get the Values outside of Adapter passing item->getId() Integer as parameter. Ex : public ImageView getIconViewAt(int position) { return iconViews.get(position); } . It will be easy then to select Item from Adapter
See more from my answer.
Try
if (f_listView.getChildCount() > 0){
f_listView.getChildAt(i)
}
Isn't it because you are saying
f_listView.getChildAt(i)
And you should be retrieving the item at that position?
f_listView.getItemAtPosition(i)
I have created a custom Array Adapter to bind a custom row that contains some static text and an editable EditText. I am trying to register to be notified when the user changes the text within the edit text and when notified to determine which ArrayList row the modified EditText corresponds to.
In the past with other types of views such as a Spinner I could simply put a reference to the parent view and the row number into the tag for the Spinner view. And then when I was notified that the value changed I read the tag to determine how to correlate it back to the master ArrayList.
The problem with registering to be notifed with an EditText change is that you do not get back a view but instead get a TextWatcher and I have no way to correlate back to the parent view or ArrayList row.
What is the technique that you need to use in this circumstance?
You can use onEditorAction on your EditText in your ArrayAdapter:
mEditText.setOnEditorActionListener(new OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView view, int actionId, KeyEvent event) {
// Parse parent tree to find position of view
int position = 0;
View v = null;
while (view != v && position < mListView.getChildCount())
v = mListView.getChildAt(position++);
// do something
something(position);
// do not consume the action
return false;
}
});
Note that using this method, you are going to trigger an event only when user press "ok", "enter", "done", etc. on the keyboard.