Showing posts with label Xamarin.Android. Show all posts
Showing posts with label Xamarin.Android. Show all posts

Thursday, September 12, 2013

Creating and using sliding menus in Xamarin.Android

I needed a stylish way to navigate menu options in my app and by piecing together different examples from documentation and online forums I was able to get my menu to slide forward when traversing deeper into my menus, and slide backwards when backing up.

In my Menu layout I specify a ViewFlipper as the parent view, and then underneath that I list all of the views I'll be sliding between.  I've used TableLayouts as my child views since I think they work well for buttons, but you can use LinearLayouts or whatever you prefer.  Make sure you give each view a unique ID and then place the file in the Resources\layout folder of your project.

MySlidingMenu.axml:
<?xml version="1.0" encoding="utf-8"?>
<ViewFlipper xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@+id/viewFlipper"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent">
 <TableLayout
  android:id="@+id/tlMainMenu"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content">
  <Button
   android:id="@+id/btnSubmenu1"
   android:text="To Submenu1"
   android:minWidth="25px"
   android:minHeight="25px"
   android:layout_width="fill_parent"
   android:layout_height="wrap_content" />
  <Button
   android:id="@+id/btnSubmenu2"
   android:text="To Submenu2"
   android:minWidth="25px"
   android:minHeight="25px"
   android:layout_width="fill_parent"
   android:layout_height="wrap_content" />
 </TableLayout>
 <TableLayout
  android:id="@+id/tlSubmenu1"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content">
  <Button
   android:id="@+id/btnAction1"
   android:text="Action One"
   android:minWidth="25px"
   android:minHeight="25px"
   android:layout_width="fill_parent"
   android:layout_height="wrap_content" />
  <Button
   android:id="@+id/btnCancel1"
   android:text="Cancel"
   android:minWidth="25px"
   android:minHeight="25px"
   android:layout_width="fill_parent"
   android:layout_height="wrap_content" />
 </TableLayout>
 <TableLayout
  android:id="@+id/tlSubmenu2"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content">
  <Button
   android:id="@+id/btnAction2"
   android:text="Action Two"
   android:minWidth="25px"
   android:minHeight="25px"
   android:layout_width="fill_parent"
   android:layout_height="wrap_content" />
  <Button
   android:id="@+id/btnSubSubmenu"
   android:text="To Sub-Submenu"
   android:minWidth="25px"
   android:minHeight="25px"
   android:layout_width="fill_parent"
   android:layout_height="wrap_content" />
  <Button
   android:id="@+id/btnCancel2"
   android:text="Cancel"
   android:minWidth="25px"
   android:minHeight="25px"
   android:layout_width="fill_parent"
   android:layout_height="wrap_content" />
 </TableLayout>
 <TableLayout
  android:id="@+id/tlSubSubmenu"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content">
  <Button
   android:id="@+id/btnAction3"
   android:text="Action Three"
   android:minWidth="25px"
   android:minHeight="25px"
   android:layout_width="fill_parent"
   android:layout_height="wrap_content" />
  <Button
   android:id="@+id/btnCancel3"
   android:text="Cancel"
   android:minWidth="25px"
   android:minHeight="25px"
   android:layout_width="fill_parent"
   android:layout_height="wrap_content" />
 </TableLayout>
</ViewFlipper>
Next, you'll need four animation XML files that tell the screen how to transition.  Create these four files and then place them in the Resources\anim\ folder in your project.  You will probably need to create the anim folder if it doesn't already exist.

slide_in_left.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
 <translate android:fromXDelta="-50%p" android:toXDelta="0"
            android:duration="@android:integer/config_mediumAnimTime"/>
 <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
            android:duration="@android:integer/config_mediumAnimTime" />
</set>
slide_in_right.xml:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
 <translate android:fromXDelta="50%p" android:toXDelta="0"
            android:duration="@android:integer/config_mediumAnimTime"/>
 <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
            android:duration="@android:integer/config_mediumAnimTime" />
</set>
slide_out_left.xml:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
 <translate android:fromXDelta="0" android:toXDelta="-50%p"
            android:duration="@android:integer/config_mediumAnimTime"/>
 <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
            android:duration="@android:integer/config_mediumAnimTime" />
</set>
slide_out_right.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
 <translate android:fromXDelta="0" android:toXDelta="50%p"
            android:duration="@android:integer/config_mediumAnimTime"/>
 <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
            android:duration="@android:integer/config_mediumAnimTime" />
