Month: October 2015

A Fun Little Sliding Overlay in Xamarin.Forms using AbsoluteLayout

my iphone is a music stand

Last year I took a few Skype lessons with someone who’s fairly well known in the guitar community: Rusty Cooley. He is incredibly skilled in the art of shredding, and he put me on a path that has enabled me to drastically improve my own skills through daily practice. This practice regimen is really simple, you don’t even need a metronome, just a timer.

My iPhone timer works okay for this, but I wanted to build something a little more purpose-oriented that doesn’t involve leaving the guitar to touch the phone and then reset to playing position.

Practice UI
This actually appears to be the A Ionian scale. It’s test data!

The design I came up with is pretty rough – I’m gonna make it prettier – but that’s because it took me forever to implement just the basic page structure. You’ve got some music, and a timer, and some instructions for how to do the exercise. The exercise instructions slide up over the music, and this was the UI component I was struggling to implement. In fact, it took me so long that I actually purchased (and allowed to lapse) an Apple Developer subscription during the course of the year I’ve been working on it. Finally, progress!

Practice UI with instructions expanded
Practice UI with instructions expanded

The exercise instructions animate up and down, and when the instructions are in the ‘collapsed’ position, it is docked to the top bar of the instructions.

So how did I accomplish this little sliding overlay? Let’s take a look.

Laying out a sliding overlay using xamarin.forms

I like to use XAML to lay out UI, personally. I like marking up layout stuff declaratively and then adding codebehind. I have a ton of web dev experience that makes this a natural paradigm for me, but you could certainly accomplish this in code as well. I do use the codebehind to set some initial positions and do some necessary calculations.

The page is loaded in a X.F NavigationPage. The ContentPage itself has one child – an AbsoluteLayout with no other attributes than a name (MainLayout) and a background color. The purpose of this absolute layout is so that its child elements can be anchored to some position within this parent AbsoluteLayout.

The MainLayout has two children:

  • A StackLayout that contains all of the controls at the top of the UI – timer, start, stop, title, Next Exercise button (all in a Grid), in addition to the music image, which is in a ScrollView. This layout contains anything you want to stay fixed. This StackLayout has its AbsoluteLayout.LayoutFlags set to All and its AbsoluteLayout.LayoutBounds set to fill the view with a value of "1,1,1,1"
  • Another AbsoluteLayout named aInstructions, which is responsible for holding the sliding panel of instructions. Its AbsoluteLayout.LayoutFlags (remember, this is in reference to the parent AbsoluteLayout, MainLayout) are set to All, and its initial LayoutBounds of "1,1,1,0.7" result in a partially expanded instructions panel. We overwrite this in code anyway.

So, the parent AbsoluteLayout and its two children, the StackLayout and AbsoluteLayout for instructions, are the three main components that you need to make this work. You can put anything you want in the StackLayout and anything you want in the child AbsoluteLayout and you will get similar behavior.

The code in the OnAppearing method for this page figures out how big the title bar of the instructions panel is and then translates the entire layout to the position where the title bar is docked to the bottom.

protected override void OnAppearing ()
{
	PopulateViewModel (exercises[currentIndex]);
			
	var actualHeight = instructionsTitleLayout.Height;
	drawerExpandedPosition = aInstructions.Bounds;
	drawerCollapsedPosition = aInstructions.Bounds;

	drawerCollapsedPosition.Y = aInstructions.Height - actualHeight;
	drawerExpandedPosition.Y = 0;

	aInstructions.TranslateTo (drawerCollapsedPosition.X, drawerCollapsedPosition.Y, 200, Easing.CubicOut);

	base.OnAppearing ();
}

The line aInstructions.TranslateTo(...) is what performs the actual animation, and I just move it back and forth between the collapsed and expanded positions of the drawer.

Here’s the basic skeleton code for the layout itself, in case it’s helpful to you. This is the code inside my ContentPage.Content tag. I’ve removed most of the contained controls and attribute specifics in order to make the actual page structure more clear.

<AbsoluteLayout BackgroundColor="White" x:Name="MainLayout">
	<StackLayout Orientation="Vertical" BackgroundColor="White" AbsoluteLayout.LayoutBounds="1,1,1,1" AbsoluteLayout.LayoutFlags="All">
		<Grid Padding="10,0,10,0">
			<!-- Buttons and exercise title here -->
		</Grid>

		<ScrollView HorizontalOptions="StartAndExpand" VerticalOptions="StartAndExpand">
			<Image x:Name="imgTab">
				<!-- Music image goes here -->
			</Image>
		</ScrollView>
	</StackLayout>

	<AbsoluteLayout AbsoluteLayout.LayoutBounds="1,1,1,0.7" AbsoluteLayout.LayoutFlags="All" BackgroundColor="#CC222222" x:Name="aInstructions">
		<StackLayout Orientation="Vertical" AbsoluteLayout.LayoutBounds="1,1,1,1" AbsoluteLayout.LayoutFlags="All">
			<StackLayout Padding="0,0,20,0" x:Name="instructionsTitleLayout" Orientation="Horizontal" AbsoluteLayout.LayoutFlags="WidthProportional,PositionProportional" AbsoluteLayout.LayoutBounds="0,0,1,AutoSize" BackgroundColor="#CC111111">
				<Button x:Name="btnDoStuff" TextColor="White" Text="Show Exercise Instructions" StackLayout.HorizontalOptions="EndAndExpand"/>
			</StackLayout>
			<ScrollView HorizontalOptions="Fill" VerticalOptions="FillAndExpand" Padding="10">
				<Label TextColor="#CCC" x:Name="lblInstructions"></Label>
			</ScrollView>
		</StackLayout>
	</AbsoluteLayout>			
</AbsoluteLayout>

I like this sliding mechanic quite a bit. Understanding AbsoluteLayout has been a fun journey. Also notice in the declaration for instructionsTitleLayout, we have set the AbsoluteLayout.LayoutFlags to both WidthProportional and PositionProportional to ensure that I can properly align the button within that view. Without WidthProportional, the title bar would not scale all the way across the screen.

Advertisements