39

I'd like to get the exact, pixel position of the ListView scroll. And no, I am not referring to the first visible position.

Is there a way to achieve this?

saarraz1
  • 2,957
  • 6
  • 25
  • 44
  • The way you provided solves a different problem - how to RESTORE the position of the ListView, while my problem is how to GET the actual value of the scroll position. – saarraz1 Nov 21 '12 at 20:54

8 Answers8

79

Okay, I found a workaround, using the following code:

View c = listview.getChildAt(0);
int scrolly = -c.getTop() + listview.getFirstVisiblePosition() * c.getHeight();

The way it works is it takes the actual offset of the first visible list item and calculates how far it is from the top of the view to determine how much we are "scrolled into" the view, so now that we know that we can calculate the rest using the regular getFirstVisiblePosition method.

saarraz1
  • 2,957
  • 6
  • 25
  • 44
  • 96
    This is only accurate if all your list items are the same height. – Paul Lammertsma Apr 11 '13 at 15:04
  • 6
    What if the ListView has a header? – jonah_w Dec 02 '13 at 01:53
  • I believe it should work (if the header has the same height as the items) - because headers are transparent to the list view itself - they are created by a wrapper adapter that is generated by the listview on setAdapter and are henceforth treated like any other views. – saarraz1 Dec 02 '13 at 02:01
  • really useful to check scrollY position of a simple list view. – speedynomads Dec 03 '13 at 09:53
  • I liked chet's link/comment pointing to that other question, but the answer I like best there is this: http://stackoverflow.com/a/3035521/346733 – Josh Apr 22 '14 at 00:17
  • 1
    I needed to know relative scroll position to see if it's scrolling up or down, so used `Math.max` like this: ```View firstView = view.getChildAt(0); maxHeight = Math.max(maxHeight, firstView.getHeight()); int y = -firstView.getTop() + view.getFirstVisiblePosition() * maxHeight;``` – ViliusK Sep 09 '14 at 21:25
29

Saarraz1's answer will only work if all the rows in the listview are of the same height and there's no header (or it is also the same height as the rows).

Note that once the rows disappear at the top of the screen you don't have access to them, as in you won't be able to keep track of their height. This is why you need to save those heights (or accumulated heights of all). My solution requires keeping a Dictionary of heights per index (it is assumed that when the list is displayed the first time it is scrolled to the top).

private Dictionary<Integer, Integer> listViewItemHeights = new Hashtable<Integer, Integer>();

private int getScroll() {
    View c = listView.getChildAt(0); //this is the first visible row
    int scrollY = -c.getTop();
    listViewItemHeights.put(listView.getFirstVisiblePosition(), c.getHeight());
    for (int i = 0; i < listView.getFirstVisiblePosition(); ++i) {
        if (listViewItemHeights.get(i) != null) // (this is a sanity check)
            scrollY += listViewItemHeights.get(i); //add all heights of the views that are gone
    }
    return scrollY;
}
Maria
  • 1,055
  • 11
  • 16
  • I'm confused by this solution. When do you call this method? You only store a single value in your Dictionary? – Dan_Dan_Man Dec 10 '12 at 22:53
  • No, not one. The value of listView.getFirstVisiblePosition() changes while you scroll down the list. And once the top rows disappear at the top I have no access to them to check their height. – Maria Dec 12 '12 at 17:12
  • 2
    Of course, this only works if the user manually scrolled through all the list items. If you are programatically setting the list position, this won't work. – Tom anMoney May 30 '13 at 03:38
  • 1
    View c returns null alltime. – Olkunmustafa Dec 11 '14 at 15:49
  • 1
    I'd suggest using a SparseIntArray or a Map (HashMap) instead of Hashtable for better performance. Hashtable's synchronized behavior is useless in this case. – Piovezan Feb 04 '15 at 19:39
  • Note, if you go this route, you will need to call getScroll on onScroll. – Danedo Aug 18 '15 at 19:54
  • This gives a lot of jitter in scrolls – Shubham Chaudhary Aug 27 '15 at 17:13
20

Simplest idea I could come up with was to extend ListView and expose the "computeVerticalScrollOffset" which is protected by default, then use "com.your.package.CustomListView" in your xml layouts.

public class CustomListView extends ListView {

    public CustomListView(Context context) {
        super(context);
    }

