How to detect if a ListView is fast scrolling
Asked Answered
J

2

5

Is there a way to detect if a ListView is fast scrolling? That is to say, can we somehow know when the user presses and releases the FastScroller?

Juryrigged answered 25/1, 2014 at 21:34 Comment(0)
U
6

Reflection allows you to do it. By taking a look at the AbsListView source code, there is a FastScroller object which indicates a wrapper around the fast scrolling functionality. Its source code shows an interesting field:

/**
 * Current decoration state, one of:
 * <ul>
 * <li>{@link #STATE_NONE}, nothing visible
 * <li>{@link #STATE_VISIBLE}, showing track and thumb
 * <li>{@link #STATE_DRAGGING}, visible and showing preview
 * </ul>
 */
private int mState;

This field contains the status of the FastScroller object. The solution consists in reading this field's value via reflection, every time the onScroll() and onScrollStateChanged() methods are triggered.

This code implements the solution described above:

private class CustomScrollListener implements OnScrollListener {

    private ListView list;
    private int mState = -1;
    private Field stateField = null;
    private Object mFastScroller;
    private int STATE_DRAGGING;

    public CustomScrollListener() {
        super();

        String fastScrollFieldName = "mFastScroller";
        // this has changed on Lollipop
        if (Build.VERSION.SDK_INT >= 21) {
            fastScrollFieldName = "mFastScroll";
        }

        try {
            Field fastScrollerField = AbsListView.class.getDeclaredField(fastScrollFieldName);
            fastScrollerField.setAccessible(true);
            mFastScroller = fastScrollerField.get(list);

            Field stateDraggingField = mFastScroller.getClass().getDeclaredField("STATE_DRAGGING");
            stateDraggingField.setAccessible(true);
            STATE_DRAGGING = stateDraggingField.getInt(mFastScroller);

            stateField = mFastScroller.getClass().getDeclaredField("mState");
            stateField.setAccessible(true);
            mState = stateField.getInt(mFastScroller);

        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {

        // update fast scroll state
        try {
            if (stateField != null) {
                mState = stateField.getInt(mFastScroller);
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }


        if (mState == STATE_DRAGGING)) {
            // the user is fast scrolling through the list
        }
    }

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

        // update fast scroll state
        try {
            if (stateField != null) {
                mState = stateField.getInt(mFastScroller);
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

    }
}
Upsilon answered 1/7, 2014 at 14:53 Comment(7)
what is the field "grid" in fastScrollerField.get(grid); Also is there any disadvantages to using this, i assume reflections makes this 'slow'Mellissamellitz
grid is the GridView that I was using. I'm changing that to list, so to be more clear.Upsilon
Also assuming this might break if the source is changed from sdk to sdk? and therefore the try catch. seems like a hacky solutionMellissamellitz
Yes, it is hacky, and yes, it has changed already, but only once. I'm updating the answer with the two variants. IMO, this is the only viable approach.Upsilon
Right so it works! Brilliant! +1 But like you said it has changed so that the field variable is now "mFastScroll". Got to be a better way hummmMellissamellitz
The thing is that now we have RecyclerView as the main list, and that does not support fast scrolling out of the box. Mark Hallison is writing a nice series of articles on this very subject: blog.stylingandroid.com/recyclerview-fastscroll-part-1Upsilon
This unfortunately does not work anymore on API 28 (Pie) if using targetSdkVersion=28, because "mState" is on the dark grey-list, resulting in NoSuchFieldException when trying to access that field.Entertainment
S
0

Try using the setOnScrollListener and implement the onScrollStateChanged. The scrollState can be idle, touch scroll, or fling

setOnScrollListener(new OnScrollListener(){
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
      // TODO Auto-generated method stub
    }
    public void onScrollStateChanged(AbsListView view, int scrollState) {
      // TODO Auto-generated method stub
      if(scrollState == 2) Log.i("a", "fast scroll");
    }
  });
}
Skardol answered 25/1, 2014 at 21:41 Comment(4)
This doesn't mean it's fast tho. This will trigger scroll even if the user flings with little acceleration.Jonejonell
Are you trying to determine how fast it's scrolling?Skardol
I tried that already and scrolling touching the screen or scrolling the fastScroller return the same scrollState. I am using JazzyListView with a SLIDE_IN effect and have fast scrolling enabled in the ListView. When I fast scroll it does not work quite right because the first or last visible items are many times not visible. I wanted disable the effect when entering the fast scroll mode and re-enable it when leaving it.Juryrigged
It appears like there isn't a way to determine when fast scroll is used. there is a bug report about it that says a fix is already released, but I can't find anything in the docs about it.Skardol

© 2022 - 2024 — McMap. All rights reserved.