[Windows 8] Créer un timer rond en XAML

15. April 2013 21:27 by Renaud in Expression Blend, Windows 8, XAML  //  Tags: , , , ,   //   Comments (0)

Pour le développement du jeu WordDefy pour Windows 8, Matthieu (@MatthieuVdh) et moi avons demandé l'aide d'une amie graphiste. Et comme elle nous a rapidement pondu un super truc, la moindre des choses c'était de tenter de le reproduire à l'identique ! Il y a plusieurs bons côtés à cela : premièrement on a une app avec un look plutôt sympa, et deuxièmement j'ai pu jouer avec Blend et m'amuser avec le XAML, ce qui fait que j'ai appris pas mal de choses!

Une des parties consistait à reproduire un Timer en forme de cercle. Pour que vous y voyez plus clair, voici le compteur original tel que designé et le résultat final (qui est assez ressemblant :)) en XAML :

Design original
Design original

Résultat en XAML
XAML

Conception d'un RoundedTimer

Il existe plein d'exemples de timer sur le web, mais aucun fonctionnant sous WinRT. Et évidemment, le XAML sur Windows 8 a quelques particularités qui font que l'exemple ne sont pas toujours utilisables. J'en ai donc recréé un avec Blend !

Générer un Path

La première étape consiste à créer le cercle du compteur. Ce cercle doit être transparent au milieu. Pour faire cela, on va utiliser deux Ellipses et les combiner (ou plus précisément les soustraire). Placez donc deux Ellipses de manière à ce qu'elles soient centrées sur le même point, et avec l'une d'elle légèrement plus petite que l'autre.

Le code jusqu'à présent:

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    	<Ellipse Fill="#FFF4F4F5" HorizontalAlignment="Left" Height="200" Margin="160,240,0,0" Stroke="Black" VerticalAlignment="Top" Width="200"/>
    	<Ellipse Fill="#FFF4F4F5" HorizontalAlignment="Left" Height="140" Margin="190,270,0,0" Stroke="Black" VerticalAlignment="Top" Width="140"/>
    </Grid>

Pour obtenir le résultat voulu, on peut donc soustraire la deuxième Ellipse à la première ou exclure les zones de chevauchement (lisez aussi Combiner des formes et des tracés (Blend pour Visual Studio)). Faites un clique droit sur l'arbre visuel et les Ellipses sélectionnées et choisissez l'opération qui vous convient dans le menu Combiner

Le résultat de cette opération est un Path généré en remplacement des deux Ellipses précédemment créées. Le résultat devrait ressembler à ceci:

Et le code correspondant : 

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    	<Path Data="M100,30.5 C61.6162,30.5 30.5,61.6162 30.5,100 C30.5,138.384 61.6162,169.5 100,169.5 C138.384,169.5 169.5,138.384 169.5,100 C169.5,61.6162 138.384,30.5 100,30.5 z M100,0.5 C154.952,0.5 199.5,45.0477 199.5,100 C199.5,154.952 154.952,199.5 100,199.5 C45.0477,199.5 0.5,154.952 0.5,100 C0.5,45.0477 45.0477,0.5 100,0.5 z" Fill="#FFF4F4F5" HorizontalAlignment="Left" Height="200" Margin="160,240,0,0" Stretch="Fill" Stroke="Black" UseLayoutRounding="False" VerticalAlignment="Top" Width="200"/>
    </Grid>

Ce cercle est la base de notre RoundedTimer... Passons à l'étape suivante: le clipping ! :)

Clipping Path

Le principe du clipping path est d'utiliser un élément pour délimiter la zone visible d'un autre élément. Malheureusement dans les apps Windows Store on ne peut pas utiliser un Path pour "masquer" un autre élément. Par contre on peut utiliser des Ellipse et des Rectangle pour délimiter une zone. 

