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!

No comments:

Post a Comment