public class CustomTransition : IPageTransition
/// Initializes a new instance of the <see cref="CustomTransition"/> class.
public CustomTransition()
/// Initializes a new instance of the <see cref="CustomTransition"/> class.
/// <param name="duration">The duration of the animation.</param>
public CustomTransition(TimeSpan duration)
/// Gets the duration of the animation.
public TimeSpan Duration { get; set; }
public async Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken)
if (cancellationToken.IsCancellationRequested)
var tasks = new List<Task>();
var parent = GetVisualParent(from, to);
var scaleYProperty = ScaleTransform.ScaleYProperty;
var animation = new Animation
FillMode = FillMode.Forward,
Setters = { new Setter { Property = scaleYProperty, Value = 1d } },
Property = scaleYProperty,
tasks.Add(animation.RunAsync(from, null, cancellationToken));
var animation = new Animation
FillMode = FillMode.Forward,
Property = scaleYProperty,
Setters = { new Setter { Property = scaleYProperty, Value = 1d } },
tasks.Add(animation.RunAsync(to, null, cancellationToken));
await Task.WhenAll(tasks);
if (from != null && !cancellationToken.IsCancellationRequested)
/// Gets the common visual parent of the two control.
/// <param name="from">The from control.</param>
/// <param name="to">The to control.</param>
/// <returns>The common parent.</returns>
/// <exception cref="ArgumentException">
/// The two controls do not share a common parent.
/// Any one of the parameters may be null, but not both.
private static IVisual GetVisualParent(IVisual? from, IVisual? to)
var p1 = (from ?? to)!.VisualParent;
var p2 = (to ?? from)!.VisualParent;
if (p1 != null && p2 != null && p1 != p2)
throw new ArgumentException("Controls for PageSlide must have same parent.");
return p1 ?? throw new InvalidOperationException("Cannot determine visual parent.");