A splash screen animation for UWP

What’s a splash screen if it doesn’t splash?

This article shows how to zoom the splash screen of a UWP app to the size of its window with an animation that doesn’t use the UI thread. This is how it looks like in a sample app:

AnimatedSplashScreen

The splash screen grows and gets transparent while the app is navigating to its main page. I did not wrote the feature myself, the beef of the code can be found in the Windows UI Dev Labs. I just refactored the code, made it more generic, and packaged it for easy reuse. I was able to implement it as an extension method of the Page class.

All you need to do to add the animation to your app is adding one line of code in the Application.OnLaunched method (in app.xaml.cs):

if (rootFrame.Content == null)
{
    rootFrame.Navigate(typeof(Shell), e.Arguments);
    (rootFrame.Content as Page).OpenFromSplashScreen(
	e.SplashScreen.ImageLocation);
}

// Ensure the current window is active
Window.Current.Activate();

While the app navigates to its first page, a copy of the splash screen (or another image if you want) is placed on the position of the original image. The LaunchActivatedEventArgs instance from OnLaunched has a reference to the SplashScreen instance, which exposes its ImageLocation. That rectangle is passed to the OpenFromSplashScreen call, optionally together with the path to the image file, and a background color (in case you have a transparent image).

Here’s an alternative call, we provide the path to the (transparent) splash screen of the UWP Community Toolkit, and specify a black background:

(rootFrame.Content as Page).OpenFromSplashScreen(
	e.SplashScreen.ImageLocation, 
	Colors.Black, 
	new Uri("ms-appx:///Assets/ToolkitLogoTransparent.png"));

The OpenFromSplashScreen method first registers a handler to the Loaded event, to start the animation. Then the SurfaceLoader helper instance is initialized, and finally we display a copy of the splash screen image that will be animated later:

public static void OpenFromSplashScreen(
	this Page page, 
	Rect imageBounds, 
	Color backgroundColor, 
	Uri imageUri)
{
    page.Loaded += Page_Loaded;

    // Initialize the surface loader
    SurfaceLoader.Initialize(
	ElementCompositionPreview.GetElementVisual(page).Compositor);

    // Show the custom splash screen
    ShowImage(page, imageBounds, imageUri, backgroundColor);
}

The copy of the splash screen image is entirely drawn through the Composition API. Via ElementCompositionPreview.GetElementVisual we get access to the page’s Visual and its Compositor. This compositor factory is used to create a ContainerVisual that will have the background color (a SpriteVisual painted with a solid CompositionColorBrush) and the image (in a CompositionDrawingSurface) as its Children:

private static async void ShowImage(Page page, Rect imageBounds, Uri imageUri, Color backgroundColor)
{
    var compositor = ElementCompositionPreview.GetElementVisual(page).Compositor;
    var windowSize = new Vector2((float)Window.Current.Bounds.Width, (float)Window.Current.Bounds.Height);

    //
    // Create a container visual to hold the color fill background and image visuals.
    // Configure this visual to scale from the center.
    //
    var container = compositor.CreateContainerVisual();
    container.Size = windowSize;
    container.CenterPoint = new Vector3(windowSize.X, windowSize.Y, 0) * .5f;
    ElementCompositionPreview.SetElementChildVisual(page, container);

    //
    // Create the colorfill sprite for the background, set the color to the same as app theme
    //
    var backgroundSprite = compositor.CreateSpriteVisual();
    backgroundSprite.Size = windowSize;
    backgroundSprite.Brush = compositor.CreateColorBrush(backgroundColor);
    container.Children.InsertAtBottom(backgroundSprite);

    //
    // Create the image sprite containing the splash screen image.  Size and position this to
    // exactly cover the Splash screen image so it will be a seamless transition between the two
    //
    var surface = await SurfaceLoader.LoadFromUri(imageUri);
    var imageSprite = compositor.CreateSpriteVisual();
    imageSprite.Brush = compositor.CreateSurfaceBrush(surface);
    imageSprite.Offset = new Vector3((float)imageBounds.X, (float)imageBounds.Y, 0f);
    imageSprite.Size = new Vector2((float)imageBounds.Width, (float)imageBounds.Height);
    container.Children.InsertAtTop(imageSprite);
}