</set>
Now you have all of the XML files, the last thing you need is the code added to your activity.  I suggest declaring the variables that hold the ViewFlipper, Views and Buttons at the top of your Activity like so:
private ViewFlipper vf;
private TableLayout tlMainMenu; 
private TableLayout tlSubmenu1; 
private TableLayout tlSubmenu2;
private TableLayout tlSubSubmenu;
private Button btnSubmenu1;
private Button btnSubmenu2;
private Button btnSubSubmenu;
private Button btnCancel1;
private Button btnCancel2;
private Button btnCancel3;
I didn't include the Action buttons because those are just placeholders for whatever it is you want to display on your own menu so I don't need to show you how to configure those.  Next, the following goes in your OnCreate method:
SetContentView (Resource.Layout.MySlidingMenu);

vf = FindViewById<ViewFlipper> (Resource.Id.viewFlipper);
tlMainMenu = FindViewById<TableLayout> (Resource.Id.tlMainMenu);
tlSubmenu1 = FindViewById<TableLayout> (Resource.Id.tlSubmenu1);
tlSubmenu2 = FindViewById<TableLayout> (Resource.Id.tlSubmenu2);
tlSubSubmenu = FindViewById<TableLayout> (Resource.Id.tlSubSubmenu);
btnSubmenu1 = FindViewById<Button> (Resource.Id.btnSubmenu1);
btnSubmenu2 = FindViewById<Button> (Resource.Id.btnSubmenu2);
btnSubSubmenu = FindViewById<Button> (Resource.Id.btnSubSubmenu);
btnCancel1 = FindViewById<Button> (Resource.Id.btnCancel1);
btnCancel2 = FindViewById<Button> (Resource.Id.btnCancel2);
btnCancel3 = FindViewById<Button> (Resource.Id.btnCancel3);

btnSubmenu1.Click += delegate {
 vf.SetInAnimation(this, Resource.Animation.slide_in_right);
 vf.SetOutAnimation(this, Resource.Animation.slide_out_left);
 vf.DisplayedChild = vf.IndexOfChild(tlSubmenu1);
};
btnSubmenu2.Click += delegate {
 vf.SetInAnimation(this, Resource.Animation.slide_in_right);
 vf.SetOutAnimation(this, Resource.Animation.slide_out_left);
 vf.DisplayedChild = vf.IndexOfChild(tlSubmenu2);
};
btnSubmenu3.Click += delegate {
 vf.SetInAnimation(this, Resource.Animation.slide_in_right);
 vf.SetOutAnimation(this, Resource.Animation.slide_out_left);
 vf.DisplayedChild = vf.IndexOfChild(tlSubmenu3);
};
btnSubSubmenu.Click += delegate {
 vf.SetInAnimation(this, Resource.Animation.slide_in_right);
 vf.SetOutAnimation(this, Resource.Animation.slide_out_left);
 vf.DisplayedChild = vf.IndexOfChild(tlSubSubmenu);
};
btnCancel1.Click += delegate {
 vf.SetInAnimation(this, Resource.Animation.slide_in_left);
 vf.SetOutAnimation(this, Resource.Animation.slide_out_right);
 vf.DisplayedChild = vf.IndexOfChild(tlMainMenu);
};
btnCancel2.Click += delegate {
 vf.SetInAnimation(this, Resource.Animation.slide_in_left);
 vf.SetOutAnimation(this, Resource.Animation.slide_out_right);
 vf.DisplayedChild = vf.IndexOfChild(tlMainMenu);
};
btnCancel3.Click += delegate {
 vf.SetInAnimation(this, Resource.Animation.slide_in_left);
 vf.SetOutAnimation(this, Resource.Animation.slide_out_right);
 vf.DisplayedChild = vf.IndexOfChild(tlSubmenu2);
};
Notice that the submenu buttons all have an InAnimation of slide_in_right and an OutAnimation of slide_out_left, while the cancel buttons all have an InAnimation of slide_in_left and an OutAnimation of slide_out_right.  This gives the illusion that submenus are further to the right, and your parents menus are to the left via the sliding animation.

Lastly, you'll want to add support for using the back button to navigate your menus.  Since the back button should do something different based on where you are at on the menu I suggest using the following method to capture the use of the back key, determine where you are and simulate the click of the cancel/back button on that portion of the menu.  Also, this is the reason for declaring the ViewFinder/Views/Buttons outside of the OnCreate method, because if we declared them locally we'd have to re-declare them for this method:
public override bool OnKeyDown (AndroidViewsKeycode keyCode, AndroidViewsKeyEvent e)
{
 if (keyCode == Keycode.Back) {
  if (vf.DisplayedChild == vf.IndexOfChild (tlSubmenu2))
   btnCancel1.PerformClick ();
  else if (vf.DisplayedChild == vf.IndexOfChild (tlSubmenu3))
   btnCancel2.PerformClick ();
  else if (vf.DisplayedChild == vf.IndexOfChild (tlSubSubmenu))
   btnCancel3.PerformClick ();
  return true;
 }
 else
  return base.OnKeyDown (keyCode, e);
}
That's it!  You now have everything you need to create the illusion of sliding menus, and the ability to traverse them at will.  Please leave me a comment if this was helpful for you, or if you have any questions.  Thank you!

