The fourth and final part in the not-as-short-as-I-thought-it-would be series on building a camera app for Android using Oxygene. In this penultimate instalment we will add the capability to actually take a picture. But that won’t take very long, so then we will spend a bit of time tidying up the application UI.
Taking A Picture
Taking a picture on Android is actually very straightforward. Once you have a reference to the Camera object (which we already have) it is a simple matter of calling the takePicture() method. This method requires a reference to an implementation of the Camera.PictureCallback interface.
This is a simple interface which I chose to implement on the MainActivity itself, resulting in this new version of the MainActivity class:
MainActivity = public class(Activity, Camera.PictureCallback) private mCamera: Camera; mViewFinder: ViewFinder; public method onCreate(savedInstanceState: Bundle); override; method onDestroy; override; method CaptureClick(v: View); private // Camera.PictureCallback method onPictureTaken(aPicture: array of SByte; aCamera: Camera); end;
The code we need to add to the CaptureClick method is trivial:
method MainActivity.CaptureClick(v: View); begin // Stop the preview while we take the picture mViewFinder.stop; mCamera.takePicture(NIL, NIL, self); end;
This call triggers an asynchronous image capture from the camera. The three parameters to the method in this call are references to callbacks called at different points in the image capture process.
The first is a Camera.ShutterCallback – this is a simple notification that the shutter has fired and a picture taken. I could use this to play a sound for the user’s benefit, for example.
The second is a Camera.PictureCallback – this is called when the RAW image data is ready. I could use this if I wished to capture the RAW image data.
The third is also a Camera.PictureCallback and is called then the JPEG image data is ready. This is the only notification I am interested in for this app, which is why the first two callbacks are specified as NIL.
Of course, I also need to implement that JPEG callback – my onPictureTaken implementation:
method MainActivity.onPictureTaken(aPicture: array of SByte; aCamera: Camera); var timestamp: String; dir: File; jpegFile: File; stream: FileOutputStream; begin // Get a string containing formatted datetime as the basis for a unique filename timestamp := new SimpleDateFormat('yyyyMMdd_HHmmss').format(new Date()); // Obtain the location of the pictures storage directory and compose a file dir := Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); jpegFile := new File(dir.Path + File.separator + 'IMG_' + timestamp + '.jpg'); // If we have a valid File, open an output stream and write the aPicture data to it if assigned(jpegFile) then begin stream := new FileOutputStream(jpegFile); stream.write(aPicture); stream.close; end; // Restart the viewfinder preview mViewFinder.start; end;
You may have noticed the calls to start() and stop() the viewfinder. These were the result of a simple (manual) refactoring of the existing ViewFinder code previously contained in the onSurfaceCreated and onSurfaceDestroyed methods.
One more finesse and we’re almost done.
Maintaining (or rather Obtaining) Focus
I thought it would be cute to add a “touch to autofocus” feature. Nothing fancy, just tap the preview to trigger the camera to autofocus.
This will be handled by the ViewFinder class, so we add the View.OnTouchListener interface to the list of implemented interfaces and the corresponding onTouch method to the declaration of that class:
ViewFinder = public class(SurfaceView, SurfaceHolder.Callback, View.OnTouchListener) // ... private // View.OnTouchListener method onTouch(aView: View; aEvent: MotionEvent): Boolean; // ... end;
In the existing constructor we add the simple assignment of the ViewFinder as it’s own listener for touch events:
OnTouchListener := self;
And finally of course, implement the onTouch method itself:
method ViewFinder.onTouch(aView: View; aEvent: MotionEvent): Boolean; begin mCamera.autoFocus(NIL); end;
autoFocus is another example of an asynchronous method that will notify a callback when the operation is complete, in this case indicating whether focus was successful or not via parameters passed to the call back. Again, we could use this to provide some audio feedback or perform other processing with a focussed preview image. But I’m not going to bother so can simply pass NIL.
That’s it!
The app is (almost) complete with the following features.
- Live, real-time “viewfinder” preview
- Image capture to Pictures storage on the device
- Touch-to-Focus AutoFocus feature
By no means perfect, but good enough for now.
There is one last feature I want to add, but that is the subject of the next and absolutely final instalment.
One last thing I do want to do at this point however is tidy up the UI.
Polishing The Pig
Our app isn’t very pretty right now. In fact, downright ugly would be a more honest appraisal:
(The “Snapper” title is because I grabbed this screen shot using an early, experimental version of the app before I changed the name. The UI was otherwise exactly as described up to now)
Horrible. It doesn’t even look very “Android-y”. Of course it is strictly speaking entirely and utterly Android. But the reason it looks a bit “off” on my ICS tablet (I think I said before at some point that I was running JellyBean, but I realised I’m not) is that my app is not using an ActionBar so familiar from more recent versions of Android (since 3.0). An ActionBar will actually provide a better place for our capture button as well, allowing the entire screen (apart from the Action Bar area obviously) to be used for the viewfinder.
There are a few steps involved.
First, we set an appropriate minimum SDK version in our manifest. Action Bars were first introduced in 3.0 (SDK 11). We can get them on 2.1 devices as well with a little more work, but for my purposes SDK version 11 is good enough. While I’m at it I may as well also add the intended target version for my device (4.0.3 – API level 14):
<uses-sdk android:minSdkVersion="11" android:targetSdkVersion="14"/>
This is all we need to do to actually enable the Action Bar for the app, but we do need to make it visible which we easily achieve with the addition of a simple call added to the MainActivity.onCreate method:
self.ActionBar.show;
While we’re updating the onCreate method we can also remove the code that assigns the onClick listener for the button since we won’t be needing that anymore. We keep the CaptureClick method itself though. All will become clear.
Now we need to change the layout – it is actually made simpler since we do not need to explicitly declare either the Action Bar or the capture button (for reasons that will become clear in a moment):
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <FrameLayout android:id="@+id/camera_preview" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_centerHorizontal="true" android:layout_gravity="center_horizontal|top"> </FrameLayout> </RelativeLayout>
As I said, the ActionBar itself is provided for us automatically, displaying the application icon and name.
To add additional items to the bar, we define a menu resource. This is a further XML file placed in a folder called, funnily enough, menu, within our project folder (a sibling of the layout and values folders, within the res folder):
In this folder I create a new XML file which I choose to call main.xml, with the following content:
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/capture_button" android:icon="@android:drawable/ic_menu_camera" android:orderInCategory="100" android:showAsAction="always" android:onClick="captureClick" /> </menu>
If I understand things correctly, this menu resource defines actions that will appear either on the popup menu of our activity when the device “menu” button is pressed, or will be presented in the ActionBar for the activity, or both. Where and when these menu items appear is governed by the android:showAsAction attribute.
In this case I have just the one item which I want always to be shown as an action – i.e. as a button on the ActionBar. This button has no caption and is represented by an icon representing a camera. I can provide my own icon image if I wish, but in this case I am simply identifying the stock Android menu icon for the camera.
This menu item replaces my previous button.
I set the onClick attribute to reference the captureClick method of my activity. However, the onClick for a MenuItem is slightly different to that for a Button, so we need to change the signature of the method – it now accepts a MenuItem parameter, rather than a View. While I’m at it, I apply the lowercase initial letter convention for methods that I am starting to grow accustomed to and which, it has to be said, is in turn growing on me. You can see that I already identified the method using this case convention in the menu item declaration.
Unfortunately, this menu is not loaded automatically. An Activity must explicitly “inflate” the appropriate menu resource at runtime. This presumably is in order to allow an Activity to choose the appropriate menu according to the runtime conditions and/or to make any adjustments that might be appropriate.
Inflating the menu is very simple and involves overriding the onCreateOptionsMenu method of the MainActivity:
method MainActivity.onCreateOptionsMenu(aMenu: Menu): Boolean; begin MenuInflater.inflate(R.menu.main, aMenu); result := inherited; end;
And we’re done.
That might have seemed like a lot of work and very fiddly, but it really wasn’t. It took me longer to write it up for this post than it took to research (a one time cost) and implement.
I’ve learned enough in the process that I could now create myself a new project template for Visual Studio, to create an “Empty Action Bar” application in one go.
The result was well worth it I think:
Very good !
I’ve been following this series of posts & it’s been great to see someone in the same position as myself (long time Delphi developer, since v1) getting to grips with both Oxygene and Android development.
I’ve had Oxygene for some time, but am only just really starting to use it after waiting to see what XE5 was going to offer. I’m not impressed with the way that XE5 / Firemonkey works, and can see HUGE limitations as to what you can do with it.
My main concern is that there is no way (as far as I can see) to send or receive android intents, and that there is no support for creating a ‘service’ activity which would run in the background. To me these are ESSENTIAL capabilities of android applications.
Therefore, I have FINALLY made the decision that I will (regrettably) abandon Delphi for all future development (including Windows due to lack of new innovation in the VCL) and focus on using Oxygene.
Hi Jolyon
I have been following your Android series and have to say that I am impressed.
Please keep them coming. I am slowly moving to mobile world and I think Oxygene solution is for me.
It enables to learn standard API (no need to wait for updates from EMB) and gives ability to code in a similar Pascal environment. At least in my case it is a winner.
I have downloaded a trial version this week. Do you think you could share the entire code for the photo app ?
On the other note, this week I have received my SA renewal from EMB and it came with an another increase. I am undecided if I want to wait till a next version for the VCL framework to be improved.
A lot of bugs needs to be fixed in order to maintain a quality product. I might give them one more year as you do. I think for a desktop Windows it is still a good product.
Cheers.
Chris
Avid reader of your blog.
Thanks Chris. And yes, I shall post the full source to the app with the next (and final – I promise!) instalment. π
w.r.t your SA increase… did they comply with their own terms and conditions ?
Under section 7 “Fees”. Any increase must be notified at least 30 days prior to renewal.
Under section 12 “Notices” (e). Any notice must be in writing, by letter, fax or personal delivery.
In my case the only notice of the increase was when I received the emailed renewal invoice (not a valid notice under section 12e) and much less than 30 days from the renewal day (breach of obligation under section 7).
I challenged them on mine and they backed down. I didn’t renew anyway – but there was a principle involved.
Oxygene needs a form designer! That would defenitifly increase sales. Especially, much more people would switch from Delphi to Oxygene, including me π
I am too lazy to create UIs by hand (code or xml).
I mean an integrated form designer.
As an experiment I created the revised layout in this app using the form designer in Android Studio. It was OK but honestly writing the XML by hand was quicker. And unlike Xcode Interface Builder, you cannot easily edit a layout file in isolation from a complete project with Android Studio.
I might come back to the issue of form design in a future post, but obviously this app was not particularly demanding in this area so not much can be learned from it one way or the other in that respect.
kkkkkkkk Oxygene. a copy of the java poorly made and on your other post about the size you are looking app already installed then it always about the size now if u look at the apk goes much lower and is the apk that u then lowers his comparison was not done right
Great post!
If you find some time you can test this too:
http://blog.synopse.info/post/2013/09/19/FreePascal-Lazarus-and-Android-Native-Controls
http://blog.naver.com/simonsayz/120196955980
Jolyon, it’s a very detailed and helpful introduction into Android programming with Oxygene, thank you! I was able to run the app on the Android 4.3 emulator. Though it not works on Android 2.3.3 (nor emulator nor real phone). Exception: ‘java.lang.RuntimeException: startPreview failed’ at android.view.ViewRoot.draw. I did not use the ActionBar which is unavailable on Android 2.x as you have specified.
It’s a pity that Oxygene has no a GUI form designer. Yes, there is auto-completion feature for XML, but it cannot suggest a value for a parameter. For ex. android:orientation=”horizontal” – we have to type “horizontal” by hand. And in general the Delphi IDE more capable than VS 2012 IDE in many ways IMO. But for other reasons Oxygene is the most advanced Pascal-style environment for the moment.
Yes, I have noticed the problem with attribute values myself. The XML editor in Android Studio does provide autocompletion even for this so perhaps Oxygene can and will catch up in that regard. in the meantime I could of course use Android Studio for any complex XML editing (and indeed visual form design), but it’s a bit clunky.
I’ll take a look at the issues you mention with the app. Not running on 2.3.3 is more-or-less expected – as I think I mentioned in the posts, there were some changes in the accepted uses of certain API’s in some versions of Android, where you cannot change camera settings with an active preview in older Androids.
My code is flagrantly ignoring those issues.
Update: It’s ignoring a whole bunch of things that it shouldn’t be. I am only an egg when it comes to Android. I am tidying up these issues as I identify them (mostly trivial) and will include these in the final version with the next instalment. π
Unfortunately I recently upgraded my 2.3.3 Gingerbread phone to a custom 4.2.2 ROM, but I will check things in a 2.3.3 emulator.
OK, thank you for the answer! At least it’s not my fault as I thought. π I’ll try fix the code for 2.3.3 too.
We need add this line to the constructor ViewFinder:
mSurfaceHolder.Type := SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS;
Source: http://developer.android.com/guide/topics/media/camera.html
That’s one thing, yes. Another is stopping and starting the preview when adjusting camera settings. But the biggest impact is needing to use the Support Library for pre 3.0 devices to be able to use the ActionBar UI.
This is potentially such a significant subject that it will make for a useful article in it’s own right I think. π
OMG!!!
The amount of work(code) involved for building such a simple app is just unacceptable.
I think one should use alternative development tools instead of being stubborn and sticking to Delphi or Oxygen for mobile development. The first alternative that comes to mind is Livecode.
Yogi, you’re certainly kidding, comparing professional programming tools with the beginner’s IDE with the toy language.
Keyvich, Livecode is by no means a beginner’s IDE. Yes it definitely helps a developer in coding less and getting more!!!
It seems you need some enlightenment:
http://newsletters.livecode.com/june/issue111/newsletter4.php
http://www.itwriting.com/blog/4319-hands-on-with-runrev-livecode.html
http://forums.runrev.com/viewtopic.php?t=5512