Android: putting a ViewPager inside a ListView

So, I’ve been Googling for a while to find a clean, reasonable approach for putting a ViewPager inside of a ListView (or any vertically scrolling element). The problem with putting a ViewPager inside a ListView is that it takes only a small amount of vertical (Y) delta before the ListView begins to consume touch events instead of delegating to the child in order to enable the scrolling of the ListView. That’s fine, but the problem often occurs after the ViewPager has already started processing a page change (when dragging horizontally), producing a poor user experience resulting in the improper pagination and scrolling of the two views.

There are a few ways to try to handle this.

  1. You could not do it at all (thanks Dianne and Mark)
  2. You can set a touch listener on the ListView and try to delegate the touches based on which view you think needs to get the touches and what actions need to occur based on those touches.
  3. You can also combine a touch listener with GestureDetector to try to make the implementation a little less cumbersome. The problem is that in some ListViews, our layouts are a little more complex than single item rows of text. If the item layout has for instance a Button, we’d then need to properly delegate the touch event down to the Button. This obviously has huge ramifications for implementation and testing.

In order to avoid the aforementioned shenanigans, I added an OnPageChangeListener to the ViewPager and based on the state of the pager, forced a touch delegate from the Activity.

Contrived example:

class MyActivity extends Activity {
    private ViewPager mViewPager;
    private View mTouchTarget;

    @Override
    protected void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.example);

        mViewPager = (ViewPager) findViewById(R.id.view_pager);

        mViewPager.setOnPageChangeListener(new OnPageChangeListener() {

            private int mPreviousState = ViewPager.SCROLL_STATE_IDLE;

            @Override
            public void onPageSelected(int position) {
                // NO-OP
            }

            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                // NO-OP
            }

            @Override
            public void onPageScrollStateChanged(int state) {
                // All of this is to inhibit any scrollable container from consuming our touch events as the user is changing pages
                if (mPreviousState == ViewPager.SCROLL_STATE_IDLE) {
                    if (state == ViewPager.SCROLL_STATE_DRAGGING) {
                        mTouchTarget = mViewPager;
                    }
                } else {
                    if (state == ViewPager.SCROLL_STATE_IDLE || state == ViewPager.SCROLL_STATE_SETTLING) {
                        mTouchTarget = null;
                    }
                }

                mPreviousState = state;
            }
        });
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mTouchTarget != null) {
            boolean wasProcessed = mTouchTarget.onTouchEvent(ev);

            if (!wasProcessed) {
                mTouchTarget = null;
            }

            return wasProcessed;
        }

        return super.dispatchTouchEvent(ev);
    }
}

Now, we have a ViewPager that retains control of touch events once the page starts changing and releases it when paging is idle or settling.

Renaming files by pattern

Occasionally (and especially when receiving assets) I have the need to rename multiple files at once — often by some sort of pattern. For instance, I may need to rename all files with the string "_msg" in it where the file ends with ".png". Previously, I might have manually found and renamed all of them or written some hack of a bash “for loop” to iterate over a find result. Neither options are quick or easy.

Recently, I found a great and simple util called rename to quickly and easily rename files with simple search-and-replace functionality.

In this example, I’m renaming any *.png file under a drawable* folder, replacing _msg with _message.