When the main page is loaded (in the sample app, it’s the shell that hosts the main SplitView), the animation is started:

private static void Page_Loaded(object sender, RoutedEventArgs e)
{
    (sender as Page).Loaded -= Page_Loaded;

    // Now that loading is complete, dismiss the custom splash screen
    ShowContent(sender as Page);
}

The animation contains a ScalarKeyFrameAnimation for the opacity, and a Vector2KeyFrameAnimation for the image’s size. Both animations are started through a StartAnimation call, right after the declaration of a ScopedBatch instance on the same compositor:

private static void ShowContent(Page page)
{
    var container = (ContainerVisual)ElementCompositionPreview.GetElementChildVisual(page);
    var compositor = container.Compositor;

    // Setup some constants for scaling and animating
    const float scaleFactor = 7.5f;
    var duration = TimeSpan.FromMilliseconds(2000);

    // Create the fade animation which will target the opacity of the outgoing splash screen
    var fadeOutAnimation = compositor.CreateScalarKeyFrameAnimation();
    fadeOutAnimation.InsertKeyFrame(1, 0);
    fadeOutAnimation.Duration = duration;

    // Create the scale up animation for the Splash screen visuals
    var scaleUpSplashAnimation = compositor.CreateVector2KeyFrameAnimation();
    scaleUpSplashAnimation.InsertKeyFrame(0, new Vector2(1, 1));
    scaleUpSplashAnimation.InsertKeyFrame(1, new Vector2(scaleFactor, scaleFactor));
    scaleUpSplashAnimation.Duration = duration;

    // Configure the visual to scale from the center
    var frameworkElement = page.Content as FrameworkElement;
    var visual = ElementCompositionPreview.GetElementVisual(frameworkElement);
    visual.Size = new Vector2((float)frameworkElement.ActualWidth, (float)frameworkElement.ActualHeight);
    visual.CenterPoint = new Vector3(visual.Size.X, visual.Size.Y, 0) * .5f;

    //
    // Create a scoped batch for the animations.  When the batch completes, we can dispose of the
    // splash screen visuals which will no longer be visible.
    //
    var batch = compositor.CreateScopedBatch(CompositionBatchTypes.Animation);

    container.StartAnimation("Opacity", fadeOutAnimation);
    container.StartAnimation("Scale.XY", scaleUpSplashAnimation);

    currentPage = page; // TODO: find a better way to pass the page to the event.
    batch.Completed += Batch_Completed;
    batch.End();
}

This enables us to clean up all Composition API instances in the batch’s Completed event:

private static void Batch_Completed(
	object sender, 
	CompositionBatchCompletedEventArgs args)
{
    // Now that the animations are complete, dispose of the custom Splash Screen visuals
    ElementCompositionPreview.SetElementChildVisual(currentPage, null);
}

What about the phone?

The animated splash screen is a UWP library, so it also works on your Windows phone. But on such a small-screen device the splash screen is relatively big (its width is the same as the whole screen width). So the animation is less noticeable than on a PC. For smaller screens it makes sense to let the whole content zoom in, instead of just the splash screen. The code for that is in the code base, but commented out.

Where’s the code?

The library and the sample app are hosted on GitHub. if you want to add the effect right away to your app then just install its NuGet package.

Enjoy!

Advertisements

2 thoughts on “A splash screen animation for UWP

  1. Ratish Philip

    Hi,
    Nice and useful article.
    However, in case of phone you need to consider the scale factor to set the image in the right size.

    var ScaleFactor = (float)DisplayInformation.GetForCurrentView().RawPixelsPerViewPixel;

    and use this scale Factor in the ShowImage method like this

    imageSprite.Size = new Vector2((float)imageBounds.Width / ScaleFactor, (float)imageBounds.Height / ScaleFactor);

    Liked by 1 person

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s