Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

FastScroller only scrolls back to the first element, not to the header view

I have enabled fast scrolling in a ListView, which has a not selectable headerview. If you scroll down the list and drag the fast scroll thumb to the top, the list only scrolls back to the first element, but not to the header view. Dragging the list, works as expected.

Screenshot1: The red area in the screenshot is the header view.
Screenshot2: If you drag the thumb to the top you get only to the first element and the header view is still above.

ListView lv = (ListView) findViewById(R.id.listView);
lv.addHeaderView(getLayoutInflater().inflate(R.layout.view,null), null, false);

 

<ListView 
   android:layout_height="fill_parent" 
   android:id="@+id/listView" 
   android:layout_width="fill_parent"
   android:fastScrollEnabled="true"
></ListView>

I have created a demo project: https://github.com/mikegr/fastscroll-bug

Why does dragging the thumb not scroll back to the top?

Red area is a header viewdragging thumb does not go back to top

like image 522
Michael Greifeneder Avatar asked Oct 24 '11 12:10

Michael Greifeneder


1 Answers

This is deliberate behavior of the FastScroller. When you call setAdapter on your ListView, the adapter is wrapped in a HeaderViewListAdapter if there are any headers set; this is why you must call addHeaderView before setAdapter. Then, in the FastScroller code, we see:

    if (adapter instanceof HeaderViewListAdapter) {
        mListOffset = ((HeaderViewListAdapter)adapter).getHeadersCount();
        adapter = ((HeaderViewListAdapter)adapter).getWrappedAdapter();
    }

That is, get an offset and use the underlying adapter. mListOffset is then used to set the top position to scroll to with the fast scroller. So where does this wrapping actually happen? Up to, as expected, ListView.addHeaderView, where we see:

    if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
        mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
    } else {
        mAdapter = adapter;
    }

So we're definitely looking around the right place. Now, it sounds like your goal is to NOT have the offset behavior for list headers for your fast thumb, but otherwise have a normal list with header. To do this, it's sufficient (based on what we've seen of the code) to have FastScroller.mListOffset = 0. This is set only in getSectionsFromIndexer, which is called unconditionally in init, and conditionally in several other functions only when mListAdapter == null. mListAdapter is only null if onSectionsChanged gets called, so let's ignore that path for now.

After a lot of digging around, and playing with various reflection hooks, I can say that there's no way to do this that will be even slightly future-compatible. You can use reflection to swap out the HeaderViewListAdapter for one that lies about its header count, etc; but that's quite fragile. Similarly, you can subclass the (package visible) FastScroller with one with your own behavior; but mListOffset is referenced widely and not through a getter, so this is even uglier than usual. Basically, you're running up against the fact that the system doesn't work quite the way you want.

I hesitate to call this a bug, since it's so clear from the code that it's deliberate behavior. Have you considered making the first element of the list just a special first element (possibly using a custom WrapperListAdapter much like HeaderViewListAdapter if desired for book-keeping), rather than using the header mechanism?

like image 192
addaon Avatar answered Sep 22 '22 05:09

addaon