Ajoutons par exemple un Rectangle dans sur un coin de notre cercle : 

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <Rectangle Margin="160,240,1106,428" Width="100" Height="100"/>
    <Path Data="M100,30.5 C61.6162,30.5 30.5,61.6162 30.5,100 C30.5,138.384 61.6162,169.5 100,169.5 C138.384,169.5 169.5,138.384 169.5,100 C169.5,61.6162 138.384,30.5 100,30.5 z M100,0.5 C154.952,0.5 199.5,45.0477 199.5,100 C199.5,154.952 154.952,199.5 100,199.5 C45.0477,199.5 0.5,154.952 0.5,100 C0.5,45.0477 45.0477,0.5 100,0.5 z" Fill="#FFF4F4F5" HorizontalAlignment="Left" Height="200" Margin="160,240,0,0" Stretch="Fill" Stroke="Black" UseLayoutRounding="False" VerticalAlignment="Top" Width="200"/>
</Grid>

 

Le résultat obtenu est le suivant : 

On se retrouve avec un quart de cercle, ce qui va en fait représenter un quart de notre RoundedTimer ! Ce qu'il reste à faire est d'animer ce quart de Timer pour représenter le temps qui s'écoule.

Animer le Timer

Pour cela on va utiliser une Transformation sur l'élément RectangleGeometry. A vrai dire il y a sans doute plusieurs façon d'arriver au même résultat ! Pour ma part j'y suis arrivé en utilisant un SkewTransform.

Si l'on reprend le quart de cercle précédent, on peut l'animer en utilisant la transformation correcte : 

<Path Data="M100,30.5 C61.6162,30.5 30.5,61.6162 30.5,100 C30.5,138.384 61.6162,169.5 100,169.5 C138.384,169.5 169.5,138.384 169.5,100 C169.5,61.6162 138.384,30.5 100,30.5 z M100,0.5 C154.952,0.5 199.5,45.0477 199.5,100 C199.5,154.952 154.952,199.5 100,199.5 C45.0477,199.5 0.5,154.952 0.5,100 C0.5,45.0477 45.0477,0.5 100,0.5 z" Fill="White" HorizontalAlignment="Left" Height="200" Margin="160,240,0,0" Stretch="Fill" Stroke="Black" UseLayoutRounding="False" VerticalAlignment="Top" Width="200">
	<Path.Clip>
		<RectangleGeometry Rect="0,0,100,100">
			<RectangleGeometry.Transform>
                <!-- AngleY : De 0 à 90 -->
                <SkewTransform CenterX="100" CenterY="100" AngleY="0"/>
            </RectangleGeometry.Transform>
		</RectangleGeometry>
	</Path.Clip>
</Path>

En modifiant la valeur de la propriété AngleY, on peut animer joliment le timer ! Ainsi, avec un angle de 40°, on retrouve la forme suivante :

Il ne reste donc plus qu'à créer trois autres quarts du timer, et à les animer en changeant la valeur d'un des angles toutes les secondes grâce à un DispatcherTimer. L'important étant de voir comment appliquer la SkewTransformation, en jouant sur les propriétés Center et Angle !

Téléchargement / Sources

Vous pouvez télécharger le résultat final sous la forme d'une contrôle sur GitHub ! À vous de le customiser ! :)

Pour l'intégrer dans votre app, rien de plus simple :

<Page
    x:Class="TestApp.MainPage"
    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:roundedTimer="using:RoundedTimer"
    mc:Ignorable="d">
    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <roundedTimer:RoundedTimer x:Name="MyTimer" Width="200" Height="200" Duration="00:01:00" />
    </Grid>
</Page>

On instancie le timer dans le XAML, et on démarre le décompte avec un appel à la méthode Start() dans le code-behind :

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            MyTimer.Start();
        }

 

TextBox

About the author

I'm a developer, blog writer, and author, mainly focused on Microsoft technologies (but not only Smile). I'm Microsoft MVP Client Development since July 2013.

Microsoft Certified Professional

I'm currently working as an IT Evangelist with an awesome team at the Microsoft Innovation Center Belgique, where I spend time and energy helping people to develop their projects. I also give training to enthusiastic developers and organize afterworks with the help of the Belgian community.

MIC Belgique

Take a look at my first book (french only): Développez en HTML 5 pour Windows 8

Développez en HTML5 pour Windows 8

Membre de l'association Fier d'être développeur

TextBox

Month List