Creating an Image Viewing Application
What follows is a full example that queries the MediaStore to find images and presents them to the user one after the other in the form of a slideshow.
package com.apress.proandroidmedia.ch1.mediastoregallery;
public class MediaStoreGallery extends Activity { public final static int DISPLAYWIDTH = 200;
public final static int DISPLAYHEIGHT = 200;
Instead of using the size of the screen to load and display the images, we’ll use the foregoing constants to decide how large to display them.
TextView titleTextView;
ImageButton imageButton;
In this example, we are using an ImageButton instead of an ImageView. This gives us both the functionality of a Button (which can be clicked) and an ImageView (which can display an image).
public void onCreate(Bundle savedInstanceState) {
Download from Wow! eBook <www.wowebook.com>
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
titleTextView = (TextView) this.findViewById(R.id.TitleTextView);
imageButton = (ImageButton) this.findViewById(R.id.ImageButton);
Here we specify which columns we want returned. This must be in the form of an array of strings. We pass that array into the managedQuery method on the next line.
String[] columns = { Media.DATA, Media._ID, Media.TITLE, Media.DISPLAY_NAME };
cursor = managedQuery(Media.EXTERNAL_CONTENT_URI, columns, null, null, null);
We’ll need to know the index for each of the columns we are looking to get data out of from the Cursor object. In this example, we are switching from Media.DATA to
MediaStore.Images.Media.DATA. This is just to illustrate that they are the same.
Media.DATA is just shorthand that we can use since we have an import statement that encompasses it: android.provider.MediaStore.Images.Media.
fileColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
titleColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.TITLE);
displayColumn =
cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME);
After we run the query and have a resulting Cursor object, we call moveToFirst on it to make sure that it contains results.
if (cursor.moveToFirst()) {
//titleTextView.setText(cursor.getString(titleColumn));
titleTextView.setText(cursor.getString(displayColumn));
imageFilePath = cursor.getString(fileColumn);
bmp = getBitmap(imageFilePath);
// Display it
imageButton.setImageBitmap(bmp);
}
We then specify a new OnClickListener for imageButton, which calls the moveToNext method on the Cursor object. This iterates through the result set, pulling up and displaying each image that was returned.
imageButton.setOnClickListener(
Here is a method called getBitmap, which encapsulates the image scaling and loading that we need to do in order to display these images without running into memory problems as discussed earlier in the chapter.
private Bitmap getBitmap(String imageFilePath) {
// Load up the image's dimensions not the image itself
BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();
bmpFactoryOptions.inJustDecodeBounds = true;
Bitmap bmp = BitmapFactory.decodeFile(imageFilePath, bmpFactoryOptions);
int heightRatio = (int) Math.ceil(bmpFactoryOptions.outHeight
/ (float) DISPLAYHEIGHT);
int widthRatio = (int) Math.ceil(bmpFactoryOptions.outWidth
/ (float) DISPLAYWIDTH);
bmpFactoryOptions.inJustDecodeBounds = false;
bmp = BitmapFactory.decodeFile(imageFilePath, bmpFactoryOptions);
return bmp;
} }
The following is the layout XML that goes along with the foregoing activity. It should be put in the res/layout/main.xml file.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content"
android:id="@+id/ImageButton"></ImageButton>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/TitleTextView"
android:text="Image Title"/>
</LinearLayout>
Internal Metadata
EXIF, which stands for exchangeable image file format, is a standard way of saving metadata within an image file. Many digital cameras and desktop applications support the use of EXIF data. Since EXIF data is actually a part of the file, it shouldn’t get lost in the transfer of the file from one place to another. For instance, when copying a file from the SD card of the Android device to a home computer, this data would remain intact. If you open the file in an application such as iPhoto, the data will be present.
In general, EXIF data is very technically orientated; most of the tags in the standard relate to data about the capturing of the image itself, such as ExposureTime and ShutterSpeedValue.
There are some tags, though, that make sense for us to consider filling in or modifying.
Some of these might include the following:
UserComment: A comment generated by the user
ImageDescription: The title
Artist: Creator or taker of image
Copyright: Copyright holder of image
Software: Software used to create image
Fortunately, Android provides us a nice means to both read and write EXIF data. The main class for doing so is ExifInterface.
Here’s how to use ExifInterface to read specific EXIF data from an image file:
ExifInterface ei = new ExifInterface(imageFilePath);
String imageDescription = ei.getAttribute("ImageDescription");
if (imageDescription != null) {
Log.v("EXIF", imageDescription);
}
Here is how to save EXIF data to an image file using ExifInterface:
ExifInterface ei = new ExifInterface(imageFilePath);
ei.setAttribute("ImageDescription","Something New");
ExifInterface includes a set of constants that define the typical set of data that is automatically included in captured images by the Camera application.
The latest version of the EXIF specification is version 2.3 from April 2010. It is available online here: www.cipa.jp/english/hyoujunka/kikaku/pdf/DC-008-2010_E.pdf.
Summary
Throughout this chapter, we looked at the basics of image capture and storage on Android. We saw how powerful using the built-in Camera application on Android could be and how to effectively leverage its capabilities through an intent. We saw that the
Camera application offers a nice and consistent interface for adding image capture capabilities into any Android application.
We also looked at the need to be conscious of memory usage when dealing with large images. We learned that the BitmapFactory class helps us load scaled versions of an image in order to conserve memory. The need to pay attention to memory reminds us that mobile phones are not desktop computers with seemingly limitless memory.
We went over using Android’s built-in content provider for Images, the MediaStore. We learned how to use it to save images to a standard location on the device as well as how to query it to quickly build applications that leverage already captured images.
Finally we looked at how we can associate certain metadata with images with a standard called EXIF, which is transportable and used in a variety of devices and software applications.
This should give us a great starting point for exploring what more we can do with media on Android.
I am looking forward to it!