109

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.

N J
  • 27,217
  • 13
  • 76
  • 96
lacker
  • 5,470
  • 6
  • 36
  • 38

6 Answers6

216

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.

Community
  • 1
  • 1
Joe
  • 42,036
  • 13
  • 45
  • 61
  • 11
    Great answer, but doesn't quite work right when you have header views in your list. The assignment to `firstPosition` should be `int firstPosition = listView.getFirstVisiblePosition() - listView.getHeaderViewsCount();` to fix this. – pospi Oct 27 '11 at 08:13
  • @pospi: thanks, good point! I've updated my answer to account for that. – Joe Oct 31 '11 at 19:20
  • 1
    Good answer and works in majority of cases, but I don't understand why you believe that child views appear in the order they are in the array of child views. Since views can be reused how can we be sure that first view doesn't represent the last visible view? – Victor Denisov Oct 28 '13 at 10:01
  • 3
    @VictorDenisov: this can only happen on the UI thread; therefore the UI thread will be blocked while this code is executing, thus the child views are stationary and will not be modified. `ListView` is already handling "moving" the child views around after recycling old `convertView`s, etc, so you *can* be guaranteed that `ListView.getChildAt(0)` **is** in fact the first attached view from the adapter. It might not be fully visible (and might not even be visible at all, depending on `ListView`'s threshold of "visibility" before recycling a view that it considers is "scrolled out of view") – Joe Oct 31 '13 at 16:33
  • 1
    @Matheus that's not how Android ListView works. If the list item is off-screen, its view has already been recycled and reused for one of the other list items that *are* on screen. You should re-think how you're using ListView if that's a requirement. – Joe Jul 01 '15 at 14:55
  • I've got the same question as Matheus'. Working on an ExpandableListView, if I do something with the child and the groupView is not being shown, I can't change its view. It's not redrawn by the adapter btw, I checked it. I'd like to see a solution for that – Teo Inke Jul 14 '15 at 16:19
  • The only way in my case was to do `adapter.notifyDataSetChanged()` and let the adapter recreate the Views (and update what's necessary). But no way to change the View manually. – Teo Inke Jul 14 '15 at 17:07
  • In what method of Activity or Fragment I should use this code? – Ksenia Oct 20 '16 at 13:40
18

This code is easier to use:

 View rowView = listView.getChildAt(viewIndex);//The item number in the List View
    if(rowView != null)
        {
           // Your code here
        }
Chris - Jr
  • 398
  • 4
  • 11
Kalimah
  • 11,217
  • 11
  • 43
  • 80
  • 10
    doesn't work always. getChildAt counts from the first visible row, not from the top of the data. – SteelBytes Oct 13 '12 at 05:51
  • 13
    The ViewID argument is confusing. It's an index (or position). The view ID is a completely arbitrary integer generated by the aapt tool. – Johan Pelgrim Oct 16 '12 at 11:46
6

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

Feet
  • 2,567
  • 3
  • 22
  • 29
  • 1
    I've tried this, for doing selections. It usually works but there are off-by-one errors sometimes and it isn't reliable. I wouldn't recommend it. – Timmmm Sep 18 '12 at 12:13
  • Thanks Timmmm, I hadn't actually tried it just found it in the docs. – Feet Oct 02 '12 at 01:30
5
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

Zain Aftab
  • 703
  • 7
  • 21
Wes
  • 79
  • 1
  • 7
  • getChildAt is relative to current scrolled items. This will stop working if you scroll your list just a bit. – nurettin Oct 24 '16 at 18:44
4
int position = 0;
listview.setItemChecked(position, true);
View wantedView = adapter.getView(position, null, listview);
Milaaaad
  • 317
  • 6
  • 13
-9

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.

jasonhudgins
  • 2,815
  • 1
  • 25
  • 20
  • 43
    `getView()` is called internally by the ListView, when populating the list. You *should not* use it to get the view at that position in the list, as calling `getView()` with null for the `convertView` causes the adapter to inflate a new view from the adapter's layout resource (does not get the view that is already being displayed). – Joe Apr 20 '10 at 22:51
  • 1
    Even position will be a position in visual screen, not to the adapter's data source position. – Vikas Jul 27 '11 at 05:09
  • @jasonhudgins This is the method i have been using for i thought it is the best but when i try to make a progressbar in the child view invisible, but instead it made all progressbars in listvew invisible........all in all the accepted answer did the trick – Edijae Crusar Feb 16 '16 at 11:57