Thursday, July 18, 2013

Using Intents in Xamarin to catch "Send to" and "Open with" file prompts

In Xamarin for Android you don't directly edit the AndroidManifest.xml file which means you need to create code that will generate the necessary portions of the manifest which direct your application to make itself available to handle incoming file hand-offs.  This is done using an IntentFilter which you place at the top of the class for the activity which you want to handle the incoming data.  In my case, I chose a neutral splashscreen as the loading activity so that the user will see my app's loading screen while I determine and load the true activity that will be handling the file.

namespace MyApp

{

 [Activity (Label="MyAppName", MainLauncher = true, Icon="@drawable/icon", Theme = "@style/ThemeBase.Splash", NoHistory = true)]

 [IntentFilter (new[]{Intent.ActionView},

 Categories=new[]{Intent.ActionDefault, Intent.CategoryBrowsable, Intent.ActionSend, Intent.ActionSendMultiple},

 DataScheme="mimetype",

 DataPathPattern="*/*",

 DataHost="*.*")]

 public class SplashActivity : Activity

 {

  protected override void OnCreate (Bundle bundle)

  {

   base.OnCreate (bundle);



   Intent intent = Intent;

   String action = intent.Action;

   String type = intent.Type;

   if (Intent.ActionSend.Equals(action) && type != null) {

    if (type.StartsWith("image/")) {

     handleSendImage(intent); // Handle single image being sent

    }

   } else if (Intent.ActionSendMultiple.Equals(action) && type != null) {

    if (type.StartsWith("image/")) {

     handleSendMultipleImages(intent); // Handle multiple images being sent

    }

   } 

   else

   {
    // Start our real activity if no acceptable file received

    StartActivity (typeof (MainActivity));

   }

  }

    }

}

The above code allows me to handle any filetype at all (DataHost="*.*") and will cause my application to appear as an option in the "Send" link of any other app.  If you only need to handle a specific filetype you would change the second * in DataHost to the filetype you need, such as (DataHost="*.png") would only handle PNG (Portable Network Graphics) files.  

In my case, since I am accepting all filetypes, I need to determine what type of file I was given and handle it appropriately.  You can see this occurring in the operations with the data derived from intent (intent, action, type), which is the incoming file details and requested action by the other app.  The action type gives a hint on what needs to be done with the file being sent (Send, Open, View, etc.) and the type tells you the MIME type information for the file, whether it's a PDF, image file, APK, MP3, webpage, etc.  If you aren't sure what file type you need to receive, set your DataHost to *.* then set a breakpoint, debug your app and inspect the intent.type after opening the desired file.

Sunday, June 16, 2013

Example of Deleting from SQLite DB using C# in Xamarin Android

I tried to use LINQ syntax to delete my objects from a table like shown here, but got an error "Cannot store type:  MyObject":

private static void DeleteOldObjects()
{
 string path = Path.Combine (System.Environment.GetFolderPath (System.Environment.SpecialFolder.MyDocuments), "MyDatabase.db");
 var db = new SQLiteConnection(path,password,false);
 var query = db.Table<MyObject>().Where(rt => rt.Date < DateTime.Now.AddDays(-3));

 if (query != null) {
  foreach (var object in query.ToList<MyObject>()) {
   db.Delete<MyObject>(object);
  }
 }
 db.Commit ();
}

From what I could determine by looking at the SQLite.cs code, it seems like this error is when it's trying to bind my object as a parameter:


