I need to find out the pixel position of one element in a list that's been displayed using a ListView. It seems like I should get one of the TextView's and then use getTop(), but I can't figure out how to get a child view of a ListView.
Update: The children of the ViewGroup do not correspond 1-to-1 with the items in the list, for a ListView. Instead, the ViewGroup's children correspond to only those views that are visible right now. So getChildAt() operates on an index that's internal to the ViewGroup and doesn't necessarily have anything to do with the position in the list that the ListView uses.
See: Android ListView: get data index of visible item
and combine with part of Feet's answer above, can give you something like:
int wantedPosition = 10; // Whatever position you're looking for
int firstPosition = listView.getFirstVisiblePosition() - listView.getHeaderViewsCount(); // This is the same as child #0
int wantedChild = wantedPosition - firstPosition;
// Say, first visible position is 8, you want position 10, wantedChild will now be 2
// So that means your view is child #2 in the ViewGroup:
if (wantedChild < 0 || wantedChild >= listView.getChildCount()) {
Log.w(TAG, "Unable to get view for desired position, because it's not being displayed on screen.");
return;
}
// Could also check if wantedPosition is between listView.getFirstVisiblePosition() and listView.getLastVisiblePosition() instead.
View wantedView = listView.getChildAt(wantedChild);
The benefit is that you aren't iterating over the ListView's children, which could take a performance hit.
This code is easier to use:
View rowView = listView.getChildAt(viewIndex);//The item number in the List View
if(rowView != null)
{
// Your code here
}
A quick search of the docs for the ListView class has turned up getChildCount() and getChildAt() methods inherited from ViewGroup. Can you iterate through them using these? I'm not sure but it's worth a try.
Found it here
listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, final View view, int position, long id) {
View v;
int count = parent.getChildCount();
v = parent.getChildAt(position);
parent.requestChildFocus(v, view);
v.setBackground(res.getDrawable(R.drawable.transparent_button));
for (int i = 0; i < count; i++) {
if (i != position) {
v = parent.getChildAt(i);
v.setBackground(res.getDrawable(R.drawable.not_clicked));
}
}
}
});
Basically, create two Drawables - one that is transparent, and another that is the desired color. Request focus at the clicked position (int position as defined) and change the color of the said row. Then walk through the parent ListView, and change all other rows accordingly. This accounts for when a user clicks on the listview multiple times. This is done with a custom layout for each row in the ListView. (Very simple, just create a new layout file with a TextView - do not set focusable or clickable!).
No custom adapter required - use ArrayAdapter
int position = 0;
listview.setItemChecked(position, true);
View wantedView = adapter.getView(position, null, listview);
This assumes you know the position of the element in the ListView :
View element = listView.getListAdapter().getView(position, null, null);
Then you should be able to call getLeft() and getTop() to determine the elements on screen position.
Related
I am having trouble with my code of putting a section header in my listview on more than one position.
this is my code,
to tell the adapter there are two types,
#Override
public int getViewTypeCount() {
return 2;
}
#Override
public int getItemViewType(int position) {
if (position == MainClass.addHeader) {
return 0;
} else {
return 1;
}
}
my getView method for the adapter,
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (position == MainClass.addHeader) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.heads, null);
}
} else {
// Normal views are handled by the adapter
convertView = super.getView(position, convertView, parent);
}
return convertView;
}
Now the problem is that, the value of static variable MainClass.addHeader is initially 0 so the section header shows at position 0 , and that is correct.
But as I scroll through the list view the value of MainClass.addHeader changes several times , each time the value of MainClass.addHeader is changed, the section header is added on that position. but the problem is that the section header is disappeared from the previous positions when appear on new position.
for example,
initially, the value of MainClass.addHeader is 0 and section header appears at that position.
I scrolls down, the value of MainClass.addHeader change to 8 , the section header appears at that position but disappears from 0 position and onwards.
How to make my section header not to disappear from previous positions ?
The header is ALWAYS at position 0. If you want to add a different element to other places in your list, you should just make a custom adapter, and adjust that particular element that way.
Bottom line, the header is designed to remain at the top of your list (you could use a footer for the bottom), and is not designed to be inserted into the middle of a list. You are trying to use it incorrectly.
If you are looking for a "Sticky Header" - ie. something that always remains on the screen, even when you scroll the list, you should create a unique view, and place it above your ListView in your layout. You shouldn't use a list header for this, as it is specifically designed to scroll WITH the list.
How may I get all children of an item of ListView ?
There are methods to get child at position but I was not able to find any method which returns collection of all children in an item of ListView.
Update : I just found out that I need ListView items(rows) instead. How may I achieve this ?
What I am doing is, comparing the selected ListView item with all items.
You get all list item from list adapter through iteration as below
for (int i=0;i<adapter.getCount();i++){
adapter.getItem(i);
}
Update : You can compare specific item using index with list adapter all items as below :
for (int i=0;i<adapter.getCount();i++){
if(adapter.get(selectedIndex)==adapter.getItem(i)){
// TODO : write code here item match
break; // after match is good practice to break loop instead compare rest of item even match
}
}
Use this method to get all insight and out-sight view of list view:
for ( int i = 0 ; i < listView.getCount() ; i++){
View v = getViewByPosition(i,listView);
}
public View getViewByPosition(int position, ListView listView) {
final int firstListItemPosition = listView.getFirstVisiblePosition();
final int lastListItemPosition =firstListItemPosition + listView.getChildCount() - 1;
if (position < firstListItemPosition || position > lastListItemPosition ) {
return listView.getAdapter().getView(position, listView.getChildAt(position), listView);
} else {
final int childIndex = position - firstListItemPosition;
return listView.getChildAt(childIndex);
}
}
I guess if your items in the ListView are of type ViewGroup you could do the following
ArrayList<View> children = new ArrayList<View>();
for (int i = item.getChildCount() - 1 ; i>=0; i--) {
children.add(item.getChildAt(i));
}
May be you are looking for something like this. You need to have access to the rootView from which you can get the child views. onItemSelected is only used as it gives the rootview of clicked position.
public void onItemSelected(AdapterView<?> parent, View view, int pos,long id) {
ImageView imgView = view.findViewById(R.id.myImageView);//your imageview that is inflated inside getView() of adapter
//similarly for other views
}
Android GridView is quite interesting, it reuses the child views. The ones scrolled up comes back from bottom. So there is no method from GridView to get the child view by its position. But I really need to get view by its position and do some work on it. So to do that, I created an SparseArray and put views by their position in it from getView of BaseAdapter.
SparseArray<View> ViewArray = new SparseArray<View>();
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if (view == null)
view = li.inflate(layoutID, null);
ViewArray.put(position, view);
}
Now, I can get all visible views by their position. Everything works perfect as it should but in some devices, the first child view(position 0) is not same as the one in array. I logged the getView and found that for position 0, getView got called many times and each time array was set with different view. I have no idea why GridView is calling getView for position 0 many times and that happens only on few devices. Any solution ?
After reading source of getPositionForView, I have wrote this method in own GridView, works perfect by API 18
public View GetViewByPosition(int position) {
int firstPosition = this.getFirstVisiblePosition();
int lastPosition = this.getLastVisiblePosition();
if ((position < firstPosition) || (position > lastPosition))
return null;
return this.getChildAt(position - firstPosition);
}
You can't reach the views directly because of the recycling. The view at position 0 may be re-used for the position 10, so you can't be sure of the data present in a specific view.
The way to go is to use the underlying data. If you need to modify data at position 10, then do it in the List or array under your adapter and call notifyDataSetChanged() on the adapter.
if you need to have different views for different data subtypes, you can override the two following method in your adapter: getItemViewType() and getViewTypeCount()
Then, in getView() you can 1) decide which layout to inflate 2) know the type of view recycled using getItemViewType()
You can find an example here:
https://stackoverflow.com/a/5301093/990616
There was an issue reported for this. Here is the link. This issue has been closed as WorkingAsIntended. Wish means we can expect the GridView to call getView() on pos 0 multiple times.
My work around is as follow:
public class GridAdapter extends BaseAdapter {
...
int previouslyDisplayedPosition = -1;
...
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(previouslyDisplayedPosition != position) {
....
previouslyDisplayedPosition = position;
}
return convertedView;
}
What I am trying to do here is returning the same 'convertView' if same pos is called again and again. There by preventing any logic within getView() (eg setting image view etc)to be executed again and again.
i want to change color of listview (simple_list_item_2 adapter) children at specific positions programmatically. (here for example all items with a = lv.getCount();)
ListView lv = getListView();
int a = lv.getCount();
for (int i = 0; i < a; i++) {
((TextView) lv.getChildAt(i).findViewById(android.R.id.text1)).setTextColor(Color
.parseColor("#EEC900"));
}
getChildAt(); doesnt always work for me. in case of the list-item being out of sceen, getChild doesnt return a view or something..
isnt there a better solution instead if getChildAt?
You will have to do this in your Adapter class. Android caches and re-cycles the views in a listview to conserve memory. So there are are no views that you can't see to change the color of.
So for example if you had an arrayAdapter, you would override the getView function and run your check there:
#Override
public View getView(int position, View view, ViewGroup parent) {
// use "position" to determine which item you have.
// Then set the properties of "view" which is your list row.
}
After I have gotten the data for a single row of a ListView, I want to update that single row.
Currently I am using notifyDataSetChanged(); but that makes the View react very slowly. Are there any other solutions?
As Romain Guy explained a while back during the Google I/O session, the most efficient way to only update one view in a list view is something like the following (this one update the whole view data):
ListView list = getListView();
int start = list.getFirstVisiblePosition();
for(int i=start, j=list.getLastVisiblePosition();i<=j;i++)
if(target==list.getItemAtPosition(i)){
View view = list.getChildAt(i-start);
list.getAdapter().getView(i, view, list);
break;
}
Assuming target is one item of the adapter.
This code retrieve the ListView, then browse the currently shown views, compare the target item you are looking for with each displayed view items, and if your target is among those, get the enclosing view and execute the adapter getView on that view to refresh the display.
As a side note invalidate doesn't work like some people expect and will not refresh the view like getView does, notifyDataSetChanged will rebuild the whole list and end up calling getview for every displayed items and invalidateViews will also affect a bunch.
One last thing, one can also get extra performance if he only needs to change a child of a row view and not the whole row like getView does. In that case, the following code can replace list.getAdapter().getView(i, view, list); (example to change a TextView text):
((TextView)view.findViewById(R.id.myid)).setText("some new text");
In code we trust.
One option is to manipulate the ListView directly. First check if the index of the updated row is between getFirstVisiblePosition() and getLastVisiblePosition(), these two give you the first and last positions in the adapter that are visible on the screen. Then you can get the row View with getChildAt(int index) and change it.
This simpler method works well for me, and you only need to know the position index to get ahold of the view:
// mListView is an instance variable
private void updateItemAtPosition(int position) {
int visiblePosition = mListView.getFirstVisiblePosition();
View view = mListView.getChildAt(position - visiblePosition);
mListView.getAdapter().getView(position, view, mListView);
}
The following code worked for me. Note when calling GetChild() you have to offset by the first item in the list since its relative to that.
int iFirst = getFirstVisiblePosition();
int iLast = getLastVisiblePosition();
if ( indexToChange >= numberOfRowsInSection() ) {
Log.i( "MyApp", "Invalid index. Row Count = " + numberOfRowsInSection() );
}
else {
if ( ( index >= iFirst ) && ( index <= iLast ) ) {
// get the view at the position being updated - need to adjust index based on first in the list
View vw = getChildAt( sysvar_index - iFirst );
if ( null != vw ) {
// get the text view for the view
TextView tv = (TextView) vw.findViewById(com.android.myapp.R.id.scrollingListRowTextView );
if ( tv != null ) {
// update the text, invalidation seems to be automatic
tv.setText( "Item = " + myAppGetItem( index ) + ". Index = " + index + ". First = " + iFirst + ". Last = " + iLast );
}
}
}
}
There is another much more efficient thing you can do, if it fits your use-case that is.
If you are changing the state and can somehow call the proper (by knowing the position) mListView.getAdapter().getView() it will the most efficient of all.
I can demonstrate a really easy way to do it, by creating an anonymous inner class in my ListAdapter.getView() class. In this example I have a TextView showing a text "new" and that view is set to GONE when the list item is clicked:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// assign the view we are converting to a local variable
View view = convertView;
Object quotation = getItem(position);
// first check to see if the view is null. if so, we have to inflate it.
if (view == null)
view = mInflater.inflate(R.layout.list_item_quotation, parent, false);
final TextView newTextView = (TextView) view.findViewById(R.id.newTextView);
view.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (mCallbacks != null)
mCallbacks.onItemSelected(quotation.id);
if (!quotation.isRead()) {
servicesSingleton.setQuotationStatusReadRequest(quotation.id);
quotation.setStatusRead();
newTextView.setVisibility(View.GONE);
}
}
});
if(quotation.isRead())
newTextView.setVisibility(View.GONE);
else
newTextView.setVisibility(View.VISIBLE);
return view;
}
The framework automatically uses the correct position and you do have to worry about fetching it again before calling getView.
For me below line worked:
Arraylist.set(position,new ModelClass(value));
Adapter.notifyDataSetChanged();
Just for the record, did anyone consider the 'View view' on the override method ?
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//Update the selected item
((TextView)view.findViewById(R.id.cardText2)).setText("done!");
}