[Silverlight] Un petit contrôle d’expand/collapse
J’ai du cette semaine trouver une solution pour “Animer la transition entre l’état Visible et Collapsed” d’un contrôle.
L’idée est de faire quelque chose permettant d’avoir une transition smooth.
Pour avoir une flexibilité maximum, j’ai choisi d’étendre un ContentControl. Cela me permettra d’utiliser mon contrôle comme un container facilement réutilisable.
Le style est plutôt simple :
<Style TargetType="local:VisibilityAnimator">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:VisibilityAnimator">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Rectangle x:Name="animator" />
<ContentPresenter x:Name="presenter" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Notez tout de même la présence du rectangle “animator” sur lequel je reviendrai plus tard.
Au niveau du code behind, il faut noter la présence de la DependencyProperty Visibility qui est évidemment celle qui nous utiliserons afin de lancer les animations. Il est évidemment impératif d’utiliser le mot clé “new” afin de spécifier que nous voulons faire un override de la propriété (déjà existante dans UIElement).
Dans le Handler de la DependencyProperty, il nous suffira de faire un check sur le e.NewValue afin d’appeller les méthode Expand ou Collapse.
Pour le Collapse, les chose sont assez simple. Nous allons procéder en 4 étapes.
- Donner au rectangle “animator” (cf le code XAML) la taille actuelle du contrôle.
- Rendre “presenter” transparent. Ici, pas d’animation, après de nombreux test, le temps du fade out est plus gênant qu’autre chose. Mais je garde quand même l’étape pour 2 petites raisons, si vous voulez utiliser ce contrôle et que vous désirez une animation de FadeOut avant celle de Collapse, c’est ici que ca se passe. Lors de l’Expand, j’utilise une animation pour effectuer le FadeIn. j’ai donc besoin que l’opacité soit à 0.
- Rendre “presenter” invisible. j’insiste sur la nuance, ici nous utilisons bien la propriété Visibility.
- Enfin, une petite animation pour la taille de “animator”.
Au niveau du code cela donne:
private void Collapse()
{
//Step 1 : Set animator size
animator.Width = ActualWidth;
animator.Height = ActualHeight;
//Step 2 : Fade out Children
UpdatePresenterOpacity(0);
//Step 3 : Make Children invisible
UpdatePresenterVisibility(Visibility.Collapsed);
//Step 4 : Make Rect small
GetAnimatorSizeStoryboard(0, 0).Begin();
}
Ici nous remarquons tout d’abord les 2 méthode UpdatePresenterXXX() qui ne font rien d’autre que changer la propriété ciblée avec la valeur en paramètre. L’utilité réside dans le fait qu’avant de réellement modifier la valeur, je fait un test afin de vérifier que “presenter” n’est pas NULL.
Vous n’aurez évidemment pas manqué la méthode GetAnimatorStoryboard dont voici le code:
private Storyboard GetAnimatorSizeStoryboard(double width, double height)
{
Storyboard sizeStoryboard = new Storyboard();
sizeStoryboard.Children.Add(GetAnimation(animator, width, ANIMATION_TIME, "Width"));
sizeStoryboard.Children.Add(GetAnimation(animator, height, ANIMATION_TIME, "Height"));
return sizeStoryboard;
}
Utilisant GetAnimation :
private static Timeline GetAnimation(DependencyObject obj, double value, double duration, string property)
{
var newAnim = new DoubleAnimationUsingKeyFrames { BeginTime = TimeSpan.FromSeconds(0) };
Storyboard.SetTarget(newAnim, obj);
Storyboard.SetTargetProperty(newAnim, new PropertyPath(property));
newAnim.KeyFrames.Add(new SplineDoubleKeyFrame
{
Value = value,
KeyTime = TimeSpan.FromSeconds(duration),
KeySpline = new KeySpline
{
ControlPoint1 = new Point(.188, .000),
ControlPoint2 = new Point(.0, 1)
}
});
return newAnim;
}
Enfin, nous pouvons nous attaquer à la méthode Expand, un tout petit peu plus compliquée. Cette fois-ci j’ai relevé 5 étapes clé :
- Donner au rectangle “animator” la taille actuelle du contrôle. Cela devrait donner 0…
- Récupérer la taille finale (nous verrons comment après…)
- Animer la taille du rectangle pour retrouver la taille finale
- Rendre “presenter” visible
- Enfin, une petite animation pour le FadeIn de “presenter”
ce qui donne en code:
private void Expand()
{
//Step 1 : Set animator size
animator.Width = ActualWidth;
animator.Height = ActualHeight;
//Step 2 : Get final size
UpdatePresenterVisibility(Visibility.Visible);
UpdateLayout();
double finalHeight = ActualHeight;
double finalWidth = ActualWidth;
UpdatePresenterVisibility(Visibility.Collapsed);
UpdateLayout();
//Step 3 : Make rect big
Storyboard expandStoryboard = GetAnimatorSizeStoryboard(finalWidth, finalHeight);
expandStoryboard.Completed += delegate
{
//Step 4 : Make children visible
UpdatePresenterVisibility(Visibility.Visible);
//Step 5 : Fade in Children
GetChildrenFadeStoryboard(1).Begin();
};
expandStoryboard.Begin();
}
Nous conviendrons tous que la Step 2 n’est pas parfaite mais je n’ai pas trouvé de meilleure façon de faire… Je suis donc ouvert à toute amélioration…
J’arrive donc a la fin de cet article… Il ne me reste plus qu’à espérer que vous avez apprécier votre lecture et que ce contrôle vous sera utile ;-)
A bientôt,
DjoDjo
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :
Attachment(s): VisibilityAnimator.zip