Creating a FluidBanner control using Windows Composition

Introduction

A few days ago, I came across the following tweet which showed a banner of images with a complex set of animations during user interactions. It was created for the web using CSS.

I wondered if the same could be achieved in Windows UWP applications using Composition APIs. After tinkering for a few days, I came up with the FluidBanner control.

fb3.gif

 

In this post I will be describing in detail about the FluidBanner control.

FluidBanner Dependency Properties

FluidBanner has the following dependency properties which allow the user to customize it.

Dependency Property Type Description Default Value
DecodeHeight int The height, in pixels, that the images are decoded to. (Optional) 0
DecodeWidth int The width, in pixels, that the images are decoded to. (Optional) 0
ItemBackground Color The background color of each item in the FluidBanner Black
ItemGap double The gap between adjacent items in the banner. 30
ItemsSource IEnumerable<Uri> The collection of Uris of images to be shown in the FluidBanner null
Padding Thickness The padding inside the FluidBanner Thickness(0)

Demystifying the FluidBanner Animations

For each of the animations in the FluidBanner, a key role is played by the the Clip property (which is an instance of the InsetClip class) of the visual. According to MSDN, the InsetClip clips a portion of a visual. The visible portion of the visual is a rectangle defined as inset values from the edges of the visual. The portion of the visual outside the rectangle is clipped.

InsetClip.PNG

 

Most of the animations within the FluidBanner is accomplished by animating the LeftInset and RightInset properties of the InsetClip class.

Digging deeper into the FluidBanner

FluidBanner Visual States

Based on FluidBanner’s behavior during user interaction, its visual states can be categorized into

    • Unselected state – In this state, which is also the initial state, none of the images are selected by the user and each image is represented by a BannerItem which is a vertical slice of the image. (I will provide more details about BannerItem later in this blog.) Adjacent BannerItems are separated by a predefined set of pixels (determined by the ItemGap property).

FluidBanner_Layout

  • Selected state – In this state, the user has selected an image by clicking or tapping on it and the selected BannerItem animates and expands to fill the total area of the FluidBanner. The rest of the BannerItems scale back and fade out.

FluidBanner_Layout_Selected

FluidBanner Layout

FluidBanner derives from the Panel class and it means that the responsibility to arrange and render its children lies solely on the FluidBanner. Before describing how FluidBanner renders its children, it is important to understand how the Layout Pass works.

Layout Pass

According to MSDN, the Layout Pass describes the process of measuring and arranging the members of a Panel-derived object’s collection of children, and then drawing them onscreen. The layout pass is a mathematically-intensive process—the larger the number of children in the collection, the greater the number of calculations required. At its simplest, layout is a recursive system that leads to an element being sized, positioned, and drawn onscreen.

Sounds complicated? Well, let me explain it in simpler terms. The Layout Pass can be thought of like a communication between the layout container and its children. This communication takes place in two stages which are often referred to as passes.

In the first pass, the parent informs each child about the size available for rendering (based on constraints such as Margin, Padding, Alignment etc) and asks how much space the child needs to render itself. Before a child informs the parent, how much space it needs, the child asks all of its children how much space they need. (This scenario is valid in case of nested panels). This goes on recursively until all the children have been queried. Once each child calculates the space required, its DesiredSize property is set.

At the end of the first pass, once all the children respond, the parent can determine its required size by aggregating the DesiredSize properties of its children according to its own layout rules. This pass is called the Measure pass.

In the second pass, the parent informs each child the final size that has been allocated to it and the child must render itself in that size. The child, in turn, calculates the final size for each of its child (based on its own layout rules) and informs them accordingly. This happens recursively until all the children have been notified. This pass is called the Arrange pass.

LayoutPass2

 

The Measure pass is characterized by the Measure() call made by the parent which invokes the MeasureOverride() method on its children. The Arrange pass is characterized by the Arrange() method call made by the parent which then invokes the ArrangeOverride() method on its children.

protected override Size MeasureOverride(Size availableSize);
protected override Size ArrangeOverride(Size finalSize);

The actual arrangement of children happens during the Arrange pass. It is important to understand that the availableSize in the MeasureOverride() method might not be same as the finalSize in the ArrangeOverride() method.

Layout Pass in FluidBanner

Measure Pass

In the Measure pass, since the panel will accommodate all sizes of images, there is no need to explicitly calculate the size of the its children. So the FluidBanner is happy with whatever size is available.

However, one crucial thing to note is that if you are implementing a custom control and handling the Layout passes yourself, then MeasureOverride method is the best place to initialize your Composition related objects (Compositor, Visuals, Animations etc) which you may be using throughout your application. (Make sure that you initialize them only once. You can accomplish this by checking whether the Compositor reference is already initialized.). Layout Pass happens before the Loaded event is raised.

Visual _rootVisual;
Compositor _compositor;

protected override Size MeasureOverride(Size availableSize)
{
    if (_compositor != null)
    {
        // Initialize Composition related elements
        InitializeComposition();
    }

    ...
}

public void InitializeComposition()
{
    _rootVisual = ElementCompositionPreview.GetElementVisual(this);
    _compositor = _rootVisual.Compositor;
    
    ...
}
Arrange Pass

As previously mentioned, Arrange pass is responsible for the arrangement of children within the parent. The following factors influence the arrangement of the BannerItemsPadding, the finalSize provided and the number of images to be displayed. Based on these factors, the size and offset of each BannerItem is calculated and each BannerItem created and added to the visual tree.

BannerItem

Ok, now lets talk about BannerItem in detail. BannerItem represents a single image displayed in the FluidBanner (whether in Selected or Unselected state).

BannerItem comprises of a three visuals –

  • contentVisual – It is a SpriteVisual which hosts the image loaded from the Uri.
  • bgVisual – It is a SpriteVisual filled with the color specified by the ItemBackground property (default Black). It serves as a background for the image if the image does not stretch to occupy the entire BannerItem area.
  • container – It is a ContainerVisual which hosts the contentVisual and the bgVisual. The Clip property of the container is set to an InsetClip whose LeftInset, RightInset, TopInset & BottomInset properties determine the size of the image slice displayed in the BannerItem. The BannerItem size is expanded and collapsed by animating these properties.

BannerItem

Layers and Animations

At the root of the FluidBanner’s visual tree is a ContainerVisual called _rootContainer. In order to accommodate its two visual states, the _rootContainer hosts two LayerVisuals_bgLayer and _topLayer. As the name suggests, _topLayer is rendered above the _bgLayer.

In the Unselected state, the _bgLayer hosts all the BannerItems. Clicking on any of the BannerItems will cause the FluidBanner to transition to the Selected state. During this transition, the selected BannerItem is moved to the _topLayer and its Clip animated to move to the left most position in the FluidBanner. After that the Clip is expanded to occupy the full surface area of the FluidBanner. Meanwhile the remaining BannerItems in the _bgLayer scale back and fade out.

Clicking or tapping on the expanded selected BannerItem will cause the FluidBanner to transition to the Unselected state. During this transition, the Clip of the selected BannerItem collapses to occupy the left most position in the FluidBanner and then it moves back to its original location in the _bgLayer. Meanwhile the remaining BannerItems in the _bgLayer fade in and scale up to normal size.

FB_LayoutAnim

Pointer Interactions

Pointer interactions are handled by subscribing to the PointerMoved and PointerPressed events in the FluidBanner. Since the visuals themselves do not support pointer interactions, the location of each of the BannerItems is kept track of and based on the Pointer event raised, the corresponding BannerItem under the Pointer is obtained and the appropriate animations initiated.

Source Code

The source code for FluidBanner is available in GitHub.

Advertisements

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