internal static void BindParameter (IntPtr stmt, int index, object value)
{
  if (value == null) {
    SQLite3.BindNull (stmt, index);
  } else {
    if (value is Int32) {
      SQLite3.BindInt (stmt, index, (int)value);
    } else if (value is String) {
      SQLite3.BindText (stmt, index, (string)value, -1, NegativePointer);
    } else if (value is Byte || value is UInt16 || value is SByte || value is Int16) {
      SQLite3.BindInt (stmt, index, Convert.ToInt32 (value));
    } else if (value is Boolean) {
      SQLite3.BindInt (stmt, index, (bool)value ? 1 : 0);
    } else if (value is UInt32 || value is Int64) {
      SQLite3.BindInt64 (stmt, index, Convert.ToInt64 (value));
    } else if (value is Single || value is Double || value is Decimal) {
      SQLite3.BindDouble (stmt, index, Convert.ToDouble (value));
    } else if (value is DateTime) {
      SQLite3.BindText (stmt, index, ((DateTime)value).ToString ("yyyy-MM-dd HH:mm:ss"), -1, NegativePointer);
    } else if (value.GetType ().IsEnum) {
      SQLite3.BindInt (stmt, index, Convert.ToInt32 (value));
    } else if (value is byte[]) {
      SQLite3.BindBlob (stmt, index, (byte[])value, ((byte[])value).Length, NegativePointer);
    } else {
      throw new NotSupportedException ("Cannot store type: " + value.GetType ());
    }
  }
}

That seemed to me like the most intuitive way to perform that task since it's the same syntax used to insert objects, but since it didn't work I found you actually need to pass the primary key of the object you want to delete, like this:

private static void DeleteOldObjects()
{
 string path = Path.Combine (System.Environment.GetFolderPath (System.Environment.SpecialFolder.MyDocuments), "MyDatabase.db");
 var db = new SQLiteConnection(path,password,false);
 DateTime expireDate = DateTime.Now.AddDays(-3));
 var query = db.Table<MyObject>().Where(rt => rt.Date < expireDate );

 if (query != null) {
  foreach (var object in query.ToList<MyObject>()) {
   db.Delete<MyObject>(object.PrimaryKeyId);
  }
 }
 db.Commit ();
}

Saturday, June 15, 2013

Efficient Custom ListView Adapter Selections

A customer requested I create a ListView of their business objects using custom graphics and text based on the status of the object.  I had no problem creating it but they complained that it took too long to refresh when making selections.  After puzzling over the code for awhile trying to determine how to speed it up, I realized that the convertView paramter passed in to the GetView method that I was overriding in my custom listview adapter was actually the existing view of the object, and if it wasn't null I didn't have to recreate it.

So now in my code I check first and I only create the view from scratch if it's null.  Otherwise, I only modify the existing view to change the background color to signify that it has been selected.  I also store the two Drawables that are the backgrounds so they don't have to be retrieved and assigned memory for each object.  This allows the ListView to refresh quickly as it doesn't have to reacquire the underlying objects and rebuild the entire view based on the object's properties each time the selection is changed.  This seems like a simple thing, but maybe someone else that is struggling with ListView performance will happen across my post.

namespace Droid
{
 public class ListViewObjectAdapter : BaseAdapter<MyObject> {
  List<MyObject> items;
  public int selectedListItem = -1;
  Activity context;
  Drawable bgDefault;
  Drawable bgHighlight;

  public ListViewobjectAdapter(Activity context, List<MyObject> items)
   : base()
  {
   this.context = context;
   this.items = items;
   Drawable bgDefault = context.Resources.GetDrawable(Resource.Drawable.gray_button_focused);
   Drawable bgHighlight = context.Resources.GetDrawable(Resource.Drawable.gray_button_default);
  }

  public override long GetItemId(int position)
  {
   return position;
  }

  public void SetItems(List<MyObject> items)
  {
   this.items = items;
   this.NotifyDataSetInvalidated ();
  }

  public override MyObject this[int position]
  {
   get { return items[position]; }
  }

  public override int Count
  {
   get { return items.Count; }
  }

