Sunday, January 11, 2009

Loading animation


This animation was inspired by well known video sharing site, and it's handy when you need to block the UI during a long running process. It will dim any controls placed underneath it and block mouse access to those controls.






The concept is fairly simple, just a style that targets Control. It contains a series of ellipses layed out in a circle on a canvas. When the control is set to visible the animation starts, each ellipse is targeted by a storyboard which lasts just over one and a half seconds. These storyboard simply animate the fill from opage to transparent.

You can download the full source from here.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Color x:Key="FilledColor" A="255" B="155" R="155" G="155"/>
<Color x:Key="UnfilledColor" A="0" B="155" R="155" G="155"/>

<Style x:Key="BusyAnimationStyle" TargetType="Control">
<Setter Property="Background" Value="#7F000000"/>

<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Control">
<ControlTemplate.Resources>
<Storyboard x:Key="Animation0" BeginTime="00:00:00.0" RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames
Storyboard.TargetName="ellipse0"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
>
<SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
<SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>

<Storyboard x:Key="Animation1" BeginTime="00:00:00.2" RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames
Storyboard.TargetName="ellipse1"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
>
<SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
<SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>

<Storyboard x:Key="Animation2" BeginTime="00:00:00.4" RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames
Storyboard.TargetName="ellipse2"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
>
<SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
<SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>

<Storyboard x:Key="Animation3" BeginTime="00:00:00.6" RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames
Storyboard.TargetName="ellipse3"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
>
<SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
<SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>

<Storyboard x:Key="Animation4" BeginTime="00:00:00.8" RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames
Storyboard.TargetName="ellipse4"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
>
<SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
<SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>

<Storyboard x:Key="Animation5" BeginTime="00:00:01.0" RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames
Storyboard.TargetName="ellipse5"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
>
<SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
<SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>

<Storyboard x:Key="Animation6" BeginTime="00:00:01.2" RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames
Storyboard.TargetName="ellipse6"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
>
<SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
<SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>

<Storyboard x:Key="Animation7" BeginTime="00:00:01.4" RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames
Storyboard.TargetName="ellipse7"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
>
<SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
<SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</ControlTemplate.Resources>

<ControlTemplate.Triggers>
<Trigger Property="IsVisible" Value="True">
<Trigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource Animation0}" x:Name="Storyboard0" />
<BeginStoryboard Storyboard="{StaticResource Animation1}" x:Name="Storyboard1"/>
<BeginStoryboard Storyboard="{StaticResource Animation2}" x:Name="Storyboard2"/>
<BeginStoryboard Storyboard="{StaticResource Animation3}" x:Name="Storyboard3"/>
<BeginStoryboard Storyboard="{StaticResource Animation4}" x:Name="Storyboard4"/>
<BeginStoryboard Storyboard="{StaticResource Animation5}" x:Name="Storyboard5"/>
<BeginStoryboard Storyboard="{StaticResource Animation6}" x:Name="Storyboard6"/>
<BeginStoryboard Storyboard="{StaticResource Animation7}" x:Name="Storyboard7"/>
</Trigger.EnterActions>

<Trigger.ExitActions>
<StopStoryboard BeginStoryboardName="Storyboard0"/>
<StopStoryboard BeginStoryboardName="Storyboard1"/>
<StopStoryboard BeginStoryboardName="Storyboard2"/>
<StopStoryboard BeginStoryboardName="Storyboard3"/>
<StopStoryboard BeginStoryboardName="Storyboard4"/>
<StopStoryboard BeginStoryboardName="Storyboard5"/>
<StopStoryboard BeginStoryboardName="Storyboard6"/>
<StopStoryboard BeginStoryboardName="Storyboard7"/>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>

<Border
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
>
<Canvas Height="60" Width="60">
<Canvas.Resources>
<Style TargetType="Ellipse">
<Setter Property="Width" Value="15"/>
<Setter Property="Height" Value="15" />
<Setter Property="Fill" Value="#009B9B9B" />
</Style>
</Canvas.Resources>

<Ellipse x:Name="ellipse0" Canvas.Left="1.75" Canvas.Top="21"/>
<Ellipse x:Name="ellipse1" Canvas.Top="7" Canvas.Left="6.5"/>
<Ellipse x:Name="ellipse2" Canvas.Left="20.5" Canvas.Top="0.75"/>
<Ellipse x:Name="ellipse3" Canvas.Left="34.75" Canvas.Top="6.75"/>
<Ellipse x:Name="ellipse4" Canvas.Left="40.5" Canvas.Top="20.75" />
<Ellipse x:Name="ellipse5" Canvas.Left="34.75" Canvas.Top="34.5"/>
<Ellipse x:Name="ellipse6" Canvas.Left="20.75" Canvas.Top="39.75"/>
<Ellipse x:Name="ellipse7" Canvas.Top="34.25" Canvas.Left="7" />
<Ellipse Width="39.5" Height="39.5" Canvas.Left="8.75" Canvas.Top="8" Visibility="Hidden"/>
</Canvas>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

9 comments:

  1. I'm very new to WPF, and I like your loading animation here. How do I turn the animation on (displayed) when the user clicks a tab control (GotFocus), then have it turn off once I've loaded a lot of info from the db to put into the tab? My newbie attempts have come up empty. I assume I have to use some sort of asynchronous mechanism - spawn a thread to load the data? A real-world example having the animation disabled once a process is finished would be much appreciated. I'm out of water outside the web.

    ReplyDelete
  2. Thanks, very helpful!

    ReplyDelete
  3. Thanks you made my day today :)

    ReplyDelete
  4. When I copy the code in my project, it keeps telling me that "BusyAnimationStyle" was not found. Any idea why it can't find the static resource? I would really appreciate your help. Thank you.

    ReplyDelete
  5. Oh, I saw the App.xaml. Sorry! And thank you!

    ReplyDelete
  6. how to use this in mvvm? as stated in your comment here http://stackoverflow.com/questions/1182825/making-a-generic-loading-animation-in-mvvm

    ReplyDelete
  7. You bind the Visibility property of the control to a suitable property on your view model. In the StackOverflow answer I bound the Visibility property of the control to a boolean IsLoadingData property in the view model, using the BooleanToVisibilityConverter to convert the value to Visibility.Visible/Collapsed

    ReplyDelete
  8. Thank you very much for this simple and helpful article!

    ReplyDelete

Note: Only a member of this blog may post a comment.