The goal of the XamlFlair library is to ease the implementation of common animations and allow a developer to easily add a single or combined set of animations with just a few lines of Xaml.
The basic concept of XamlFlair is based on animations that are categorized as From and To. Any UI element that consists of a From animation will start with one or more arbitrary values, and complete using the default value of the corresponding property. Any UI element that consists of a To animation will start in its current state and animate to one or more arbitrary values.
Example of a From animation (a UI element translating to the default value of a Translation (0)):
Example of a To animation (a UI element sliding away from its current state):
Platform | Package | NuGet |
---|---|---|
UWP | XamlFlair.UWP | |
WPF | XamlFlair.WPF |
To install XamlFlair, run the following command in the Package Manager Console:
UWP:
Install-Package XamlFlair.UWP
Your app must target a minimum of Windows 10 version 1803 (build 17134)
WPF:
Install-Package XamlFlair.WPF
Requires .Net Framework 4.7.2
To begin, you need to have the following Xaml namespace reference:
UWP:
xmlns:xf="using:XamlFlair"
WPF:
xmlns:xf="clr-namespace:XamlFlair;assembly=XamlFlair.WPF"
From here on, it's a simple matter of setting an attached property to any FrameworkElement
that needs an animation:
<Border xf:Animations.Primary="{StaticResource FadeIn}" />
Note: If your
FrameworkElement
defines aCompositeTransform
in your Xaml, it will be altered during the animation process.
Note: The use of
StaticResource
is to reference global common animations, which is discussed in the next section.
Warning: Be careful when animating
FadeTo
since the element remains in the Visual Tree if theVisibility
isVisible
. There may be cases where you'll need to manually manageIsHitTestVisible
to allow the user to tap through the element.
The following lists some notable default values when working with XamlFlair:
- Kind: FadeTo
- Duration: 500
- Easing: Cubic
- Easing Mode: EaseOut
- TransformCenterPoint: (0.5, 0.5)
- Event: Loaded
- Saturation: 0.5 (UWP-only)
- Tint: Transparent (UWP-only)
All common animations should be placed in a global ResourceDictionary
(ex: Animations.xaml
) and used where needed throughout the app. The goal is to consolidate all the animations into one file with meaningful names so that any developer can understand exactly what animation is applied to a FrameworkElement
. Here's a small example of what it looks like:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:xf="using:XamlFlair">
<x:Double x:Key="PositiveOffset">50</x:Double>
<x:Double x:Key="NegativeOffset">-50</x:Double>
<x:Double x:Key="SmallScaleFactor">0.75</x:Double>
<x:Double x:Key="LargeScaleFactor">1.25</x:Double>
<xf:AnimationSettings x:Key="FadeIn"
Kind="FadeFrom"
Opacity="0" />
<xf:AnimationSettings x:Key="FadeOut"
Kind="FadeTo"
Opacity="0" />
<!-- Scale to a larger value -->
<xf:AnimationSettings x:Key="Expand"
Kind="ScaleXTo,ScaleYTo"
ScaleX="{StaticResource LargeScaleFactor}"
ScaleY="{StaticResource LargeScaleFactor}" />
<!-- Scale from a larger value -->
<xf:AnimationSettings x:Key="Contract"
Kind="ScaleXFrom,ScaleYFrom"
ScaleX="{StaticResource LargeScaleFactor}"
ScaleY="{StaticResource LargeScaleFactor}" />
.
.
.
</ResourceDictionary>
To setup this set of pre-configured AnimationSettings
already available in your app, perform the following steps:
- Right-click on your project, then click Add > New Item...
- Choose Resource Dictionary and name it
Animations.xaml
- In your
App.xaml
, add the following:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Animations.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
- In
Animations.xaml
, copy & paste the contents from the corresponding links below:
Your app now has a global set of common animations ready to use.
Animations can be combined, and as previously mentioned, any of these combined animations that are commonly used should be placed in the global ResourceDictionary
(ex: Animations.xaml
):
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:xf="using:XamlFlair">
.
.
.
<xf:AnimationSettings x:Key="FadeInAndSlideFromLeft"
Kind="FadeFrom,TranslateXFrom"
Opacity="0"
OffsetX="{StaticResource NegativeOffset}" />
<xf:AnimationSettings x:Key="FadeInAndSlideFromTop"
Kind="FadeFrom,TranslateYFrom"
Opacity="0"
OffsetY="{StaticResource NegativeOffset}" />
<xf:AnimationSettings x:Key="FadeInAndGrow"
Kind="FadeFrom,ScaleXFrom,ScaleXFrom"
Opacity="0"
ScaleX="{StaticResource SmallScaleFactor}"
ScaleY="{StaticResource SmallScaleFactor}" />
<xf:AnimationSettings x:Key="FadeInAndGrowHorizontally"
Kind="FadeFrom,ScaleXFrom"
Opacity="0"
ScaleX="{StaticResource SmallScaleFactor}" />
.
.
.
</ResourceDictionary>
This demonstrates a combined animation of a FadeFrom
and TranslateFrom
:
This demonstrates a combined animation of a FadeFrom
, TranslateFrom
, and ScaleFrom
:
Animations can have their settings overridden directly on the FrameworkElement
. This is commonly done to alter values for Delay and Duration so that we don't over-populate the Animations.xaml
file with repeated resources. To achieve overriding, use the Animate
markup extension paired with the BasedOn
property:
<Border xf:Animations.Primary="{xf:Animate BasedOn={StaticResource ScaleFromBottom}, Delay=500}">
A compound animation is simply a multi-step animation using the CompoundSettings
class. Each inner animation executes once the previous one completes, hence they're sequential animations:
<xf:CompoundSettings x:Key="Compound">
<xf:CompoundSettings.Sequence>
<xf:AnimationSettings Kind="ScaleXTo"
ScaleX="1.25"
Duration="1250" />
<xf:AnimationSettings Kind="ScaleXTo"
ScaleX="1"
Duration="1250" />
<xf:AnimationSettings Kind="RotateTo"
Rotation="360"
Duration="1250" />
</xf:CompoundSettings.Sequence>
</xf:CompoundSettings>
Note:
CompoundSettings
support theEvent
property, which is discussed in a later section.
An animation can be repeated by using the IterationBehavior
and IterationCount
properties (default values of Count
and 1
respectively).
The following demonstrates how to run an animation only 5 times:
<Border xf:Animations.Primary="{StaticResource FadeIn}"
xf:Animations.IterationCount="5" />
The following demonstrates how to run an animation indefinitely:
<Border xf:Animations.Primary="{StaticResource FadeIn}"
xf:Animations.IterationBehavior="Forever" />
Also note that it is also possible to repeat a Compound animation. For example, using the compound animation (named Progress) from the previous section:
<Border xf:Animations.Primary="{StaticResource Progress}"
xf:Animations.IterationCount="5" />
Warning: When using repeating animations, you cannot set a
Secondary
animation on the element.
By default, all animations execute once the UI element fires its Loaded
event. This behavior can be overridden by setting the Event
property. Event
can be one of the following values:
- Loaded (default value)
- Loading (UWP-only)
- None
- Visibility (triggers only when Visibility == Visible)
- DataContextChanged
- PointerOver
- PointerExit
- GotFocus
- LostFocus
When specifying None
, you will manually need to trigger your animations using the PrimaryBinding
or SecondaryBinding
properties. These properties are of type bool
and expect a value of True
in order to execute the corresponding animation. The following is an example of triggering an animation based off the IsChecked
of the CheckBox control:
<CheckBox x:Name="SampleCheckBox"
IsChecked="False" />
<Rectangle xf:Animations.PrimaryBinding="{Binding IsChecked, ElementName=SampleCheckBox}"
xf:Animations.Primary="{xf:Animate BasedOn={StaticResource FadeIn}, Event=None}" />
The above animation will only execute when the IsChecked
is True
. If None
was not specified for Event
, the animation would then execute on Loaded
and on the binding.
There will be cases when you will need your UI element to start in a specific state, for example, the element needs to be shrunk before its animation executes. This is achieved using the StartWith
property:
<Rectangle xf:Animations.Primary="{xf:Animate BasedOn={StaticResource ScaleFromBottom}, Delay=500}"
xf:Animations.StartWith="{StaticResource ScaleFromBottom}" />
In the above example, since the element is scaling from the bottom, but with a delay, we need to start in the scaled position, so we use the StartWith
property to set its initial state. What StartWith
essentially does is setup the initial values on the element as soon as it has loaded.
The .Net documentation states the following:
In some cases, it might appear that you can't change the value of a property after it has been animated. ...you must stop the animation from influencing the property.
There may be cases when you animate the opacity, in which the opacity animation suddenly resets it's value instead of animating, or doesn't behave as you intend. In cases, you may need to set AllowOpacityReset = False
(the default value of AllowOpacityReset
is True
) to achieve the intended behavior:
<Image xf:Animations.Primary="{StaticResource FadeInThenFadeOut}"
xf:Animations.AllowOpacityReset="False"
Source="/Assets/..." />
A helpful property that exists in WPF, ClipToBounds
is a helpful property that exists in WPF, but unfortunately not in UWP. Therefore, it has been added in XamlFlair due to its ease of use and handiness. To clip child content to the bounds of the containing element, simply set ClipToBounds
to True
on the containing element:
<Border xf:Layout.ClipToBounds="True">
.
.
.
<Border>
The XamlFlair library abstracts its logging using LibLog. LibLib supports the major logging frameworks, which allows a developer using the XamlFlair library to choose their preferred logging system. Below is a logging example using Serilog in a UWP app:
public App()
{
this.InitializeComponent();
.
.
.
// Setup the Serilog logger
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Debug()
.CreateLogger();
}
To output the values of one or more animations, simply set True
to the EnableLogging
property on the target FrameworkElement
:
<Rectangle xf:Animations.EnableLogging="True"
xf:Animations.Primary="{StaticResource SlideFromLeft}" />
Doing so will provide you with the following similar console output (differs slightly for WPF):
Element = Windows.UI.Xaml.Controls.Button
Kind = FadeFrom, TranslateFrom
TargetProperty = Translation
Duration = 500
Delay = 0
Opacity = 0
OffsetX = 0
OffsetY = 50
OffsetZ = 0
ScaleX = 1
ScaleY = 1
ScaleZ = 1
Rotation = 0
Blur = 0
TransformCenterPoint = 0.5,0.5
Easing = Cubic
EasingMode = EaseOut
As each storyboard executes, it's kept in an internal list until it completes (or gets stopped). To output this internal list, temporarily add the following in your app startup code:
Animations.EnableActiveTimelinesLogging = true;
Doing so will provide you with the following similar console output:
---------- ALL ACTIVE TIMELINES --------
Active timeline removed at 12:42:26:43222
Element = Button, Key = d69f826a-1978-4a4e-b516-4a6b0469238b, ElementGuid = 195d8c13-1dd7-4fef-a7f3-fc78bdab1cd7
State = Running, IsSequence = False, IsIterating = False, IterationBehavior = Count, IterationCount = 0
------------------------------------
---------- ACTIVE TIMELINE --------
Guid d69f826a-1978-4a4e-b516-4a6b0469238b - Updated state to: Completed at 12:42:26:88616
------------------------------------
---------- ALL ACTIVE TIMELINES --------
Active timeline removed at 12:42:26:89614
NO ACTIVE TIMELINES!
------------------------------------
In order to properly implement item animations on list items, it was not enough to simply create attached properties against the ListViewBase (UWP) and ListBox (WPF) controls. Instead, inherited controls were created: AnimatedListView
and AnimatedGridView
for UWP, and AnimatedListView
and AnimatedListBox
for WPF, all available from the XamlFlair.Controls
namespace:
UWP namespace:
xmlns:xfc="using:XamlFlair.Controls"
WPF namespace:
xmlns:xfc="clr-namespace:XamlFlair.Controls;assembly=XamlFlair.WPF"
Animating items in lists is slightly different than animating a typical UI element. The main reason for this difference is that the Event
value on the corresponding AnimationSettings
cannot be changed from its default value. Therefore the following is invalid:
<xfc:AnimatedListView ItemsSource="Binding SampleDataList"
xf:Animations:Items="{xf:Animate BasedOn={StaticResource FadeIn}, Event=Visibility}" />
List item animations, by default, animate based on the Loaded
event of each visible item, and also based on an update to the ItemsSource
property of the list control. In order to disable these default behaviors, the following two properties can be used independently:
<xfc:AnimatedListView ItemsSource="Binding SampleDataList"
xf:Animations.AnimateOnLoad="False"
... />
<xfc:AnimatedListView ItemsSource="Binding SampleDataList"
xf:Animations.AnimateOnItemsSourceChange="False"
... />
By default, item animations have a delay of 25 milliseconds between each item. This value can be changed using the InterElementDelay
property:
<xfc:AnimatedListView ItemsSource="Binding SampleDataList"
xf:Animations.InterElementDelay="50"
... />
Just like PrimaryBinding
and SecondaryBinding
, item animations can be triggered by a binding with the use of the ItemsBinding
property:
<xfc:AnimatedListView ItemsSource="Binding SampleDataList"
xf:Animations.AnimateOnLoad="False"
xf:Animations.ItemsBinding="{Binding MyViewModelProperty}"
xf:Animations:Items="{xf:Animate BasedOn={StaticResource FadeIn}}" />
Warning (UWP-only): Be aware that if you have any
ItemContainerTransitions
set on theAnimatedListView
orAnimatedGridView
, they will be cleared. This is done to avoid conflicting item animations.
Note (UWP-only): To avoid any flickers on item animations, there is currently a constraint in place:
Items
animation must contain aFadeFrom
.