Jumping back into MonoDroid development the last couple days at work after having not touched it
in almost a year, I k
new I w
as going to be rusty. Interestingly enough I'm finding it much closer to Windows Phone development than I remembered. Having had no luck
in finding
MonoDroid for Windows Phone Developers I figured I'd start an ongoing post.
Open a Browser
In Windows Phone you would open a Web Browser with the following function:
private void openBrowser(string url) {
  
var task = new WebBrowserTask();
task.Uri = new Uri(url);
task.Show();
}
]]>
However
in MonoDroid you have to
do this:
private void openBrowser(string url) {
  
var browserIntent = new Intent(Intent.ActionView, Android.Net.Uri.Parse(url));
StartActivity(browserIntent);
}
]]>
New Line in TextView
In Windows Phone
in your XAML you'd
do something like
this to insert a
new line into your
TextBlock
:
<TextBlock>This line is awesome<LineBreak/>but this one is better</TextBlock>
]]>
However
in MonoDroid you need to be sure to
set the
singleLine
property to false like so:
<TextView
android:layout_width="fill_parent"
android:singleLine="false"
android:text="This line is awesome\r\nbut this one is better"
/>
]]>
Login form (aka tapping enter goes to next field, with the last field hiding the keyboard)
In your
XAML on Windows Phone you might have something like the following:
<StackPanel Orientation="Vertical">
<TextBox x:Name="TextBoxUsername" />
<TextBox x:Name="TextBoxPassword" />
</StackPanel>
]]>
And then
in your code behind:
public LoginPage() {
  
InitializeComponent();
TextBoxUsername.KeyDown += TextBoxUsername_KeyDown;
TextBoxPassword.KeyDown += TextBoxPassword_KeyDown;
}
void TextBoxUsername_KeyDown(object sender, KeyEventArgs e) {
  
if (e.Key == Key.Enter) {
  
TextBoxPassword.Focus();
}
}
void TextBoxPassword_KeyDown(object sender, KeyEventArgs e) {
  
if (e.Key == Key.Enter) {
  
Focus();
}
}
]]>
Basically upon hitting the enter key
while in the
TextBoxUsername
field it will
set the focus to the
TextBoxPassword
field. Upon hitting enter
in the
TextBoxPassword
field, it will
set the focus to the ma
in page and close the keyboard.
For
MonoDroid, it
is a little different.
In your axml:
<EditText android:id="@+id/TextBoxUsername" android:imeOptions="actionNext" android:singleLine="true" android:layout_width="fill_parent" android:layout_height="wrap_content" />
<EditText android:id="@+id/TextBoxPassword" android:imeOptions="actionDone" android:singleLine="true" android:layout_width="fill_parent" android:layout_height="wrap_content" />
]]>
The key part
is the
android:imeOptions
values,
actionNext will move the focus to the next
EditText
field and the
actionDone will send the done command back to your keylisteners etc.
In your
Activity
code behind, you need to add
this override. Update the
Resource.Id.xxxxxxx
with the name of the field you want the keyboard to hide upon hitting enter:
public override bool DispatchKeyEvent(KeyEvent e) {
  
if (CurrentFocus.Id == Resource.Id.TextBoxPasswordKey && (e.KeyCode == Keycode.NumpadEnter || e.KeyCode == Keycode.Enter)) {
  
var imm = GetSystemService(Context.InputMethodService) as InputMethodManager;
if (imm != null) {
  
imm.HideSoftInputFromWindow(this.CurrentFocus.WindowToken, 0);
}
return true;
}
return base.DispatchKeyEvent(e);
}
]]>
I should also note you'll need the
using Android.Views.InputMethods; line added to your code behind
as well.
Capturing Images via the Camera
In
Windows Phone capturing images from your Library or Taking a
new picture
is pretty trivial, assuming you have a
Button
to choose/take the picture:
PhotoChooserTask _pcTask = null;
private byte[] _pictureBytes;
public PictureUpload() {
  
InitializeComponent();
_pcTask = new PhotoChooserTask();
_pcTask.Completed += new EventHandler<PhotoResult>(_pcTask_Completed);
}
void _pcTask_Completed(object sender, PhotoResult e) {
  
if (e.TaskResult == TaskResult.OK) {
  
MemoryStream ms = new MemoryStream();
e.ChosenPhoto.CopyTo(ms);
_pictureBytes = ms.ToArray();
ms.Dispose();
}
}
private void btnChooseImage_Click(object sender, RoutedEventArgs e) {
  
_pcTask.ShowCamera = true;
_pcTask.Show();
}
]]>
From there just upload the
_pictureBytes
to your WCF Service or wherever.
In
MonoDroid as expected
is a little different, assuming you have a button click
event to take the picture and an
ImageView
to display the image:
private string _imageUri; // Global variable to access the image's Uri later
void btnChooseImage_Click(object sender, EventArgs e) {
  
var uri = ContentResolver.Insert(isMounted ? Android.Provider.MediaStore.Images.Media.ExternalContentUri : Android.Provider.MediaStore.Images.Media.InternalContentUri, new ContentValues());
_imageUri = uri.ToString();
var i = new Intent(Android.Provider.MediaStore.ActionImageCapture);
i.PutExtra(Android.Provider.MediaStore.ExtraOutput, uri);
StartActivityForResult(i, 0);
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) {
  
if (resultCode == Result.Ok && requestCode == 0) {
  
imageView = FindViewById<ImageView>(Resource.Id.ivThumbnail);
imageView.DrawingCacheEnabled = true;
imageView.SetImageURI(Android.Net.Uri.Parse(_imageUri));
}
}
]]>
At
this po
int you have the picture taken and the Uri of the image.
In your Layout:
<Button android:text="Choose Image" android:id="@+id/btnChooseImage" android:layout_width="fill_parent" android:layout_height="wrap_content" />
<ImageView android:id="@+id/ivThumbnail" android:layout_width="300dp" android:layout_height="150dp" />
]]>
Loading a picture from local storage and avoiding the dreaded java.lang.outofmemory exception
A fairly common scenario, maybe pulling an image from the code described above, now you want to upload it some where?
In the
Windows Phone
above
in addition to taking/capturing the picture, we have a
byte[]
with the data, on
MonoDroid it
is a little different. A situation I ran into on my
HTC Vivid w
as a
java.lang.outofmemory exception. Further investigation, apparently
Android
h
as a 24mb VM limit per app (and some devices it
is set to 16mb).
Doing some research, I came across
Twig's post. As expected it w
as in Java, so I converted it over to
MonoDroid and added some additional features to fit my needs. So literally
this function will
return a scaled
Bitmap object for you to turn around and convert to a
Byte[]
.
The function:
private Android.Graphics.Bitmap loadBitmapFromURI(Android.Net.Uri uri, int maxDimension) {
  
var inputStream = ContentResolver.OpenInputStream(uri);
var bfOptions = new Android.Graphics.BitmapFactory.Options();
bfOptions.InJustDecodeBounds = true;
var bitmap = Android.Graphics.BitmapFactory.DecodeStream(inputStream, null, bfOptions);
inputStream.Close();
var resizeScale = 1;
if (bfOptions.OutHeight > maxDimension || bfOptions.OutWidth > maxDimension) {
  
resizeScale = (int)Math.Pow(2, (int)Math.Round(Math.Log(maxDimension / (double)Math.Max(bfOptions.OutHeight, bfOptions.OutWidth)) / Math.Log(0.5)));
}
bfOptions = new Android.Graphics.BitmapFactory.Options();
bfOptions.InSampleSize = resizeScale;
inputStream = ContentResolver.OpenInputStream(uri);
bitmap = Android.Graphics.BitmapFactory.DecodeStream(inputStream, null, bfOptions);
inputStream.Close();
return bitmap;
}
]]>
For a practical use, loading the image, scaling
if necessary and then getting a
Byte[]
:
var bitmap = loadBitmapFromURI(Android.Net.Uri.Parse(_imageUri), 800);
var ms = new MemoryStream();
bitmap.Compress(Android.Graphics.Bitmap.CompressFormat.Jpeg, 100, ms);
]]>
At
this po
int doing a
ms.ToArray()
will
get you to the same po
int the
Windows Phone
code above did, so
if you had a WCF Service, you could at
this po
int upload the
byte array just
as you could with a
Windows Phone
above.
Transparent Background on a ListView, LayoutView etc?
In
Windows Phone you can
set the
Background
or
Foreground
properties to simply
Transparent
like so:
<StackPanel Background="Transparent" Orientation="Vertical">
<TextBlock>Transparentcy is awesome</TextBlock>
</StackPanel>
]]>
In
MonoDroid it's simply "@null" like so
for the
ListView
<ListView android:background="@null" android:layout_width="match_parent" android:layout_height="match_parent">
<TextView android:text="Transparentcy is awesome" android:gravity="center" />
]]>
Locking Orientation
In
Windows Phone you can
set your page's orientation to be forced into
Landscape
or
Portrait
in your xaml like so:
<phone:PhoneApplicationPage
SupportedOrientations="Portrait" Orientation="Portrait">
]]>
In
MonoDroid I couldn't figure
out a better way to
do it than the following line inside your Activity's
OnCreate
function like so:
protected override void OnCreate(Bundle bundle) {
  
base.OnCreate(bundle);
RequestedOrientation = ScreenOrientation.Portrait;
}
]]>
On a side note, you'll need to add
this line to the top of your
Activity
if it isn't already there:
using Android.Content.PM;