Mobile - - By Bruce Cooper

Build an Android Pager Component

One of the most important user interface paradigms for modern mobiles is their swipable multi-touch interface. The most obvious example of this is the pager view, which allows a user to swipe through pages of information intuitively. The best known example of this is the home screen of iOS, which arranges applications in a grid across multiple pages. This has proven so successful that it has been used in a variety of other places, and has been outright copied by other phone manufacturers.

When I started programming for Android, I had assumed there would be an out-of-the box component allowing me to do this, or at least one that would be easily configurable to perform a paged view. That turned out not to be the case, so I had to dive into the Android source to work out a way of implementing it (and as an aside, this is an out-of-the-box feature of iOS). I thought I’d share the fruits of my labour with you.

Investigating

At first, I started playing with ViewFlipper using Gestures to provide the input to change pages. This worked, but did not give the correct feedback to the user while scrolling. When the pager scrolls, the user expects the page to move with the swipe. With this approach, the page does not animate until the swipe gesture is completed.

Next, I toyed with the idea of writing my own scrolling component from scratch. This would give me the most flexibility, but would involve a lot of effort. It’s generally much better to build on existing components rather than write your own, so I had one last trawl through the API to see what I could use. It was then that I found HorizontalScrollView.

This acts as a traditional scroll view except it works horizontally, which is exactly what I want. It also provides key bindings for free, which is a bonus. The only missing pieces are a method of getting it to snap to the page boundary when the user releases their finger, an easy to use API for adding and removing pages, and a page indicator to show the current page. I created a subclass, and started customising it to produce the effect I wanted.

Simple Page Management

HorizontalScrollView accepts just one child view by default, and it is up to the programmer to ensure it is of the correct size. I wanted to have multiple child views, or pages, and each page had to take up the entire viewport. To do this I added an addPage() method, which would take a view, resize it to be the size of the viewport, and add it to the LinearLayout which served as the child view of the ScrollView.

public class Pager extends HorizontalScrollView {
    private LinearLayout contents;

    ...

    public void addPage(View child) {
        int width = getWidth();
        child.setLayoutParams(new LayoutParams(width, LayoutParams.FILL_PARENT));
        contents.addView(child);
        contents.requestLayout();

        firePageCountChanged();
    }

    ...

}

Snap to Page Boundary

To do this, we simply listen to touch-up events, and then issue a final smoothScrollTo() call to scroll back to the nearest page boundary

public boolean onTouchEvent(MotionEvent evt) {
    boolean result = super.onTouchEvent(evt);

    int width = getWidth();

    if (evt.getAction() == MotionEvent.ACTION_UP) {
        int pg = (getScrollX() + width / 2) / width;
        smoothScrollTo(pg * width, 0);
    }
    return result;
}

Additional code is required to handle keyboard based events in a similar fashion, which I leave as an exercise for the reader.

Pager at GitHub contains the full source code within the PagingScrollerExample.

Page Indicator

By default, the horizontal scroll pane will display a standard style scroll bar while the scroll pane is scrolling, which fades after a few seconds. This is fine under most circumstances, and is exactly what I used on NodeDroid, my usage checking application. But I thought it might be nice to have an iOS style page indicator at the bottom of the screen.

This was implemented by creating a separate custom view component to display the pages, which I called a PageIndicator. This component exists independently of the pager so that you can place the view where you wish. In your activity start up, you simply tell the page indicator which pager to listen to, and it automatically updates itself from then on.

In order for the pager to update itself properly, it needs to know when the page changes, and when pages are added or removed from the pager. Inexplicably, there is no default listener for ScrollView in Android, so the only way you can receive notification is to trap it in a subclass using the onScrollChanged method. In my code, I created a second set of Listener/Event classes called OnPageChangeListener, with two events named onPageChange() and onPageCountChange().

PageIndicator at GitHub contains the full source code within the PagingScrollerExample.

Try for Yourself

I have produced a sample application which demonstrates the use of the view, and the code is available on GitHub. Check out the PagingScrollerExample. If you’d like to try it out, clone the repo, load up the project in Eclipse (with the ADT Plugin for Eclipse installed), and you should be away.

Sponsors