In this article we demonstrate an easy but powerful technique to implement a fluid adaptive UI for a XAML UWP app. The UI responds to changes in the page size by rearranging its elements. All elements are tilted and float smoothly to their new position. All you need to do, is select the correct main panel type, and call an extension method to hook up the animations.

What is a VariableSizedWrapGrid?

The VariableSizedWrapGrid is a layout panel that arranges it child elements in rows and columns, where each child element can span multiple rows and columns. These rows or columns automatically wrap to a new row or column. The Orientation property specifies the direction in which child elements are arranged and wrapped. The default size of an item or tile is determined by ItemHeight and ItemWidth, but individual items can demand more space through the ColumnSpan and RowSpan attached properties. When the control is resized (e.g. when the page is resized or rotated), the VariableSizedWrapGrid automatically rearranges its children. It seems that the VariableSizedWrapGrid has been mainly used for presenting collections of pictures and news items.

The VariableSizedWrapGrid is not an ItemsControl itself, but it can be used as ItemsPanel in a GridView to present item collections.

Hosting a UI Form in a VariableSizedWrapGrid

Its capabilities to host components of different sizes and to auto-(re)arrange their position make the VariableSizedWrapGrid a nice candidate panel for a responsive/adaptive UI. The control can be used as the main panel on your page. Specify at least values for ItemHeight, ItemWidth and Orientation. Also make sure that it can scroll in the opposite direction of its orientation. If you wrap horizontally then you should place it in a vertical ScrollViewer, like this:

<ScrollViewer VerticalScrollMode="Auto"> <VariableSizedWrapGrid ItemHeight="100" ItemWidth="250" Orientation="Horizontal"> <!-- UI Components here --> </VariableSizedWrapGrid> </ScrollViewer>

You can now group all your input and other controls into appropriate subpanels (such as Grids and StackPanels), assign a value to the containers’ ColumnSpan and RowSpan and drop these into the VariableSizedWrapGrid:

<VariableSizedWrapGrid ItemHeight="100" ItemWidth="250" Orientation="Horizontal" Margin="20 20 0 0"> <Image VariableSizedWrapGrid.ColumnSpan="2" VariableSizedWrapGrid.RowSpan="3" ... Margin="0 0 20 20" /> <TextBlock VariableSizedWrapGrid.ColumnSpan="2" VariableSizedWrapGrid.RowSpan="2" Padding="0 0 20 20" TextWrapping="WrapWholeWords"> ... </TextBlock> <StackPanel VariableSizedWrapGrid.ColumnSpan="1" VariableSizedWrapGrid.RowSpan="2" HorizontalAlignment="Stretch" VerticalAlignment="Top" Padding="0 0 20 20"> <!-- ... --> </StackPanel> <Grid VariableSizedWrapGrid.ColumnSpan="2" VariableSizedWrapGrid.RowSpan="2" HorizontalAlignment="Left" VerticalAlignment="Top" Padding="0 0 20 20"> <!-- ... --> </Grid> <!-- ... --> </VariableSizedWrapGrid>

To distribute horizontal and vertical spacing, I gave the host control a Margin of “20 20 0 0” and each subpanel a Margin or Padding of “0 0 20 20”.

When you resize the page, all subpanels will be automatically rearranged:



Alternatives

If you want more granular control over the layout of a page in different sizes, then you can switch to a design based on a RelativePanel and Adaptive Triggers. Please note that this also involves a lot more work for you.

It’s adaptive, now let’s make it fluid

The grid repositions its children when the page width changes. The transition is abrupt: all children are just smashed into their new location. Let’s smoothen this process and go from ‘Smash’ to ‘Whoosh’. We’ll animate the journey to the new position and add a gentle tilt effect while moving.

The code we’ll be using is derived from the LayoutAnimation sample in the Windows UI Dev Labs repository on GitHub. We’re going to use implicit animations. For a deep dive into this topic, please read Exploring Implicit Animations, by Robert Mikhayelyan.