    public CustomListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public int computeVerticalScrollOffset() {
        return super.computeVerticalScrollOffset();
    }
}
jaredpetker
  • 550
  • 5
  • 8
  • This is a nice trick, but unfortunately the result is in some strange units, e.g. while the header is partially visible, `computeVerticalScrollOffset()` reports percent of this header view unfolding. When I scroll more, the result grows monotonically, but I am not sure how this can be translated in pixels from (virtual) top of the first header. – Alex Cohn Jun 28 '15 at 20:22
  • computeVerticalScrollOffset is relative to first item in "visible" list. You need to understand the way ListView calculates it. Do you remember getView(int position, View convertView, ViewGroup parent) method in Adapter? old item from top of list is poped out and converted into item at the bottom and visa versa. You can use getFirstVisiblePosition(). ``` y=super.computeVerticalScrollOffset(); if(listView.getFirstVisiblePosition() > 0){ y += (listView.getFirstVisiblePosition()-1)*listItemHeight+headerHeight; } ``` – El' Mar 11 '17 at 04:58
  • added full example as new answer – El' Mar 11 '17 at 05:04
9

If anyone else found this in Google while looking for a way to track relative scroll offsets in an OnScrollListener - that is, change in Y since the last call to the listener - here's a Gist showing how to calculate that.

cnnr
  • 1,606
  • 14
  • 16
  • I almost try to calculate that positiom of listview. I hope it works, I will try tomorrow. – Olkunmustafa Dec 15 '14 at 21:26
  • 1
    [Here's](http://stackoverflow.com/questions/12727594/android-listview-current-scroll-location-y-pixels/35594825#35594825) how to use it – Sarasranglt Feb 24 '16 at 06:29
9

First Declare your int variable for hold the position.

int position = 0;

then add scrollListener to your ListView,

listView.setOnScrollListener(new OnScrollListener() {

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            // TODO Auto-generated method stub

        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem,
                int visibleItemCount, int totalItemCount) {
             position = firstVisibleItem;

        }
    });

Then after getting new data or any changes in your data that time you need to set the listview current position

listView.setSelection(position);

I have used after setup my adapter , works fine for me..

CrazyMind
  • 828
  • 1
  • 18
  • 19
  • 1
    This doesn't answer the OP question, but it is a simple solution to setting the listview's scroll position based on its previous position, independent of cell size. – Grux Apr 05 '16 at 00:44
2

I know I'm late to the party but I felt like sharing my solution to this problem. I have a ListView and I was trying to find how much I have scrolled in order to scroll something else relative to it and cause a parallax effect. Here's my solution:

public abstract class OnScrollPositionChangedListener implements AbsListView.OnScrollListener {
    int pos;
    int prevIndex;
    int prevViewPos;
    int prevViewHeight;
    @Override
    public void onScroll(AbsListView v, int i, int vi, int n) {
        try {
            View currView = v.getChildAt(0);
            int currViewPos = Math.round(currView.getTop());
            int diffViewPos = prevViewPos - currViewPos;
            int currViewHeight = currView.getHeight();
            pos += diffViewPos;
            if (i > prevIndex) {
                pos += prevViewHeight;
            } else if (i < prevIndex) {
                pos -= currViewHeight;
            }
            prevIndex = i;
            prevViewPos = currViewPos;
            prevViewHeight = currViewHeight;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            onScrollPositionChanged(pos);
        }
    }
    @Override public void onScrollStateChanged(AbsListView absListView, int i) {}
    public abstract void onScrollPositionChanged(int scrollYPosition);
}

I created my own OnScrollListener where the method onScrollPositionChanged will be called every time onScroll gets called. But this method will have access to the calculated value representing the amount that the ListView has been scrolled.

To use this class, you can setOnClickListener to a new OnScrollPositionChangedListener and override the onScrollPositionChanged method.

If you need to use the onScroll method for other stuff then you can override that too but you need to call super.onScroll to get onScrollPositionChanged working correctly.

myListView.setOnScrollListener(
    new OnScrollPositionChangedListener() {
        @Override
        public void onScroll(AbsListView v, int i, int vi, int n) {
            super.onScroll(v, i, vi, n);
            //Do your onScroll stuff
        }
        @Override
        public void onScrollPositionChanged(int scrollYPosition) {
            //Enjoy having access to the amount the ListView has scrolled
        }
    }
);
aptaunk
  • 31
  • 3
  • Thanks ! I had the same idea and I did copy past your sample. But for me this does not work precisely, i have offset when i do hard test very speed. – fingerup Nov 15 '16 at 16:52
  • Thanks! can you add comments to your code to explain what you did ? – Badr At Sep 16 '19 at 15:19
2

