开发者

iPhone alike sliding header, or: how to force immediate redraw on top margin change?

开发者 https://www.devze.com 2023-03-03 22:43 出处:网络
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).

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:

iPhone alike sliding header, or: how to force immediate redraw on top margin change?

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.

Firs开发者_StackOverflow中文版t 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

  • if the current gesture matches a 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.
  • if the current gesture matches scroll 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's getView method:

@Override
public View getView(int position, View convertView, ViewGroup parent)
{
    if (position == 0)
    {
        convertView = inflater.inflate(R.layout.sliding_header, parent, 
            false);
        return convertView;
    }
    //TODO: your original method body comes here
}

where the xml referenced by R.layout.sliding_header should contain the header layout of your list.

A custom OnScrollListener implementation applied to the ListView would 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 listView in the activity's onCreate method:

listView.setOnScrollListener(new MyScrollListener());

where MyScrollListener is:

/**
 * Custom OnScrollListener
 */
private final class MyScrollListener implements OnScrollListener
{
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState)
    {}

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, 
            int visibleItemCount, int totalItemCount)
    {
        if (view.getFirstVisiblePosition() == 0)
            view.setVerticalScrollBarEnabled(false);
        else if (!view.isVerticalScrollBarEnabled())
            view.setVerticalScrollBarEnabled(true);
    }
}


I think you can also try and use my ExpandAnimation for that. http://udinic.wordpress.com/2011/09/03/expanding-listview-items/

Just pass the animation class that "guide header" view, and let the animation do the work for you, no scrolling is needed in that case, and it's smooth.

0

精彩评论

暂无评论...
验证码 换一张
取 消