It is been a while since my last post, but I have a good reason (on the 20th of July - so tired that the first time I've mistaken the date by one month - my son Mattia was born and between work and my duties and no sleep there is not much time left for writing on my blog even if I have a lot of posts that I want to do).
I will get right to the subject (the title is not one of the best chosen). A few days ago I was talking with my friend Alessandro Scardova about the possibility of implementing side menus inside an Windows Phone application (similar to the ones in the Facebook application). Even if not 100%"Modern UI" design it is a good approach for applications that have multiple options that need to be accessed quickly (also the approach can be applied cross-platform). So I took it as a challange and tried to implement it.
My initial thought was that I might be able to implement it using an templated panorama or pivot, but after some tests I was not able to get the desired behaviour:
- when we start the application we will have the selected ViewPort selected
- swiping left or right we can open/close the side menus
- also using the buttons on the upper left and on the right corners we can also close and open the side menus
- the side menus have a width smaller than 480 this way, when opened, we can still see a part of main viewport (including the upper button)
- when opening the menus the ApplicationBar is not visible
The solution I have implemented (doesn't use MVVM pattern) it is more a proof of concept on how to implement the functionality. The approach is pretty simple. We have the whole view that we move inside a canvas using manipulations and animations. Initially I thought that I can use only grid without the canvas and animate the margin of the grid but, as Windows Phone doesn't have ThicknessAnimation, my animations for opening and closing the menus were not very smooth. Also I've tried implementing the swipe behaviour using the Touch.FrameReported event but the results I got were not very good.
So how does my implementation work:
- I have a canvas/grid that has a width of 1320 and the height stretches to the whole available height that contains my whole view
- The view is inserted in a canvas with initial Canvas.Left position set to -420 this way we see the main view port (component)
- The "stable" positions inside the canvas are: 0: left menu opened, -420: main view and -840:right menu opened
- When pressing the buttons we will use a resource Storyboard with a DoubleAnimation to set the the Canvas.Left position inside the canvas to 0,-420 or -840:
<Canvas.Resources>
<Storyboard x:Name="moveAnimation">
<DoubleAnimation Duration="0:0:0.2" To="0" Storyboard.TargetProperty="(Canvas.Left)" Storyboard.TargetName="LayoutRoot" d:IsOptimized="True" />
</Storyboard>
</Canvas.Resources>
Use the animation to open/close the menus:
void MoveViewWindow(double left)
{
_viewMoved = true;
if (left==-420)
ApplicationBar.IsVisible = true;
else
ApplicationBar.IsVisible = false;
((Storyboard)canvas.Resources["moveAnimation"]).SkipToFill();
((DoubleAnimation)((Storyboard)canvas.Resources["moveAnimation"]).Children[0]).To = left;
((Storyboard)canvas.Resources["moveAnimation"]).Begin();
}
- To implement the swipe I use the ManipulationStarted, ManipulationDelta and ManipulationEnded on the canvas container. On delta we set the Canvas.Left value directly (no need for animations) between a maximum of 0 and a minimum of -840.
private void canvas_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
if (e.DeltaManipulation.Translation.X != 0)
Canvas.SetLeft(LayoutRoot, Math.Min(Math.Max(-840, Canvas.GetLeft(LayoutRoot) + e.DeltaManipulation.Translation.X), 0));
}
- when swiping we also memorize the initial Canvas.Left position. If substracting the final Canvas.Left and the initial one the absolute value is lower then 100 (not a long swipe) we bounce back to the initial position. Otherwise we move to the next position.
private void canvas_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
var left = Canvas.GetLeft(LayoutRoot);
if (_viewMoved)
return;
if (Math.Abs(initialPosition - left) < 100)
{
//bouncing back
MoveViewWindow(initialPosition);
return;
}
//change of state
if (initialPosition - left > 0)
{
//slide to the left
if (initialPosition > -420)
MoveViewWindow(-420);
else
MoveViewWindow(-840);
}
else
{
//slide to the right
if (initialPosition< -420)
MoveViewWindow(-420);
else
MoveViewWindow(0);
}
}
- _viewMoved is used to see if the view was already moved by another event since our manipulation started (like a button was pressed)
Here are some screenshots from the sample that you can download and play with:
Hope you will find it useful.
Here is the SOURCE CODE