in addition to @jaredpetker answer. ListView is not holding all the items in its scroll, so u need to operate only "visible" part of list. When you scroll down top items are shifted out and pushed as new item views. Thats how convertedView is came from (it's not empty item to fill, it's shifted item that is out of "visible" part of list. So u need to know how many items was before visible part multiply them with ListItemHeight and add headerHeight, thes how you can get real absolute offset in scroll. If u got not header, position 0 will be listItem, so you can simplify absoluteY += pos*listItemHeight;

public class CustomListView extends ListView {

private int listItemHeight = 140;
private int headerHeight = 200;

public CustomListView(Context context) {
    super(context);
}

public CustomListView(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public CustomListView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

@Override
public int computeVerticalScrollOffset() {
    final int y = super.computeVerticalScrollOffset();
    int absoluteY = y;
    int pos = getFirstVisiblePosition();
    if(pos > 0){ 
       absoluteY += (pos-1)*listItemHeight+header‌​Height;
    }
    //use absoluteY
    return y;
}
Community
  • 1
  • 1
El'
  • 369
  • 6
  • 18
1

I had faced the similar problem, That I wanted to place the Vertical Seekbar at current scrolled value of ListView. So I have my own solution like this.

First Create Class

public abstract class OnScrollPositionChangedListener implements AbsListView.OnScrollListener {
    int pos;
    public  int viewHeight = 0;
    public  int listHeight = 0;

    @Override
    public void onScroll(AbsListView v, int i, int vi, int n) {
        try {
            if(viewHeight==0) {
                viewHeight = v.getChildAt(0).getHeight();
                listHeight = v.getHeight();

            }
            pos = viewHeight * i;

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            onScrollPositionChanged(pos);
        }
    }
    @Override public void onScrollStateChanged(AbsListView absListView, int i) {}
    public abstract void onScrollPositionChanged(int scrollYPosition);
}

Then use it in Main Activity like this.

public class MainActivity extends AppCompatActivity {
    SeekBar seekBar;
    ListView listView;
    OnScrollPositionChangedListener onScrollPositionChangedListener = new OnScrollPositionChangedListener() {
        @Override
        public void onScroll(AbsListView v, int i, int vi, int n) {
            super.onScroll(v, i, vi, n);
            //Do your onScroll stuff
        }

        @Override
        public void onScrollPositionChanged(int scrollYPosition) {
            //Enjoy having access to the amount the ListView has scrolled

            seekBar.setProgress(scrollYPosition);


        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        seekBar = (SeekBar) findViewById(R.id.mySeekBar);
        listView = (ListView) findViewById(R.id.listView);
        final String[] values = new String[]{"Android List View",
                "Adapter implementation",
                "Simple List View In Android",
                "Create List View Android",
                "Android Example",

        };

        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_1, android.R.id.text1, values);
        listView.setAdapter(adapter);
        listView.setOnScrollListener(onScrollPositionChangedListener);



        seekBar.postDelayed(new Runnable() {
            @Override
            public void run() {
                seekBar.setMax((onScrollPositionChangedListener.viewHeight * values.length) - onScrollPositionChangedListener.listHeight);
            }
        }, 1000);
        seekBar.setEnabled(false);

    }
}

in App Gradle

compile 'com.h6ah4i.android.widget.verticalseekbar:verticalseekbar:0.7.0'

In XML Layout

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"

    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.centsol.charexamples.MainActivity">


    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fadeScrollbars="false"


        android:scrollbars="none"

        android:layout_alignParentTop="true"

        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true">

    </ListView>
    <!-- This library requires pair of the VerticalSeekBar and VerticalSeekBarWrapper classes -->
    <com.h6ah4i.android.widget.verticalseekbar.VerticalSeekBarWrapper
        android:layout_width="wrap_content"

        android:layout_alignParentRight="true"
        android:layout_height="match_parent">
        <com.h6ah4i.android.widget.verticalseekbar.VerticalSeekBar
            android:id="@+id/mySeekBar"
            android:layout_width="0dp"
            android:progressDrawable="@drawable/progress"
            android:thumb="@drawable/thumb"
            android:layout_height="0dp"
            android:splitTrack="false"

            app:seekBarRotation="CW90" /> <!-- Rotation: CW90 or CW270 -->
    </com.h6ah4i.android.widget.verticalseekbar.VerticalSeekBarWrapper>
    <View
        android:layout_width="1dp"
        android:visibility="visible"
        android:layout_alignParentRight="true"
        android:layout_marginTop="16dp"
        android:layout_marginRight="2.5dp"
        android:layout_marginBottom="16dp"
        android:background="@android:color/black"
        android:layout_height="match_parent" />
</RelativeLayout>

background xml

    <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >

    <solid android:color="@android:color/transparent"/>

</shape>

fill xml

<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >

<solid android:color="@android:color/transparent" />


</shape>

progress xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >

    <item
        android:id="@android:id/background"
        android:drawable="@drawable/background"/>
    <item android:id="@android:id/progress">
        <clip android:drawable="@drawable/fill" />
    </item>

</layer-list>

thumb xml

<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval" >


<solid android:color="@android:color/holo_red_dark" />
    <size
        android:height="5dp"
        android:width="5dp" />

</shape>
Ali Imran
  • 8,249
  • 3
  • 35
  • 46