I am trying to implement a similar effect like the iPhone-alike sliding header in the iPhone contact app (the sliding header that group the contacts by it’s starting letter).
This is the screen of my app, and what I want to achieve is the following:

I have a ‘guide header’ and three ‘tabs’ for sorting the list. When the user scrolls the list up, I want everything to scroll up (guide header, tabs, list). However, when the tabs reach the top of the screen (and the guide header will just be gone off the screen), I want the tabs to stop and stay there (remain as “sticky header”), and only the list items scroll as in any regular list view.
I have a view group (guide header) above a list view.
First of all, I want to have the guide header adjust it’s position depending on the scrolling position of the list view.
First approach:
My idea was to set an onScrollListener to the list view and change the top margin of the guide header to whatever the scroll position of the first item in the list view is (which would be a negative value).
The logic is correct, but the problem I’m facing is that the guide header view doesn’t get redrawn fast enough while I’m scrolling in the list view. The guide header view only updates (to my changed top margin value) when the list view fling comes to an end. Even slow scrolling doesn’t work. Invalidating (invalidate()) the guide header view or it’s parent also doesn’t help, since it would just put an invalidation request to the queue, but the invalidation and redrawing doesn’t happen immediately, but only when the UI thread becomes idle, which doesn’t seem to happen while the user still has his fingers on the scroll list view. Seems that flinging the list view blocks the whole UI thread or keeps it busy for itself.
So the main problem is: changing the margin of the guide header view doesn’t become visible immediately while the user is scrolling the list view. The code I’m using it this:
@Override
public void onScroll(final AbsListView view, final int firstVisibleItem,
final int visibleItemCount, final int totalItemCount) {
// Get the first list item and check it's scroll position. This will be the value (top), that we also
// use the scroll the header parallel.
View v = mainList.getChildAt(0);
final int top = (v==null)?0:v.getTop();
// This logs the current scroll position of the first list item element/view group.
Log.d("onScroll", "onScroll: " + top);
// Here we finally change the margin (setting a negative margin) to the header element.
((LinearLayout.LayoutParams)(findViewById(R.id.header_container).getLayoutParams())).setMargins(0, top, 0, 0);
// was just a test: invalidating the outer container/view group, doesn't help
// findViewById(R.id.ll_container).invalidate();
}
I do see the “onScroll:” log output I inserted in the code above in the logcat, but the following adjustment of the top margin just doesn’t become visible.
My second approach: is to use a scrollview for the guide header + tabs and work with those. Scrolling the guide header (which is then a scroll view) from code with scrollView.scrollTo(0,Math.abs(Math.abs(top)) from the onScroll method of the list view does work and almost immediately shows on the screen, however, it’s not very accurate/stable when the user flings the list view very fast – meaning it jumps in intervals and doesn’t look smooth; it’s only accurate/stable when scrolling slowly.
My question is now: is there any best practice to accomplish such a sliding header effect, and more concrete: is there a way to force the guide header view to be redrawn while the user is still scrolling the list view (in my first mentioned approach).
For this you should use some tricks (afaik there is no ready-to-use implementation of such a feature).
For instance, you could detect gestures on your view, and
scroll down, and the first list item
is visible, animate-shrink the
header’s size to 0, the tab view’s
size to match_parent. Start scrolling
the list only when the header is not
present anymore.
up, and the first is already visible,
animate-expand the header to it’s
original size.
So using Animation on the header view might be your solution.
Update
An other workaround would be to extend your
List(the value array of your adapter):Inster a new (dummy) item at the top for the header representation, and modify your
ListAdapter‘sgetViewmethod:where the xml referenced by
R.layout.sliding_headershould contain the header layout of your list.A custom
OnScrollListenerimplementation applied to theListViewwould make unnoticeable that the header actually is an item of the list, since it would hide the scrollbar.You should add this listener to your
listViewin the activity’sonCreatemethod:where
MyScrollListeneris: