The Gallery is a horizontal scrolling list commonly used for displaying a list
of images. Unfortunately, the standard Android Developers
Gallery tutorial
focuses on displaying a small, static list of local images. An API-based list
of remote images is much more useful, but more complex.
Laziness
Needing to fetch remote images is the cause of this complexity. A naïve approach
might download all of the images sequentially, but that quickly becomes
untenable as the number of images in question grows.
The correct solution will build up a cache of images locally, fetching them only
as they need to be displayed. Images that have not yet loaded will be
represented by a placeholder.
Mirah
I've grown quite fond of Mirah since I wrote my
initial reactions about Mirah-Android development.
Despite the bleeding edge kinks and snares, I find myself being much more
productive than when using vanilla Java. Since the language is relatively new,
I thought it could be useful to walk through some real-world code and
demonstrate what it can do.
Designing a Lazy Gallery
We'll make three components:
An adapter to provide image/view information.
A selection listener to load images when they enter the visible window.
A mechanism for downloading the images in question and making them appear.
Implementation
This code is all available on Github; clone it and follow along:
Android's list-like views are backed by an
Adapter,
which provides a standard interface for creating or updating Views to
represent list items.
This implementation accepts a JSON string and turns it into equivalent Java
objects.
Using this list of caption/src pairs, it creates ImageViews for the gallery to
display. It employs a common adapter optimization and re-uses a few existing
views by updating them rather than repeatedly instantiating and discarding
many objects.
Note that we're not using a standard ImageView – it has extra methods
(e.g. refresh), which we'll talk about below.
While I chose to build the layout by hand to demonstrate more, we could have
eliminated some of the creation lines by inflating an XML resource:
GallerySelectionListener.mirah
This listener handles selection changes. It figures out which views are
visible when a selection is made and initiates loading of the associated
images. By using gallery.setCallbackDuringFling false when creating the
gallery, we disable the selection listener during flings so that it only
gets called when we actually want to load images.
So, we use the LazyGalleryAdapter to get the caption/src map for our given
position. We update the caption and show it.
Then we iterate over the parent's children – these are the visible
children, mind you – and call load on the LazyImageViews that they
house.
LazyImageView.mirah
The LazyImageView is an ImageView
with plumbing for loading a remote image asynchronously (i.e. off the UI
thread). It attempts to display an image that is already on disk, but defers
fetching remote images until its load method is called.
LazyImageView also handles large images gracefully by asynchronously resizing
them on disk with safe_image_to_drawable
and AsyncResize; otherwise, we will get killed for using too much memory.
Finally, it can invalidate the disk-based cache of images based on image age.
AsyncDownload.mirah
AsyncDownload is a fun little helper for downloading basically anything. It uses
some java.util.concurrent goodies to ensure that a given URL is only fetched
once for a destination path. Imagine the scenario where a user goes back and
forth between two neighboring images before either loads – we don't want
the two selection events to trigger double downloads of the images.