Tags

, , , , , , , , , ,

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!