I'm a bit late to give a more complete answer, but I got inspired and started working on a reusable control/page transition. The current prototype is working quite nicely with RenderTransforms
and Clip
Transforms
for nice independent animation support.
I'll work on that reusable control for WinRT XAML Toolkit, but meanwhile you can check this code:
XAML
<Page
x:Class="FlipControls.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FlipControls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid
x:Name="ManipulationGrid"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
ManipulationMode="TranslateX,TranslateY,TranslateInertia"
ManipulationStarted="ManipulationGrid_OnManipulationStarted"
ManipulationDelta="ManipulationGrid_OnManipulationDelta"
ManipulationCompleted="ManipulationGrid_OnManipulationCompleted">
<Grid
x:Name="Page1">
<Grid.Clip>
<RectangleGeometry
Rect="0,0,80000,80000">
<RectangleGeometry.Transform>
<TransformGroup>
<TranslateTransform
x:Name="Page1ClipTranslateTransform" />
<RotateTransform
x:Name="Page1ClipRotateTransform" />
</TransformGroup>
</RectangleGeometry.Transform>
</RectangleGeometry>
</Grid.Clip>
<Grid
x:Name="Page1ContentGrid">
<Image
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Stretch="UniformToFill"
Source="http://bigbackground.com/wp-content/uploads/2013/07/tropical-beach-screensaver.jpg" />
</Grid>
</Grid>
<Grid
x:Name="Page2"
Opacity="0">
<Grid.Clip>
<RectangleGeometry
Rect="0,0,80000,80000">
<RectangleGeometry.Transform>
<TransformGroup>
<TranslateTransform
x:Name="Page2ClipTranslateTransform" />
<RotateTransform
x:Name="Page2ClipRotateTransform" />
</TransformGroup>
</RectangleGeometry.Transform>
</RectangleGeometry>
</Grid.Clip>
<Grid
x:Name="Page2ContentGrid">
<Image
x:Name="Page2SampleContentImage"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Stretch="UniformToFill"
Source="http://www.photography-match.com/views/images/gallery/Tropical_Lagoon.jpg" />
</Grid>
</Grid>
<Grid
x:Name="TransitionGridContainer">
<Grid
x:Name="TransitionGrid"
Opacity="0">
<Grid.RenderTransform>
<CompositeTransform
x:Name="TransitionGridContainerTransform" />
</Grid.RenderTransform>
<Grid.Clip>
<RectangleGeometry
Rect="0,0,80000,80000">
<RectangleGeometry.Transform>
<TransformGroup>
<TranslateTransform
x:Name="TransitionGridClipTranslateTransform" />
<RotateTransform
x:Name="TransitionGridClipRotateTransform" />
</TransformGroup>
</RectangleGeometry.Transform>
</RectangleGeometry>
</Grid.Clip>
<Image
x:Name="TransitionImage"
Stretch="None" />
</Grid>
</Grid>
</Grid>
</Page>
C#
using System;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media.Animation;
using Windows.UI.Xaml.Media.Imaging;
namespace FlipControls
{
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
PreloadTransitionGridContentAsync();
}
private async Task PreloadTransitionGridContentAsync()
{
#region Waiting for page 2 content to load
var bi = Page2SampleContentImage.Source as BitmapImage;
if (bi.PixelWidth == 0)
{
bi.ImageFailed += (s, e) => new MessageDialog("Need a different sample image.").ShowAsync();
bi.ImageOpened += (s, e) => PreloadTransitionGridContentAsync();
return;
}
if (Page2ContentGrid.ActualWidth == 0)
{
SizeChangedEventHandler sizeChangedEventHandler = null;
sizeChangedEventHandler = (s, e) =>
{
PreloadTransitionGridContentAsync();
Page2ContentGrid.SizeChanged -= sizeChangedEventHandler;
};
Page2ContentGrid.SizeChanged += sizeChangedEventHandler;
return;
}
#endregion
var rtb = new RenderTargetBitmap();
await rtb.RenderAsync(Page2ContentGrid);
TransitionImage.Source = rtb;
await Task.Delay(40000);
}
private bool isCancellationRequested;
private enum FlipDirections
{
Left,
Right
}
private FlipDirections flipDirection;
private Point manipulationStartPosition;
private double rotationCenterX;
private double rotationCenterY;
private void ManipulationGrid_OnManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e)
{
if (TransitionImage.Source == null)
{
CancelManipulation(e);
return;
}
manipulationStartPosition = e.Position;
if (Page1.Opacity == 1)
{
flipDirection = FlipDirections.Left;
Page2ClipTranslateTransform.X = ManipulationGrid.ActualWidth;
Page2.Opacity = 1;
TransitionGridClipTranslateTransform.X = -80000;
TransitionGridContainerTransform.TranslateX = ManipulationGrid.ActualWidth;
TransitionGrid.Opacity = .975;
}
else
{
if (manipulationStartPosition.X >= this.ManipulationGrid.ActualWidth / 2)
{
// Can't flip left since there is no page after the current one
CancelManipulation(e);
return;
}
flipDirection = FlipDirections.Right;
Page1.Opacity = 1;
}
}
private void ManipulationGrid_OnManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
if (this.isCancellationRequested)
{
return;
}
if (flipDirection == FlipDirections.Left)
{
var w = this.ManipulationGrid.ActualWidth;
var h = this.ManipulationGrid.ActualHeight;
var cx = Math.Min(0, Math.Max(e.Position.X - w, -w));
var cy = e.Cumulative.Translation.Y;
var angle = (Math.Atan2(cx + manipulationStartPosition.Y - w, -cy) * 180 / Math.PI + +90) % 360;
this.rotationCenterX = w + cx / 2;
if (cy < 0)
{
this.rotationCenterY = h;
}
else
{
this.rotationCenterY = 0;
}
Page2ClipTranslateTransform.X = w + cx / 2;
Page2ClipTranslateTransform.Y = -40000 + h / 2;
Page2ClipRotateTransform.CenterX = this.rotationCenterX;
Page2ClipRotateTransform.CenterY = this.rotationCenterY;
Page2ClipRotateTransform.Angle = angle;
TransitionGridClipTranslateTransform.X = -80000 - (cx / 2);
TransitionGridClipTranslateTransform.Y = -40000 + h / 2;
TransitionGridClipRotateTransform.CenterX = -cx / 2;
TransitionGridClipRotateTransform.CenterY = this.rotationCenterY;
TransitionGridClipRotateTransform.Angle = -angle;
TransitionGridContainerTransform.TranslateX = w + cx;
TransitionGridContainerTransform.CenterX = -cx / 2;
TransitionGridContainerTransform.CenterY = this.rotationCenterY;
TransitionGridContainerTransform.Rotation = 2 * angle;
}
}
private void ManipulationGrid_OnManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
{
if (this.isCancellationRequested)
{
this.isCancellationRequested = false;
return;
}
var w = this.ManipulationGrid.ActualWidth;
var h = this.ManipulationGrid.ActualHeight;
var sb = new Storyboard();
AddAnimation(sb, Page2ClipTranslateTransform, "X", w / 2);
AddAnimation(sb, Page2ClipRotateTransform, "CenterX", w / 2);
AddAnimation(sb, Page2ClipRotateTransform, "Angle", 0);
AddAnimation(sb, TransitionGridClipTranslateTransform, "X", -80000 + (w / 2));
AddAnimation(sb, TransitionGridClipRotateTransform, "CenterX", w / 2);
AddAnimation(sb, TransitionGridClipRotateTransform, "Angle", 0);
AddAnimation(sb, TransitionGridContainerTransform, "TranslateX", 0);
AddAnimation(sb, TransitionGridContainerTransform, "CenterX", w / 2);
AddAnimation(sb, TransitionGridContainerTransform, "Rotation", 0);
sb.Begin();
}
private static void AddAnimation(Storyboard sb, DependencyObject dob, string path, double to)
{
var da = new DoubleAnimation();
Storyboard.SetTarget(da, dob);
Storyboard.SetTargetProperty(da, path);
da.To = to;
da.Duration = TimeSpan.FromSeconds(.2);
sb.Children.Add(da);
}
private void CancelManipulation(ManipulationStartedRoutedEventArgs e)
{
this.isCancellationRequested = true;
e.Complete();
}
}
}