  public override View GetView(int position, View convertView, ViewGroup parent)
  {
   View view = convertView;

   if (view == null)
   {
    string SENT_PREFIX = context.Resources.GetString(Resource.String.sent_object_prefix);
    string UNSENT_PREFIX = context.Resources.GetString(Resource.String.unsent_object_prefix);

    MyObject item = items[position];
    Subobject subobject = Helper.GetSubobjectById (item.SubobjectId);

    view = context.LayoutInflater.Inflate (Resource.Layout.ListViewObjectItem, null);
    view.FindViewById<TextView> (Resource.Id.tvTitle).Text = Helper.GetInfoBySubOject (SubObject);

    if (item.Date.Date.Equals (DateTime.Now.Date))
     view.FindViewById<TextView> (Resource.Id.tvobjectId).Text = item.Date.ToShortTimeString ();
    else
     view.FindViewById<TextView> (Resource.Id.tvobjectId).Text = item.Date.ToShortDateString ();

    view.FindViewById<TextView> (Resource.Id.tvobjectIdText).Text = "";

    if (item.IsValid) {
     view.FindViewById<ImageView> (Resource.Id.Image2).SetImageResource (Resource.Drawable.icon_accept);
     view.FindViewById<TextView> (Resource.Id.tvDescription).Text = "Extra Info: " + Helper.GetAdditionalInfoByObjectId(item.MyObjectId);
    } else {
     view.FindViewById<ImageView> (Resource.Id.Image2).SetImageResource (Resource.Drawable.icon_reject);
     ValidSubobject SubobjectMeasure = Helper.GetValidSubObjectByObjectId (item.MyObjectId);
     view.FindViewById<TextView> (Resource.Id.tvDescription).Text = "MyInfo: " + SubobjectMeasure.Info.ToString () + ";
    }
   }
   RelativeLayout rlListViewItem = (RelativeLayout)view.FindViewById (Resource.Id.rlListViewItem);
   if(position == selectedListItem) {  
    rlListViewItem.SetBackgroundDrawable(bgHighlight);
   } else {
    rlListViewItem.SetBackgroundDrawable(bgDefault );
   }

   return view;
  }
 }
}

Tuesday, May 14, 2013

Defining ContextMenus for ListView Items in Xamarin Android

While working on my Android app I had need to display a list of items with position-dependent context menus.  It took me awhile to develop a clean solution so I'd like to share my work with anyone who would like dynamic menus available on their ListViews that display over items using a long-press.

Inside of my Activity class I have declared three important variables:

private ListView itemList;
private int selectedItemNumber;
private string selectedItemText;

What each variable tracks should be self explanatory from the declaration, but please comment if you have questions on anything in my posts.  The reason it's important to declare these within scope of the Activity class and not within a method is because we lose focus of what ListView item is being interacted with in-between the step of creating a menu and when that menu is used.  There might be a way to extract that information from the second step, but none was obvious to me and I found the approach of using private variables an easy solution.

In addition to those three variables I've also created constant values to track the int-based menu options:

private const int EDIT_ITEM = 0;
private const int VIEW_ITEM = 1;
private const int DELETE_ITEM = 2;

These allow me to refer to actions in a clear way in the code.  In the OnCreate method of my Activity there are two important steps needed to use context menus:

protected override void OnCreate (Bundle bundle)
{
 base.OnCreate (bundle);
 SetContentView (Resource.Layout.SendTickets);

 //Two important steps to for my context menus
 itemList = (ListView) FindViewById(Resource.Id.myListViewName);
 RegisterForContextMenu(itemList);

}

Now that I've associated my ListView with a private variable and have registered it for ContextMenus I only have to define two additional methods for everything to work.  The first is the method that is triggered when an item is selected because of the RegisterForContextMenu call made in OnCreate, and it creates the menu that is displayed:

public override void OnCreateContextMenu(IContextMenu menu, View v, IContextMenuContextMenuInfo info)
{
 AdapterViewAdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo) info;
 
 selectedItemText = ((TextView) menuInfo.TargetView).Text;
 selectedItemId = menuInfo.Id;

 //My listview is set to have a "No Data" item on the first row if it's empty
 if (!selectedTicketText.Equals("No Data"))
 {
  menu.SetHeaderTitle("My Menu Header");
  //All items can be viewed
  menu.Add(0, VIEW_ITEM, 0, "View Item");
  //But only the first item in the list can be edited
  if (selectedItemId== 0)
   menu.Add(0, EDIT_ITEM, 0, "Edit Item");

  if (!selectedTicketText.Contains ("Words that mean it can be deleted"))
   menu.Add (0, DELETE_ITEM, 0, "Delete Item");
 }
 else  //This won't create any menu at all
 {
  menu.SetHeaderTitle("No Options");
 }
}

Okay, so now my menu will show up and it will look different based on what text the item in the ListView contains, and what item number it is assigned, which is the 0-based index in the listview.  You can see in my menu code above that I have added the ability to View to any-and-all items, but only the first item in the list can be edited, and only items containing my special text can be deleted.

The last step is to specify what actions occur when the menu is used.  We do this in the OnContextItemSelected method:

public override bool OnContextItemSelected(IMenuItem item)
{
 if (item.ItemId.Equals (VIEW_ITEM)) 
 {
  MethodThatViewsMyItem(selectedItemText);
 } 
 else if (item.ItemId.Equals(EDIT_ITEM))
 {
  MethodThatEditsMyItem(selectedItemNumber);
 }
 else if (item.ItemId.Equals(DELETE_ITEM))
 {
  MethodThatDeletesMyItem(selectedItemNumber);
 }

 return base.OnOptionsItemSelected(item);
}