Implicit Animations start automatically after a trigger has been fired, so they help decouple animation from app logic. It’s the Composition Engine that does all of the work: it discovers when a trigger fires, and executes the animations. App developers semi-declaratively define the animations which they want to execute, and the events that trigger these animations. ‘Semi-declaratively’ in the previous sentence stands for ‘in C# with string-based expressions‘ (note: ‘declaratively’ would stand for ‘in XAML’).

In the documentation, at the bottom of each page dealing with one of the classes related to implicit animations, you see that they are relatively new to the framework:

Device family: Windows 10 Anniversary Edition (introduced v10.0.14393.0)

API contract: Windows.Foundation.UniversalApiContract (introduced v3)

This implies that we need to check the users’ SDK with a call to ApiInformation.IsApiContractPresent before we can use these classes from our code:

// Check if SDK > 14393 if (!ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 3)) { return; }

An ImplicitAnimationCollection can be defined on the following properties on Visual:

AnchorPoint

CenterPoint

Offset

Opacity

Orientation

RotationAngle

RotationAngleInDegrees

RotationAxis

Scale

Size

The Offset property is the one we’re interested in. It corresponds to the relative position of a Visual (every subpanel) in its container (the VariableSizedWrapGrid).

The API and its documentation feel a bit swampy here, but you should

bundle the two animations (rotation and translation) in a CompositionAnimationGroup, then,

assign this to a trigger on Offset via an ImplicitAnimationCollection, which you

assign to each elements’ ImplicitAnimations property.

Here’s the code. It’s written as an extension method of Panel. So it applies not only to VariableSizedWrapView but also to Canvas, Grid and StackPanel:

public static void RegisterImplicitAnimations(this Panel panel) { // Check if SDK > 14393 if (!ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 3)) { return; } var compositor = ElementCompositionPreview.GetElementVisual(panel).Compositor; // Create ImplicitAnimations Collection. var elementImplicitAnimation = compositor.CreateImplicitAnimationCollection(); // Define trigger and animation that should play when the trigger is triggered. elementImplicitAnimation["Offset"] = CreateOffsetAnimation(compositor); foreach (var item in panel.Children) { var elementVisual = ElementCompositionPreview.GetElementVisual(item); elementVisual.ImplicitAnimations = elementImplicitAnimation; } }

Here’s the code for the individual animation for each element. It’s a combination of two CompositionAnimation instances, each with a Duration of 0.4 seconds:

a Vector3KeyFrameAnimation for the translation from the current location to the new Offset (‘this.FinalValue‘ in the InsertExpressionKeyframe), and

a temporary ScalarKeyFrameAnimation for the rotation around its center.

The code is a copy/paste from the LayoutAnimations sample. I didn’t feel the need to change any of the parameters. After all, this was done by designers:

private static CompositionAnimationGroup CreateOffsetAnimation(Compositor compositor) { // Define Offset Animation for the Animation group var offsetAnimation = compositor.CreateVector3KeyFrameAnimation(); offsetAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue"); offsetAnimation.Duration = TimeSpan.FromSeconds(.4); // Define Animation Target for this animation to animate using definition. offsetAnimation.Target = "Offset"; // Define Rotation Animation for Animation Group. var rotationAnimation = compositor.CreateScalarKeyFrameAnimation(); rotationAnimation.InsertKeyFrame(.5f, 0.160f); rotationAnimation.InsertKeyFrame(1f, 0f); rotationAnimation.Duration = TimeSpan.FromSeconds(.4); // Define Animation Target for this animation to animate using definition. rotationAnimation.Target = "RotationAngle"; // Add Animations to Animation group. var animationGroup = compositor.CreateAnimationGroup(); animationGroup.Add(offsetAnimation); animationGroup.Add(rotationAnimation); return animationGroup; }

Thanks to the extension method, we can go from ‘Smash’ to ‘Whoosh’ with just one line of code:

// Yep: that's all. VariableSizedWrapGrid.RegisterImplicitAnimations();

Here are some action shots from the animation:



Source Code

The code lives here on GitHub.

Enjoy!