Les fonctions de easing de WPF, autrement appellées les fonctions d’accélération sont des fonctions qui permettent de ralentir ou d’accélérer le mouvement d’une animation selon une formule mathématique. Dans ce billet nous allons voir comment réaliser une petite application WPF qui liste et dessine les fonctions de Easing disponibles dans le framework WPF.
Commençons par le design de la fenêtre principale qui contiendra :
- un canvas pour dessiner les fonctions
- une liste déroulante pour choisir la fonction de easing à représenter
- une case à cocher pour activer / désactiver les animations, car on souhaite faire bouger des plots sur les courbes du graphiques, afin de mettre en évidence les effets d’accélération et de ralentissement introduits par les formules
cela devra ressembler à quelque chose comme ça :
le code XAML de la fenêtre principale (MainWindow.xaml) est le suivant :
<Window x:Class="FranckGaspoz.Wordpress.WPF.CSharp.EasingFunctionDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:EasingFunctionDemo" mc:Ignorable="d" Title="EasingFunctionDemo" ResizeMode="NoResize" WindowStartupLocation="CenterScreen" Height="402" Width="519" SizeToContent="WidthAndHeight" > <Grid> <Grid.Resources> <!-- brush de fond du diagramme --> <DrawingBrush x:Key="BackBrush" Viewport="0,0,10,10" ViewportUnits="Absolute" TileMode="Tile"> <DrawingBrush.Drawing> <DrawingGroup> <GeometryDrawing Geometry="M0,0 L1,0 1,0.1, 0,0.1Z" Brush="#EEEEEE" /> <GeometryDrawing Geometry="M0,0 L0,1 0.1,1, 0.1,0Z" Brush="#EEEEEE" /> </DrawingGroup> </DrawingBrush.Drawing> </DrawingBrush> </Grid.Resources> <!-- layout principal --> <Grid Name="GR_Layout" Background="WhiteSmoke"> <Grid.RowDefinitions> <RowDefinition Height="40"/> <RowDefinition Height="320"/> <RowDefinition Height="20"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="20"/> <ColumnDefinition Width="470"/> <ColumnDefinition Width="20"/> </Grid.ColumnDefinitions> <!-- barre de contrôles --> <StackPanel Grid.ColumnSpan="3" HorizontalAlignment="Left" Orientation="Horizontal" VerticalAlignment="Center" Margin="0" > <Label Content="easing function: "/> <!-- liste des fonctions --> <ComboBox Name="CB_EaseFunctions" Width="Auto" SelectionChanged="CB_EaseFunctions_SelectionChanged" /> <!-- activation/désactivation des animations --> <CheckBox Margin="8,0,0,0" VerticalAlignment="Center" IsChecked="{Binding AnimationEnabled}" Name="CKB_AnimationsOn" Click="CKB_AnimationsOn_Click"> animation enabled </CheckBox> </StackPanel> <Border BorderThickness="1" BorderBrush="Gray" Grid.Row="1" Grid.Column="1"> <!-- et enfin la zone de dessin --> <Canvas Background="{StaticResource BackBrush}" Name="Canvas" /> </Border> </Grid> </Grid> </Window>
Le code behind est relativement simple et s’articule autour des 4 propriétés de la classe :
// drawer du graph EasingFunctionDrawer drawer = new EasingFunctionDrawer(); // liste des fonctions BindingList<string> EasingFunctions = new BindingList<string>(); // fonction de easing en cours d'affichage EasingFunctionBase EasingFunction = null; // indique si l'animation est activée ou non public bool AnimationEnabled { get { return (bool)GetValue(AnimationEnabledProperty); } set { SetValue(AnimationEnabledProperty, value); } } // propriété de dépendance pour lier le CheckBox public static readonly DependencyProperty AnimationEnabledProperty = DependencyProperty.Register("AnimationEnabled", typeof(bool), typeof(MainWindow), new PropertyMetadata(true));
La liste des fonctions est construite par introspection des classes du namespace System.Windows.Media.Animation dans lequel les classes qui nous interressent respectent la convention de nommage {fonctionName}Easing
La méthode InitGraph est appellée lorsque le graphique doit être redessinné (constructeur de la fenêtre, sélection dans la liste ou clic sur checkbox)
Toutes les tâches de dessin sont confiées à la classe EasingFunctionDrawer, dont la méthode qui permet de réprésenter une fonction de easing :
/// <summary> /// dessine une fonction de easing sur un canvas /// </summary> /// <param name="canvas">canvas cible</param> /// <param name="origin"> coordonnées de l'origine du graph</param> /// <param name="Length">taille du graphique (carré englobant)</param> /// <param name="easeColor">couleur de trait de la fonction</param> /// <param name="easingFunc">fonction de easing</param> /// <param name="scaley">échelle sur l'axe Y (facteur de réduction)</param> internal void Draw( Canvas canvas, Point origin, double Length, Brush easeColor, EasingFunctionBase easingFunc, double scaley ) { double stp = 0.025/10d,d,x,y; // l'axe des X est parcouru linéairement for (double a = 0;a<=1;a+=stp) { x = origin.X + a * Length; y = origin.Y; /** * la fonction de easing est appellée pour déterminer la valeur * pour la position X, qui est ici fournit comme paramètre de * temps normalisé t (pour f(t) ou f est la fonction de easing) */ d = easingFunc.Ease(a); // l'ordonnée est déterminée selon la valeur de la fonction de easing y = origin.Y - d * Length / scaley; // le point est tracé DrawPlot(canvas, x, y, easeColor); } }
L’animation d’un plot qui suit la courbe est réalisée via la méthode Animate(…) :
/// <summary> /// anime un point qui parcourt la représentaiton d'une fonction de easing /// </summary> /// <param name="canvas">canvas cible</param> /// <param name="origin">origine du graphique</param> /// <param name="Length">taille du graphique (carré englobant)</param> /// <param name="easeColor">couleur de trait de la fonction</param> /// <param name="easingFunc">fonction de easing</param> /// <param name="scaley">échelle sur l'axe Y (facteur de réduction)</param> internal void Animate( Canvas canvas, Point origin, double Length, Brush easeColor, EasingFunctionBase easingFunc, double scaley ) { var duration = TimeSpan.FromSeconds(3); var r = DrawPlot( canvas, origin.X, origin.Y, easeColor, 6d); var ta = new TranslateTransform(0,0); r.RenderTransform = ta; // progression linéaire sur l'axe des X (axe de temps) var x_move = new DoubleAnimation() { From = 0, To = Length, Duration = duration, AutoReverse = true, RepeatBehavior = RepeatBehavior.Forever }; // progression selon la fonction de ease sur l'axe des Y var y_move = new DoubleAnimation() { From = 0, To = - Length/scaley, Duration = duration, EasingFunction = easingFunc, AutoReverse = true, RepeatBehavior = RepeatBehavior.Forever };<br /> // démarre l'animation ta.BeginAnimation( TranslateTransform.XProperty, x_move );<br /> ta.BeginAnimation( TranslateTransform.YProperty, y_move ); }
Voici un exemple du résultat :