rename s/_msg/_message/ drawable*/*.png

The rename util (not the same as mv) comes standard with most linux installations, but I’m on Mac OS X and that means I have to get it separately. In order to easily get rename, I use homebrew.

  1. Install homebrew.
    ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"
  2. Install rename
    brew install rename
  3. Done.

Joining Android Alliance Philadelphia

I was recently asked by Corey Latislaw to join her, Chuck Greb, and Arpit Mathur in helping organize the Android Alliance Philadelphia group. I am proud to announce that I have accepted the position as Social Media Director of Android Alliance Philadelphia and that I plan on helping to create an even more engaging community. I am excited to help bring in even more Android enthusiasts, creators, and users with interesting topics and informative events.

Looking at Android Framework source quickly

Even though I have recent checkouts of the Android source on my local machine, I usually prefer a more convenient and quick way to look at the platform code. Looking through source files on my local machine can often be slow to find the appropriate files and unreasonable to keep open in an IDE all of the time. Instead, I’ve been using the copy of the Android source that is hosted on github.

Generally, if I’m looking for something like ‘How is ListView implemented in Froyo?’, I’ll open up the source in github at https://github.com/android/platform_frameworks_base, hit ‘w’ as the shortcut for switching branches and tags, type in ‘froyo’ and choose the right branch, hit ‘t’ as the shortcut for finding a file in the repository, type ‘ListView’ and hit enter to open the file. Now I have the ListView implementation open in Github and ready to peruse!

What’s nice is that at myYearbook, we have a github:enterprise instance where we host our own internal code. So in the same way I can figure out how ListViews are implemented in Android, I can also figure out what our current Android versionCode is. Open the internal github to the myYearbook repository, hit ‘t’, type in AndroidManifest.xml, and then use my browser’s find functionality to look for ‘versionCode’. Done.

Awesome!

Lightning Talk: Drowning in Images

Last night, I gave a lightning talk alongside Matt Smollinger (a myYearbook iOS developer) and other local developers for Philly Tech Week 2012. My talk centered around downloading, caching, and displaying images in an Android application. Check out Drowning in Images — Memory Management or all of my presentations. You can also watch a video of my presentation on YouTube. I have to thank Android Alliance Philly and Philly Cocoaheads

parcelabler: for implementing Android’s Parcelable interface

I have created a tool for making the methods needed for implementation of Android’s Parcelable interface within a pre-existing class.

  1. Open the parcelabler tool.
  2. Copy the full code of the class into the “Code” text field.
    Example code:

    public class Photo {
        /**
         * Caption for the photo
         */
        public String caption;
        public int commentCount;
        public long photoId;
        public boolean isDefault;
        public Bundle metadata;
        public CommentHandler commentHandler;
    }
    
  3. Click “Build” to create the methods
  4. Copy the methods into your class
  5. Add “implements Parcelable” to your class definition
Now you should hopefully have a working implementation of Parcelable in your class.

An adb (Android Debug Bridge) wrapper for installing from a URL, installing to all devices

At myYearbook, we produce our internal and release candidate builds for all of our Android applications using Jenkins CI. This allows us to automatically produce builds when changes are pushed to code review (through gerrit) as well as when they’re merged upstream. This process is great for keeping integration clean and organized, but it means that QA, product development, and sometimes developers have to download the produced APK from jenkins on to their computer, and then install from the location they downloaded to. In order to expedite this process, I wrote a wrapper script for adb to allow the installation of an APK from a URL.

To do this, simply change your usage from

adb install /path/to/app.apk

to use a URL

adb install http://my.server.com/app.apk

Additionally, use the -a flag to execute your commands on all devices.

adb -a install http://my.server.com/app.apk

Get the source: the adt-plus project on github!

Fixing constant splash screen on a Motorola Droid 2

If your Motorola Droid 2 is in a state of constant loading (at the startup/splash screen) something has gone awry with your device. Unfortunately, the only way I know to resolve this is to perform a hard reset. This hard reset wipes the operating system of the device and reinstalls it from memory. You will lose your installed apps and have to re-download them. If you purchased apps, as long as you log in to the device with your same email address, you will be able to download the apps again for free.

To perform a hard reset of the device,

  1. Completely turn off the device (remove the battery from the device).
  2. Put the battery back in
  3. Open the keyboard, and hold the X key and then hold the power button.
  4. The device should turn on with a triangle with an exclamation mark with an Andy (the green Android mascot)
  5. Click the Search icon on the keyboard (not the soft key on the screen)
  6. Use the volume buttons to navigate the menu. Volume up/Volume down for up and down respectively. Navigate to Wipe data/Factory reset.
  7. Click the Camera button (hard key on the side) to select this option.
  8. Tap the Menu soft key (on screen) for “OK” to clear the device.
  9. Once complete, navigate to “Reboot system” and click the Camera button again to accept.

The device should now reboot and start up properly. You’ll have to re-activate the device and go through the standard set up process, but at least your device works now!