We are now ready to take on the task of capturing a photo. This will involve the following tasks:
The following sections describe the details of each step.
There are a few new UI elements we will need to add to support capturing an image; we need an ImageButton
element to initiate the process of capturing an image, and we also need an ImageView
element to display the image. We will add the new ImageButton
element at the bottom of the View next to the location and map buttons. We will add the ImageView
element just below the Latitude and Longitude fields and just above the buttons at the bottom. The following list shows the definition for the ImageView
, which should be placed just below the TableLayout
used for the Latitude and Longitude widgets:
... </TableRow> </TableLayout> <ImageView p1:src="@android:drawable/ic_menu_gallery" p1:layout_width="wrap_content" p1:layout_height="wrap_content" p1:padding="10dp" p1:id="@+id/poiImageView" p1:layout_gravity="center_horizontal" p1:scaleType="fitCenter" /> <LinearLayout ...
Create a private reference object in POIDetailActivity
and assign the reference in OnCreate()
:
ImageView _poiImageView; ... _poiImageView = FindViewById<ImageView> (Resource.Id.poiImageView);
Now, we need a button. Start by copying the ic_new_picture.png
icon from the assets
folder to the project's drawable
folder and adding it to the project in the same manner as we did in the previous chapters. Add the following button definition to the LinearLayout
that contains the other buttons:
<ImageButton p1:src="@drawable/ic_new_picture" p1:layout_width="wrap_content" p1:layout_height="wrap_content" p1:id="@+id/photoImageButton" />
Create a private reference object in POIDetailActivity
and assign the reference in OnCreate()
as follows:
ImageButton _photoImageButton; ... _photoImageButton =FindViewById<ImageButton> (Resource.Id.photoImageButton);
To start an external camera app to capture a photo, we rely on the Intent
class again, this time combined with an action. The following listing depicts creating an Intent
with the image capture action:
Intent cameraIntent = new Intent(MediaStore.ActionImageCapture);
The MediaStore.ActionImageCapture
action tells the Android platform you want to capture a photo and are willing to use any existing app that provides those capabilities.
In Chapter 7, Making POIApp Location Aware, we used PackageManager
to check to see if a map app was present to handle our intent. We now need to perform the same check for an app that can handle our ActionImageCapture
intent. The following listing shows the logic we need:
PackageManager packageManager = PackageManager; IList<ResolveInfo> activities =packageManager.QueryIntentActivities(cameraIntent, 0); if (activities.Count == 0) { //display alert indicating there are no camera apps } else { //launch the cameraIntent }
Prior to starting the intent, we need to provide some information to the camera app that processes our request; specifically, a filename and location, and the maximum size of the resulting photo. We do this by adding Extras
to the intent. The MediaStore
class defines a number of standard Extras
that can be added to an intent to control how an external app fulfils the intent.
The MediaStore.ExtraOutput
extra can be added to control the filename and location the external app should use in order to capture an image. We previously enhanced the data service to provide this information. Unfortunately, we will need to convert the string path we get from the data service to an instance of Android.Net.Uri
, which is the expected format for camera apps that consume MediaStore.ExtraOutput
.
This is a two-step process. First, we create a Java.IO.File
object using the string path from the data service and then create an Android.Net.Uri
object. The following listing shows how to accomplish the construction of the URI and set up the MediaStore.ExtraOutput
extra:
Java.IO.File imageFile = new Java.IO.File(POIData.Service.GetImageFilename(_poi.Id.Value)); Android.Net.Uri imageUri = Android.Net.Uri.FromFile (imageFile); cameraIntent.PutExtra (MediaStore.ExtraOutput, imageUri);
We are now ready to start the intent. In other cases where we used the Intent
class, we were not looking for any information to be provided as a result. In this case, we are expecting the photo app to provide either a photo or a notification that the user cancelled the photo. You accomplish this by using StartActivityForResult()
by passing in the intent. The StartActivityForResults()
method works in conjunction with a callback to OnActivityResult()
, to communicate the results of the intent. The following listing depicts the calling of StartActivityForResult()
:
const int CAPTURE_PHOTO = 0; . . . StartActivityForResult(cameraIntent, CAPTURE_PHOTO);
Notice the second parameter to StartActivityForResult()
. It is an int
value named requestCode
that will be returned as a parameter in the callback to OnActivityResult()
and help identify the original reason for launching an intent. The best practice is to define a constant value to pass in for each requestCode
that can potentially cause OnActivityResult()
to be called.
We have covered a number of topics related to starting the camera app in a somewhat fragmented fashion. The following listing is the complete implementation for NewPhotoClicked()
:
public void NewPhotoClicked(object sender, EventArgs e) { if (!_poi.Id.HasValue) { AlertDialog.Builder alertConfirm=new AlertDialog.Builder(this); alertConfirm.SetCancelable(false); alertConfirm.SetPositiveButton("OK", delegate {}); alertConfirm.SetMessage( "You must save the POI prior to attaching a photo"); alertConfirm.Show (); } else { Intent cameraIntent = new Intent (MediaStore.ActionImageCapture); PackageManager packageManager = PackageManager; IList<ResolveInfo> activities = packageManager.QueryIntentActivities(cameraIntent, 0); if (activities.Count == 0) { AlertDialog.Builder alertConfirm = newAlertDialog.Builder(this); alertConfirm.SetCancelable(false); alertConfirm.SetPositiveButton("OK", delegate {}); alertConfirm.SetMessage( "No camera app available to capture photos."); alertConfirm.Show (); } else { Java.IO.File imageFile = new Java.IO.File(POIData.Service.GetImageFilename(_poi.Id.Value)); Android.Net.Uri imageUri = Android.Net.Uri.FromFile (imageFile); cameraIntent.PutExtra (MediaStore.ExtraOutput, imageUri); cameraIntent.PutExtra (MediaStore.ExtraSizeLimit,1.5 * 1024); StartActivityForResult (cameraIntent, CAPTURE_PHOTO); } } }
The initiating activity is notified of the results of an intent via the OnActivityResult()
callback method. The following listing shows the signature for the OnActivityResult()
method:
OnActivityResult (int requestCode, Result resultCode, Intent data)
We discussed requestCode
in the previous section. The resultCode
parameter indicates the result of the intent that was launched and is of type Result
, which can have the following values:
Value |
Meaning |
---|---|
|
The activity completed the request successfully. |
|
The activity was cancelled, generally by a user action. |
|
The first value that can be used for a custom meaning. |
The third parameter, data
, is of type Intent
and can be used to pass additional information back from the activity that was launched. In our case, we are only concerned with requestCode
and resultCode
. The following listing shows the implementation of OnActivityResult()
in POIDetailActivity
:
protected override void OnActivityResult (int requestCode,Result resultCode, Intent data) { if (requestCode == CAPTURE_PHOTO) { if (resultCode == RESULT_OK) { // display saved image Bitmap poiImage = POIData.GetImageFile (_poi.Id.Value); _poiImageView.SetImageBitmap (poiImage); if (poiImage != null) poiImage.Dispose (); } else { // let the user know the photo was cancelled Toast toast = Toast.MakeText (this, "No picture captured.",ToastLength.Short); toast.Show(); } } else base.OnActivityResult (requestCode, resultCode, data); }
Notice that when resultCode
is RESULT_OK
, we load the photo that was captured into a Bitmap object and then set the image for _poiImageView
. This causes the image to be displayed at the bottom of the POIDetail
layout. If resultCode
is not RESULT_OK
, we display a toast message to the user indicating that the action was cancelled.
You will also notice the magic method GetImageFile()
on POIData
that just showed up from nowhere. It is actually not magic; we need to add it. The GetImageFile()
method is a simple utility method that accepts a POI ID and loads Android.Graphics.Bitmap
using the Android utility class BitmapFactory
. The following listing shows the GetImageFile()
method:
public static Bitmap GetImageFile(int poiId) { string filename = Service.GetImageFilename (poiId); if (File.Exists (filename)) { Java.IO.File imageFile = new Java.IO.File (filename); return BitmapFactory.DecodeFile (imageFile.Path); } else return null; }
We could have simply embedded this code in OnActivityResult()
, but we will need the same functionality in a few more places. We could have also chosen to add the method to POIJsonService
, but that would have required us to introduce specific Android types to the data service, which would have limited its reuse in other platforms.
We have added a lot of code. Run POIApp
and test adding a photo.
18.119.131.178