Tags
accordion, android, custom-ui, instagram, instagram list view, kinetic list view, list-view, mozilla, onscrolllistener, relative-layout, scroll
The ever scrolling list view of Instagram has a special property. Since the height of each list item can vary, the title (containing the user’s profile photo, name and location), stays until the entire item scrolls out of visible area. This is a cool modification of the accordion views we see in HTML.
(i) Normal ListView. (ii) ListView starts scrolling, and the title of the first list item stays. (iii) With further scrolling, the title of the first item starts scrolling too.
How hard would it be to implement this in Android? If we can monitor the scrolling of the ListView, we would be able to change the position of the title, as the list scrolls. Thankfully Android provides a way to listen for the scrolling of the list through the AbsListView.OnScrollListener interface.
Let’s create a simple list item with a title and content. The title should be above the content, hence it should be the last element in a RelativeLayout
. Also, setting android:layout_alignParentTop
on title would try to enforce the rule every time the layout parameters change. Hence it’s better not to add the rule to avoid jitter effect.
<RelativeLayout android:layout_width="fill_parent" android:layout_height="wrap_content"> <!-- This has the margin of the title view --> <TextView android:id="@+id/content" android:layout_width="fill_parent" android:layout_height="150dp" android:layout_marginTop="50dp"/> <!-- This shouldn't be aligned to the top --> <TextView android:id="@+id/title" android:layout_width="fill_parent" android:layout_height="150dp"/> </RelativeLayout>
Monitoring the scrolling of ListView and updating the title is straightforward. However, there are few catches. The listener is called even when the ListView isn’t scrolling. This is the SCROLL_STATE_IDLE
. We shouldn’t be re-laying out the views in this state. Also, when the user flings down, it’s highly likely that the title hasn’t reached the top of its container and has moved to be the second visible item in the list. This wouldn’t reset the top margin of the title view. Hence, we should reset the top margin of the title view of all visible items, except the first one.
// OnScrollListener is the AbsListView.OnScrollListener. _our_list_view.setOnScrollListener(new OnScrollListener() { int mScrollState = OnScrollListener.SCROLL_STATE_IDLE; @Override public void onScrollStateChanged(AbsListView view, int scrollState) { // Store the state to avoid re-laying out in IDLE state. mScrollState = scrollState; } @Override public void onScroll(AbsListView list, int firstItem, int visibleCount, int totalCount) { // Nothing to do in IDLE state. if (mScrollState == OnScrollListener.SCROLL_STATE_IDLE) return; for (int i=0; i < visibleCount; i++) { View listItem = list.getChildAt(i); if (listItem == null) break; TextView title = (TextView) listItem.findViewById(R.id.title); int topMargin = 0; if (i == 0) { int top = listItem.getTop(); int height = listItem.getHeight(); // if top is negative, the list item has scrolled up. // if the title view falls within the container's visible portion, // set the top margin to be the (inverse) scrolled amount of the container. // else // set the top margin to be the difference between the heights. if (top < 0) topMargin = title.getHeight() < (top + height) ? -top : (height - title.getHeight()); } // set the margin. ((ViewGroup.MarginLayoutParams) title.getLayoutParams()).topMargin = topMargin; // request Android to layout again. listItem.requestLayout(); } } });
Happy hacking!
Cool stuff. I’d probably replace the requestLayout() bits with a setTranslationY() to make sure this is smooth—you can add a little compat code to support pre-Honeycomb or simply use something like NineOldAndroids. Or even use something like offsetTopAndBottom() which is available since API 1.
I somehow don’t like compat libraries 😉 I thought of using offsetTopAndBottom(). But “AndBottom” wouldn’t help me.
Well, compat libraries are big time savers. I prefer to focus on user-visible features than writing compat code over and over again 🙂
Not sure what you mean with “AndBottom”. The offsetTopAndBottom() method is pretty much about adding a vertical offset to the view—which exactly what you’re doing there. It does that by updating the view’s transformation matrix instead of triggering a relayout, which is more efficient and hardware acceleration-friendly.
can you tell me .how to I do it in ScrollView
Usually it’s better to use AdapterViews. ScrollView is good for holding a bigger content — but not something powered by an adapter.
HI sriram i like you post but i have one problem facing..
when i using top header size fixed like 50dp so working perfect same as like instagram.but i want to not set fixed size of top panel beacuse my text lenght is not fixed so how cai i manage..thanks
here link of my source code please check it give me replay thanks…
source code:- https://www.dropbox.com/s/9jtg5iuxyfeghmn/InstagramListView.zip
Hi Sriram,
how would you make it for multiple items? Imagine that I have several listview items which are divided into groups. (e.g. items grouped by daytime) Every group has this translate header. It translates it’s position to the point where it meets a new group. So I am actually moving view itself, not content within particular view. I tried several options, nothing works for me so far. Any thoughts? Thanks!
How would we get this to work on an expandableListview or even a recyclerview that has headers and child items ?
With RecyclerView it is pretty easy. We can do hacks with ItemDecoration to get it.