This is where the information captured as selectedItemText and selectedItemNumber from OnCreateContextMenu comes in handy.  Since this method only has access to the item that was clicked, and that IMenuItem contains no information about the ListView item we have to refer to our Activity-scope variables selectedItemText and selectedItemNumber.

As always, feel free to post questions or comments, even if it's to suggest a better way of doing this!

Thursday, April 25, 2013

Example of updating a SQLite DB using Xamarin Studio in C#

It took me longer than I'd like to piece this together so I'm sharing a simple example for anyone else looking to make a secure update to a local SQLite database in Android using C#.

static void UpdateDatabase(int primaryKey, string newText, int newValue)
{
 string path = Path.Combine (System.Environment.GetFolderPath (System.Environment.SpecialFolder.MyDocuments), "mydatabase.db");
 var db = new SQLiteConnection(path,false);
 string sql = "UPDATE MyTable SET MyTextColumn = ?, MyValueColumn = ? WHERE MyPrimaryKey= ?";
 string[] parms = new String [] {newText, newValue.ToString(), primaryKey.ToString() };
 var cmd = db.CreateCommand(sql, parms);
 cmd.ExecuteNonQuery();
}

Tuesday, April 23, 2013

Creating Custom Overlays in Xzing Barcode Scanner using Xamarin Studio and C#



I've recently had need to customize the UI overlay of the excellent and free barcode scanning plugin Zxing.   While gathering material for this post I discovered that an example is provided of customizing the overlay via XML, but when I initially searched the first example I came across was the C# method - so that's the route I took.  The C# version did not have the ability to turn the flash on/off which was a feature I needed to access, so I figured out how to add that while styling.  I'm going to go over how I modified the example class created by Redth to help anyone else who might attempt the same thing for their own Android app.

For starters, you need to modify your call to Zxing to specify the use of a custom overlay:

var scanner = new ZXing.MobileMobileBarcodeScanner();
scanner.UseCustomOverlay = true;
myCustomOverlayInstance = new ZxingOverlayView(this, scanner);
scanner.CustomOverlay = myCustomOverlayInstance;
scanner.Scan().ContinueWith(t => { //Handle Result });

After that, you just need to customize the properties and methods of the ZxingOverlayView example class file.  In my example I added the ability to control the torch (flashlight) and display bitamaps so that I could have my own custom buttons.  I specified a default color and a pressed color, along with boolean flags so I could change the buttons to confirm to the user that they had pressed a button while waiting for a response:

namespace MyProject
{
 public class ZxingOverlayView : View 
 {
  private Paint defaultPaint;
  private Paint pressedPaint;
  private Android.Graphics.Bitmap resultBitmap;
  private Android.Graphics.Bitmap litTorchIcon;
                private Android.Graphics.Bitmap unlitTorchIcon;
  private Rect torchIconDimRect;
  private bool hasTorch = false;
  private bool torchOn = false;
  private bool cancelPressed = false;
  private bool problemPressed = false;
...
public ZxingOverlayView(Context context, MobileBarcodeScanner scanner) : base(context)
{
 this.context = context;
 this.scanner = scanner;
 
 SetDisplayValues ();
}
...
private void SetDisplayValues ()
{
 //Determine if device has flash/torch
 hasTorch = this.Context.PackageManager.HasSystemFeature (PackageManager.FeatureCameraFlash);
 if (hasTorch) {
  //Load button icons
  var metrics = Resources.DisplayMetrics;
  int iconHeight = metrics.HeightPixels * 2 / 9;
  int iconWidth = iconHeight * 7 / 10;
  torchIconDimRect = new Rect (0, 0, iconWidth, iconHeight);
  litTorchIcon = ImageHelper.DecodeSampledBitmapFromResource (Resources, Resource.Drawable.bulb_lit, iconWidth, iconHeight);
  unlitTorchIcon = ImageHelper.DecodeSampledBitmapFromResource (Resources, Resource.Drawable.bulb_unlit, iconWidth, iconHeight);
 }
 // Initialize these once for performance rather than calling them every time in onDraw()
 defaultPaint = new Paint (PaintFlags.AntiAlias);
 pressedPaint = new Paint (PaintFlags.AntiAlias);
 pressedPaint.Color = new Color (96, 97, 104);
 buttonFontColor = Color.White;

For each of my buttons I had to specify a rectangle on the screen.  It's best if you specify not in static pixels, but in percentage of the total screen available.  This will ensure your overlay renders the same on any size of screen.  I created three buttons, plus a rectangle to hold my torch icon bitmap.  Notice that the TorchIconDimRect was created as part of SetDisplayValues() above as we decoded and resized the bitmap icon for it at the same time:

Rect GetProblemButtonRect()
{
 
 var metrics = Resources.DisplayMetrics;
 int width = metrics.WidthPixels * 7 / 16 ;
 int height = metrics.HeightPixels * 2 / 9;
 int leftOffset = metrics.WidthPixels * 33 / 64;
 int topOffset = metrics.HeightPixels * 3 / 4;
 var problemRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
 
 return problemRect;
}

Rect GetCancelButtonRect()
{
 var metrics = Resources.DisplayMetrics;
 int width = metrics.WidthPixels * 7 / 16 ;
 int height = metrics.HeightPixels * 2 / 9;
 int leftOffset = metrics.WidthPixels / 22;
 int topOffset = metrics.HeightPixels * 3 / 4;
 var cancelRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
 
 return cancelRect;
}

Rect GetTorchIconDimRect()
{
 return torchIconDimRect;
}

Rect GetTorchIconRect()
{
 var metrics = Resources.DisplayMetrics;
 int height = metrics.HeightPixels / 8;
 int width = height * 7 / 10;
 int leftOffset = metrics.WidthPixels / 2 - ( width / 2);
 int topOffset = metrics.HeightPixels /18;
 var torchRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
 
 return torchRect;
}

Rect GetTorchButtonRect()
{
 var metrics = Resources.DisplayMetrics;
 int width = metrics.WidthPixels * 3 / 8 ;
 int height = metrics.HeightPixels * 3 / 16;
 int leftOffset = metrics.WidthPixels / 2 - (width / 2);
 int topOffset = metrics.HeightPixels / 64;
 var torchRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
 
 return torchRect;
}

Here is the ImageHelper class file which you will only need if you are using bitmaps on your overlay.  This helper class will resize your bitmap to the desired height to prevent image distortion:

internal static class ImageHelper
{
 public static int CalculateInSampleSize(BitmapFactoryOptions options, int reqWidth, int reqHeight)
 {
  // Raw height and width of image
  var height = (float)options.OutHeight;
  var width = (float)options.OutWidth;
  var inSampleSize = 1D;
  
  if (height > reqHeight || width > reqWidth)
  {
   inSampleSize = width > height
    ? height/reqHeight
     : width/reqWidth;
  }
  
  return (int) inSampleSize;
 }
 
 public static Bitmap DecodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight)
 {
  // First decode with inJustDecodeBounds=true to check dimensions
  var options = new BitmapFactoryOptions { InJustDecodeBounds = true };
  BitmapFactory.DecodeResource(res, resId, options); 
  
  // Calculate inSampleSize
  options.InSampleSize = CalculateInSampleSize(options, reqWidth, reqHeight);
  
  // Decode bitmap with inSampleSize set
  options.InJustDecodeBounds = false;
   Bitmap optimalSize = BitmapFactory.DecodeResource (res, resId, options);
   return Bitmap.CreateScaledBitmap (optimalSize, reqWidth, reqHeight, false);
 }
}

So now that all the colors, shapes and bitmaps have been defined you just need to draw them to the screen.  This is done through the OnDraw() method:

protected override void OnDraw (Canvas canvas)
{
 var scale = Resources.DisplayMetrics.Density;
 
 var frame = GetFramingRect();
 if (frame == null)
  return;
 
 var probBtn = GetProblemButtonRect();
 var cancelBtn = GetCancelButtonRect();
 var torchBtn = GetTorchButtonRect();
 
 var width = canvas.Width;
 var height = canvas.Height;
 var textPaint = new TextPaint();
 textPaint.Color = buttonFontColor;
 textPaint.AntiAlias = true;
 textPaint.BgColor = Color.Gray;
 textPaint.TextSize = 16 * scale;
 
 //Draw mask
 defaultPaint.Color = resultBitmap != null ? resultColor : maskColor;
 defaultPaint.Alpha = 245;
 canvas.DrawRect(0, 0, width, frame.Top, defaultPaint);
 canvas.DrawRect(0, frame.Bottom + 1, width, height, defaultPaint);
 
 //Draw button outlines
 defaultPaint.Color = buttonFontColor;
 defaultPaint.Alpha = 255;
 pressedPaint.Color = Color.Black;
 canvas.DrawRect (probBtn.Left+1, probBtn.Top +1, probBtn.Right + 1, probBtn.Bottom + 1, problemPressed ? pressedPaint : defaultPaint);
 canvas.DrawRect (cancelBtn.Left+1, cancelBtn.Top +1, cancelBtn.Right + 1, cancelBtn.Bottom + 1, cancelPressed ? pressedPaint : defaultPaint);
 if (hasTorch)
  canvas.DrawRect (torchBtn.Left + 1, torchBtn.Top + 1, torchBtn.Right + 1, torchBtn.Bottom + 1, torchOn ? pressedPaint : defaultPaint);
 
 //Draw buttons
 defaultPaint.Color = buttonColor;
 defaultPaint.Alpha = 255;
 pressedPaint.Color = new Color(96, 97, 104);
 canvas.DrawRect (probBtn, problemPressed ? pressedPaint : defaultPaint);
 canvas.DrawRect (cancelBtn, cancelPressed ? pressedPaint : defaultPaint);
 if (hasTorch)
 {
  canvas.DrawRect (torchBtn, torchOn ? pressedPaint : defaultPaint);
  //Draw button icons
  canvas.DrawBitmap ((torchOn ? litTorchIcon : unlitTorchIcon), GetTorchIconDimRect(), GetTorchIconRect(), defaultPaint);
 }
 
 //Draw button text
 var btnText = new StaticLayout("Scan problems?", textPaint, probBtn.Width(), Android.Text.Layout.Alignment.AlignCenter, 1.0f, 0.0f, false);
 canvas.Save();
 canvas.Translate(probBtn.Left, probBtn.Top + (probBtn.Height ()/3)+ (btnText.Height / 2));
 btnText.Draw(canvas);
 canvas.Restore();
 btnText = new StaticLayout("Cancel Scan", textPaint, cancelBtn.Width(), Android.Text.Layout.Alignment.AlignCenter, 1.0f, 0.0f, false);
 canvas.Save();
 canvas.Translate(cancelBtn.Left, cancelBtn.Top + (cancelBtn.Height ()/3) + (btnText.Height / 2));
 btnText.Draw(canvas);
 canvas.Restore();

If your buttons are pressed, you can capture that event by overriding the OnTouchEvent method.  Notice that in each button touch event I call this.Invalidate().  This call causes the screen to refresh and without it the OnDraw() method would not be invoked, and the screen would not reflect our buttons change in display when pressed.

public override bool OnTouchEvent(MotionEvent me)
{
 if (me.Action == MotionEventActions.Down)
 {
  if (GetCancelButtonRect().Contains((int)me.RawX, (int)me.RawY))
  {
   cancelPressed = true;
   this.Invalidate();
   OnUnload();
   scanner.Cancel();
  }
  else if (GetTorchButtonRect().Contains ((int)me.RawX, (int)me.RawY))
  {
   scanner.ToggleTorch();
   torchOn = !torchOn;
   this.Invalidate();
  }
  else if (GetProblemButtonRect().Contains ((int)me.RawX, (int)me.RawY))
  {
   problemPressed = true;
   this.Invalidate();
   
   if (parentActivity == null)
   {
    Intent intent = new Intent();
    intent.SetClass(context, typeof(ManualEntryActivity));
    context.StartActivity(intent);
   }
   else
   {
    parentActivity.GetManualEntry();
   }
   OnUnload();
   scanner.Cancel();
  }
  return true;
 }
 else
  return false;
}

Lastly, I read on a stackoverflow post that graphics and bitmaps are two objects which you must take care to properly dispose of when they are no longer needed  If you don't properly dispose of the bitmaps created for your custom overlay then there is a potential for a memory leak.  I dispose of my bitmaps by calling this OnUnload() method that I use at all points of exit:

private void OnUnload()
{
 if (hasTorch)
 {
  litTorchIcon.Dispose();
  unlitTorchIcon.Dispose();
 }
}

This is how my custom overlay turned out.  (Ignore the black & white checkerboard with green square which is just a test display for the android emulator's camera)


Hopefully my post helped you.  Feel free to post questions if you are having problems, or need an explanation on any of my code.

Friday, March 15, 2013

Google Cloud Print Xamarin Component Coming Out soon.


I'm pretty happy.  I was given a license for Xamarin.Mac in order to get this component on the store.

I hope it gets approved here soon.  Looks like I'm going to miss out on the the Xamarin Conference (Evolve).  I had a ticket, but it was employment related, and I took an offer given to me, and I don't have the cash to make it personally.

I'm happy just overall.  That's all that matters.  My code base is swelling.  Even started on some sqllite.net stuff, integrating with a web service in Azure.  Encryption for the shared secret between the server and client.  Authentication required.  It's all moving along.