Partager via


Xamarin.Forms BoxView

BoxView affiche un rectangle simple d’une largeur, d’une hauteur et d’une couleur spécifiées. Vous pouvez utiliser BoxView pour la décoration, les graphismes rudimentaires et pour l’interaction avec l’utilisateur par le biais de l’interaction tactile.

Étant donné que Xamarin.Forms n’a pas de système graphique vectoriel intégré, l’aide BoxView à compenser. Certains exemples de programmes décrits dans cet article sont utilisés BoxView pour le rendu des graphiques. La BoxView taille peut être dimensionnée pour ressembler à une ligne d’une largeur et d’une épaisseur spécifiques, puis pivotée par n’importe quel angle à l’aide de la Rotation propriété.

Bien que BoxView vous puissiez imiter des graphiques simples, vous pouvez examiner l’utilisation de SkiaSharp pour Xamarin.Forms obtenir des exigences graphiques plus sophistiquées.

Définition de la couleur et de la taille boxView

En règle générale, vous allez définir les propriétés suivantes :BoxView

  • Color pour définir sa couleur.
  • CornerRadius pour définir son rayon d’angle.
  • WidthRequest pour définir la largeur des unités indépendantes de l’appareil BoxView .
  • HeightRequest pour définir la hauteur du BoxView.

La Color propriété est de type Color; la propriété peut être définie sur n’importe quelle Color valeur, y compris les 141 champs statiques en lecture seule de couleurs nommées allant de AliceBlue à YellowGreen.

La CornerRadius propriété est de type CornerRadius; la propriété peut être définie sur une valeur de rayon d’angle uniforme unique double , ou une CornerRadius structure définie par quatre double valeurs appliquées au haut à gauche, en haut à droite, en bas à gauche et en bas à droite du BoxView.

Les WidthRequest propriétés ne HeightRequest jouent qu’un rôle si la BoxView disposition n’est pas contrainte . Il s’agit du cas où le conteneur de disposition doit connaître la taille de l’enfant, par exemple, quand il BoxView s’agit d’un enfant d’une cellule de taille automatique dans la Grid disposition. A BoxView n’est pas non plus contrainte lorsque ses propriétés et VerticalOptions ses HorizontalOptions propriétés sont définies sur des valeurs autres que LayoutOptions.Fill. Si la BoxView valeur est non contrainte, mais que les propriétés et HeightRequest les WidthRequest propriétés ne sont pas définies, la largeur ou la hauteur sont définies sur les valeurs par défaut de 40 unités, ou environ 1/4 pouce sur les appareils mobiles.

Les WidthRequest propriétés et HeightRequest les propriétés sont ignorées si la BoxView disposition est limitée , auquel cas le conteneur de disposition impose sa propre taille sur le BoxView.

Un BoxView peut être contraint dans une dimension et non contrainte dans l’autre. Par exemple, si l’enfant BoxView est un enfant d’une dimension verticale StackLayout, la dimension verticale de l’objet BoxView n’est pas contrainte et sa dimension horizontale est généralement limitée. Toutefois, il existe des exceptions pour cette dimension horizontale : si la BoxView HorizontalOptions propriété a la valeur autre que LayoutOptions.Fill, la dimension horizontale est également non contrainte. Il est également possible pour le StackLayout lui-même d’avoir une dimension horizontale non contrainte, auquel cas le BoxView sera également nonconstrainé horizontalement.

L’exemple affiche un carré d’un pouce non contraint BoxView au centre de sa page :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:BasicBoxView"
             x:Class="BasicBoxView.MainPage">

    <BoxView Color="CornflowerBlue"
             CornerRadius="10"
             WidthRequest="160"
             HeightRequest="160"
             VerticalOptions="Center"
             HorizontalOptions="Center" />

</ContentPage>

Voici le résultat :

BoxView de base

Si les propriétés et les VerticalOptions propriétés sont supprimées de la BoxView balise ou Filldéfinies sur , la BoxView taille de la page devient limitée par la taille de la page et se développe pour remplir la HorizontalOptions page.

Un BoxView peut également être un enfant d’un AbsoluteLayout. Dans ce cas, l’emplacement et la taille du fichier BoxView sont définis à l’aide de la LayoutBounds propriété pouvant être liée attachée. Il AbsoluteLayout est abordé dans l’article AbsoluteLayout.

Vous verrez des exemples de tous ces cas dans les exemples de programmes qui suivent.

Rendu des décorations de texte

Vous pouvez utiliser l’option BoxView pour ajouter des décorations simples sur vos pages sous la forme de lignes horizontales et verticales. L’exemple illustre cela. Tous les visuels du programme sont définis dans le fichier MainPage.xaml , qui contient plusieurs Label éléments dans BoxView l’exemple StackLayout ci-dessous :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:TextDecoration"
             x:Class="TextDecoration.MainPage">
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness">
            <On Platform="iOS" Value="0, 20, 0, 0" />
        </OnPlatform>
    </ContentPage.Padding>

    <ContentPage.Resources>
        <ResourceDictionary>
            <Style TargetType="BoxView">
                <Setter Property="Color" Value="Black" />
            </Style>
        </ResourceDictionary>
    </ContentPage.Resources>

    <ScrollView Margin="15">
        <StackLayout>

            ···

        </StackLayout>
    </ScrollView>
</ContentPage>

Tous les balisages qui suivent sont des enfants du StackLayout. Ce balisage se compose de plusieurs types d’éléments décoratifs BoxView utilisés avec l’élément Label :

Décoration de texte

L’en-tête élégant en haut de la page est obtenu avec un AbsoluteLayout dont les enfants sont quatre BoxView éléments et un Label, qui sont tous attribués des emplacements et tailles spécifiques :

<AbsoluteLayout>
    <BoxView AbsoluteLayout.LayoutBounds="0, 10, 200, 5" />
    <BoxView AbsoluteLayout.LayoutBounds="0, 20, 200, 5" />
    <BoxView AbsoluteLayout.LayoutBounds="10, 0, 5, 65" />
    <BoxView AbsoluteLayout.LayoutBounds="20, 0, 5, 65" />
    <Label Text="Stylish Header"
           FontSize="24"
           AbsoluteLayout.LayoutBounds="30, 25, AutoSize, AutoSize"/>
</AbsoluteLayout>

Dans le fichier XAML, le AbsoluteLayout fichier est suivi d’un Label texte mis en forme qui décrit le AbsoluteLayout.

Vous pouvez souligner une chaîne de texte en plaçant à la fois le Label texte et BoxView dans un StackLayout qui a sa HorizontalOptions valeur définie sur quelque chose d’autre que Fill. La largeur de l’objet StackLayout Labelest ensuite régie par la largeur du , qui impose ensuite cette largeur sur le BoxView. La BoxView hauteur est affectée uniquement à une hauteur explicite :

<StackLayout HorizontalOptions="Center">
    <Label Text="Underlined Text"
           FontSize="24" />
    <BoxView HeightRequest="2" />
</StackLayout>

Vous ne pouvez pas utiliser cette technique pour souligner des mots individuels dans des chaînes de texte plus longues ou un paragraphe.

Il est également possible d’utiliser un BoxView élément HTML hr (règle horizontale). Laissez simplement la largeur du BoxView conteneur parent, qui, dans ce cas, est la StackLayoutsuivante :

<BoxView HeightRequest="3" />

Enfin, vous pouvez dessiner une ligne verticale d’un côté d’un paragraphe de texte en plaçant à la fois le BoxView et le Label dans une ligne horizontale StackLayout. Dans ce cas, la hauteur de la BoxView valeur est la même que la hauteur de StackLayout, qui est régie par la hauteur du Label:

<StackLayout Orientation="Horizontal">
    <BoxView WidthRequest="4"
             Margin="0, 0, 10, 0" />
    <Label>

        ···

    </Label>
</StackLayout>

Description des couleurs avec BoxView

Il BoxView est pratique pour afficher les couleurs. Ce programme utilise une ListView liste de tous les champs en lecture seule statique publique de la Xamarin.FormsColor structure :

Couleurs listView

L’exemple de programme inclut une classe nommée NamedColor. Le constructeur statique utilise la réflexion pour accéder à tous les champs de la Color structure et créer un NamedColor objet pour chacun d’eux. Celles-ci sont stockées dans la propriété statique All :

public class NamedColor
{
    // Instance members.
    private NamedColor()
    {
    }

    public string Name { private set; get; }

    public string FriendlyName { private set; get; }

    public Color Color { private set; get; }

    public string RgbDisplay { private set; get; }

    // Static members.
    static NamedColor()
    {
        List<NamedColor> all = new List<NamedColor>();
        StringBuilder stringBuilder = new StringBuilder();

        // Loop through the public static fields of the Color structure.
        foreach (FieldInfo fieldInfo in typeof(Color).GetRuntimeFields ())
        {
            if (fieldInfo.IsPublic &&
                fieldInfo.IsStatic &&
                fieldInfo.FieldType == typeof (Color))
            {
                // Convert the name to a friendly name.
                string name = fieldInfo.Name;
                stringBuilder.Clear();
                int index = 0;

                foreach (char ch in name)
                {
                    if (index != 0 && Char.IsUpper(ch))
                    {
                        stringBuilder.Append(' ');
                    }
                    stringBuilder.Append(ch);
                    index++;
                }

                // Instantiate a NamedColor object.
                Color color = (Color)fieldInfo.GetValue(null);

                NamedColor namedColor = new NamedColor
                {
                    Name = name,
                    FriendlyName = stringBuilder.ToString(),
                    Color = color,
                    RgbDisplay = String.Format("{0:X2}-{1:X2}-{2:X2}",
                                               (int)(255 * color.R),
                                               (int)(255 * color.G),
                                               (int)(255 * color.B))
                };

                // Add it to the collection.
                all.Add(namedColor);
            }
        }
        all.TrimExcess();
        All = all;
    }

    public static IList<NamedColor> All { private set; get; }
}

Les visuels de programme sont décrits dans le fichier XAML. La ItemsSource propriété du fichier ListView est définie sur la propriété statique NamedColor.All , ce qui signifie que les ListView objets individuels NamedColor sont affichés :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:ListViewColors"
             x:Class="ListViewColors.MainPage">
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness">
            <On Platform="iOS" Value="10, 20, 10, 0" />
            <On Platform="Android, UWP" Value="10, 0" />
        </OnPlatform>
    </ContentPage.Padding>

    <ListView SeparatorVisibility="None"
              ItemsSource="{x:Static local:NamedColor.All}">
        <ListView.RowHeight>
            <OnPlatform x:TypeArguments="x:Int32">
                <On Platform="iOS, Android" Value="80" />
                <On Platform="UWP" Value="90" />
            </OnPlatform>
        </ListView.RowHeight>

        <ListView.ItemTemplate>
            <DataTemplate>
                <ViewCell>
                    <ContentView Padding="5">
                        <Frame OutlineColor="Accent"
                               Padding="10">
                            <StackLayout Orientation="Horizontal">
                                <BoxView Color="{Binding Color}"
                                         WidthRequest="50"
                                         HeightRequest="50" />
                                <StackLayout>
                                    <Label Text="{Binding FriendlyName}"
                                           FontSize="22"
                                           VerticalOptions="StartAndExpand" />
                                    <Label Text="{Binding RgbDisplay, StringFormat='RGB = {0}'}"
                                           FontSize="16"
                                           VerticalOptions="CenterAndExpand" />
                                </StackLayout>
                            </StackLayout>
                        </Frame>
                    </ContentView>
                </ViewCell>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</ContentPage>

Les NamedColor objets sont mis en forme par l’objet ViewCell qui est défini comme modèle de données du ListView. Ce modèle inclut une BoxView propriété dont Color la propriété est liée à la Color propriété de l’objet NamedColor .

Jouer au jeu de la vie par sous-classe BoxView

Le Jeu de la vie est un automaton cellulaire inventé par le mathématique John Conway et popularisé dans les pages d’American scientifique dans les années 1970. Une bonne introduction est fournie par l’article Wikipédia Conway’s Game of Life.

L’exemple Xamarin.Forms de programme définit une classe nommée LifeCell qui dérive de BoxView. Cette classe encapsule la logique d’une cellule individuelle dans le jeu de la vie :

class LifeCell : BoxView
{
    bool isAlive;

    public event EventHandler Tapped;

    public LifeCell()
    {
        BackgroundColor = Color.White;

        TapGestureRecognizer tapGesture = new TapGestureRecognizer();
        tapGesture.Tapped += (sender, args) =>
        {
            Tapped?.Invoke(this, EventArgs.Empty);
        };
        GestureRecognizers.Add(tapGesture);
    }

    public int Col { set; get; }

    public int Row { set; get; }

    public bool IsAlive
    {
        set
        {
            if (isAlive != value)
            {
                isAlive = value;
                BackgroundColor = isAlive ? Color.Black : Color.White;
            }
        }
        get
        {
            return isAlive;
        }
    }
}

LifeCell ajoute trois propriétés supplémentaires à BoxView: les Col propriétés Row stockent la position de la cellule dans la grille, et la IsAlive propriété indique son état. La IsAlive propriété définit également la Color propriété du BoxView noir si la cellule est vivante et blanche si la cellule n’est pas vivante.

LifeCell installe également un TapGestureRecognizer pour permettre à l’utilisateur de basculer l’état des cellules en les appuyant. La classe convertit l’événement Tapped du module de reconnaissance de mouvement en son propre Tapped événement.

Le programme GameOfLife inclut également une LifeGrid classe qui encapsule une grande partie de la logique du jeu et une MainPage classe qui gère les visuels du programme. Il s’agit notamment d’une superposition qui décrit les règles du jeu. Voici le programme en action montrant quelques centaines LifeCell d’objets sur la page :

Jeu de la vie

Création d’une horloge numérique

L’exemple de programme crée 210 BoxView éléments pour simuler les points d’un affichage de matrice à 5 par 7 à l’ancienne. Vous pouvez lire le temps en mode portrait ou paysage, mais il est plus grand dans le paysage :

Horloge à matrice de points

Le fichier XAML ne fait que instancier l’horloge AbsoluteLayout utilisée :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DotMatrixClock"
             x:Class="DotMatrixClock.MainPage"
             Padding="10"
             SizeChanged="OnPageSizeChanged">

    <AbsoluteLayout x:Name="absoluteLayout"
                    VerticalOptions="Center" />
</ContentPage>

Tout le reste se produit dans le fichier code-behind. La logique d’affichage de matrice de points est considérablement simplifiée par la définition de plusieurs tableaux qui décrivent les points correspondant à chacun des 10 chiffres et un signe deux-points :

public partial class MainPage : ContentPage
{
    // Total dots horizontally and vertically.
    const int horzDots = 41;
    const int vertDots = 7;

    // 5 x 7 dot matrix patterns for 0 through 9.
    static readonly int[, ,] numberPatterns = new int[10, 7, 5]
    {
        {
            { 0, 1, 1, 1, 0}, { 1, 0, 0, 0, 1}, { 1, 0, 0, 1, 1}, { 1, 0, 1, 0, 1},
            { 1, 1, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 0}
        },
        {
            { 0, 0, 1, 0, 0}, { 0, 1, 1, 0, 0}, { 0, 0, 1, 0, 0}, { 0, 0, 1, 0, 0},
            { 0, 0, 1, 0, 0}, { 0, 0, 1, 0, 0}, { 0, 1, 1, 1, 0}
        },
        {
            { 0, 1, 1, 1, 0}, { 1, 0, 0, 0, 1}, { 0, 0, 0, 0, 1}, { 0, 0, 0, 1, 0},
            { 0, 0, 1, 0, 0}, { 0, 1, 0, 0, 0}, { 1, 1, 1, 1, 1}
        },
        {
            { 1, 1, 1, 1, 1}, { 0, 0, 0, 1, 0}, { 0, 0, 1, 0, 0}, { 0, 0, 0, 1, 0},
            { 0, 0, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 0}
        },
        {
            { 0, 0, 0, 1, 0}, { 0, 0, 1, 1, 0}, { 0, 1, 0, 1, 0}, { 1, 0, 0, 1, 0},
            { 1, 1, 1, 1, 1}, { 0, 0, 0, 1, 0}, { 0, 0, 0, 1, 0}
        },
        {
            { 1, 1, 1, 1, 1}, { 1, 0, 0, 0, 0}, { 1, 1, 1, 1, 0}, { 0, 0, 0, 0, 1},
            { 0, 0, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 0}
        },
        {
            { 0, 0, 1, 1, 0}, { 0, 1, 0, 0, 0}, { 1, 0, 0, 0, 0}, { 1, 1, 1, 1, 0},
            { 1, 0, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 0}
        },
        {
            { 1, 1, 1, 1, 1}, { 0, 0, 0, 0, 1}, { 0, 0, 0, 1, 0}, { 0, 0, 1, 0, 0},
            { 0, 1, 0, 0, 0}, { 0, 1, 0, 0, 0}, { 0, 1, 0, 0, 0}
        },
        {
            { 0, 1, 1, 1, 0}, { 1, 0, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 0},
            { 1, 0, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 0}
        },
        {
            { 0, 1, 1, 1, 0}, { 1, 0, 0, 0, 1}, { 1, 0, 0, 0, 1}, { 0, 1, 1, 1, 1},
            { 0, 0, 0, 0, 1}, { 0, 0, 0, 1, 0}, { 0, 1, 1, 0, 0}
        },
    };

    // Dot matrix pattern for a colon.
    static readonly int[,] colonPattern = new int[7, 2]
    {
        { 0, 0 }, { 1, 1 }, { 1, 1 }, { 0, 0 }, { 1, 1 }, { 1, 1 }, { 0, 0 }
    };

    // BoxView colors for on and off.
    static readonly Color colorOn = Color.Red;
    static readonly Color colorOff = new Color(0.5, 0.5, 0.5, 0.25);

    // Box views for 6 digits, 7 rows, 5 columns.
    BoxView[, ,] digitBoxViews = new BoxView[6, 7, 5];

    ···

}

Ces champs se terminent par un tableau tridimensionnel d’éléments BoxView pour stocker les modèles de points pour les six chiffres.

Le constructeur crée tous les éléments pour les BoxView chiffres et les deux-points, et initialise également la Color propriété des éléments pour le BoxView signe deux-points :

public partial class MainPage : ContentPage
{

    ···

    public MainPage()
    {
        InitializeComponent();

        // BoxView dot dimensions.
        double height = 0.85 / vertDots;
        double width = 0.85 / horzDots;

        // Create and assemble the BoxViews.
        double xIncrement = 1.0 / (horzDots - 1);
        double yIncrement = 1.0 / (vertDots - 1);
        double x = 0;

        for (int digit = 0; digit < 6; digit++)
        {
            for (int col = 0; col < 5; col++)
            {
                double y = 0;

                for (int row = 0; row < 7; row++)
                {
                    // Create the digit BoxView and add to layout.
                    BoxView boxView = new BoxView();
                    digitBoxViews[digit, row, col] = boxView;
                    absoluteLayout.Children.Add(boxView,
                                                new Rectangle(x, y, width, height),
                                                AbsoluteLayoutFlags.All);
                    y += yIncrement;
                }
                x += xIncrement;
            }
            x += xIncrement;

            // Colons between the hours, minutes, and seconds.
            if (digit == 1 || digit == 3)
            {
                int colon = digit / 2;

                for (int col = 0; col < 2; col++)
                {
                    double y = 0;

                    for (int row = 0; row < 7; row++)
                    {
                        // Create the BoxView and set the color.
                        BoxView boxView = new BoxView
                            {
                                Color = colonPattern[row, col] == 1 ?
                                            colorOn : colorOff
                            };
                        absoluteLayout.Children.Add(boxView,
                                                    new Rectangle(x, y, width, height),
                                                    AbsoluteLayoutFlags.All);
                        y += yIncrement;
                    }
                    x += xIncrement;
                }
                x += xIncrement;
            }
        }

        // Set the timer and initialize with a manual call.
        Device.StartTimer(TimeSpan.FromSeconds(1), OnTimer);
        OnTimer();
    }

    ···

}

Ce programme utilise la fonctionnalité relative de positionnement et de dimensionnement de AbsoluteLayout. La largeur et la hauteur de chacun BoxView sont définies sur des valeurs fractionnaires, en particulier 85 % de 1 divisé par le nombre de points horizontaux et verticaux. Les positions sont également définies sur des valeurs fractionnaires.

Étant donné que toutes les positions et tailles sont relatives à la taille totale du , le SizeChanged gestionnaire de AbsoluteLayoutla page n’a besoin que d’un HeightRequest ensemble de :AbsoluteLayout

public partial class MainPage : ContentPage
{

    ···

    void OnPageSizeChanged(object sender, EventArgs args)
    {
        // No chance a display will have an aspect ratio > 41:7
        absoluteLayout.HeightRequest = vertDots * Width / horzDots;
    }

    ···

}

La largeur du fichier AbsoluteLayout est automatiquement définie, car elle s’étend sur la largeur totale de la page.

Le code final de la MainPage classe traite le rappel du minuteur et colore les points de chaque chiffre. La définition des tableaux multidimensionnels au début du fichier code-behind permet de rendre cette logique la partie la plus simple du programme :

public partial class MainPage : ContentPage
{

    ···

    bool OnTimer()
    {
        DateTime dateTime = DateTime.Now;

        // Convert 24-hour clock to 12-hour clock.
        int hour = (dateTime.Hour + 11) % 12 + 1;

        // Set the dot colors for each digit separately.
        SetDotMatrix(0, hour / 10);
        SetDotMatrix(1, hour % 10);
        SetDotMatrix(2, dateTime.Minute / 10);
        SetDotMatrix(3, dateTime.Minute % 10);
        SetDotMatrix(4, dateTime.Second / 10);
        SetDotMatrix(5, dateTime.Second % 10);
        return true;
    }

    void SetDotMatrix(int index, int digit)
    {
        for (int row = 0; row < 7; row++)
            for (int col = 0; col < 5; col++)
            {
                bool isOn = numberPatterns[digit, row, col] == 1;
                Color color = isOn ? colorOn : colorOff;
                digitBoxViews[index, row, col].Color = color;
            }
    }
}

Création d’une horloge analogique

Une horloge à matrice de points peut sembler être une application évidente de BoxView, mais BoxView les éléments sont également capables de réaliser une horloge analogique :

Horloge BoxView

Tous les visuels de l’exemple de programme sont des enfants d’un AbsoluteLayout. Ces éléments sont dimensionnés à l’aide de la LayoutBounds propriété jointe et pivotés à l’aide de la Rotation propriété.

Les trois BoxView éléments des mains de l’horloge sont instanciés dans le fichier XAML, mais pas positionnés ou dimensionnés :

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:BoxViewClock"
             x:Class="BoxViewClock.MainPage">
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness">
            <On Platform="iOS" Value="0, 20, 0, 0" />
        </OnPlatform>
    </ContentPage.Padding>

    <AbsoluteLayout x:Name="absoluteLayout"
                    SizeChanged="OnAbsoluteLayoutSizeChanged">

        <BoxView x:Name="hourHand"
                 Color="Black" />

        <BoxView x:Name="minuteHand"
                 Color="Black" />

        <BoxView x:Name="secondHand"
                 Color="Black" />
    </AbsoluteLayout>
</ContentPage>

Le constructeur du fichier code-behind instancie les 60 BoxView éléments pour les graduations autour de la circonférence de l’horloge :

public partial class MainPage : ContentPage
{

    ···

    BoxView[] tickMarks = new BoxView[60];

    public MainPage()
    {
        InitializeComponent();

        // Create the tick marks (to be sized and positioned later).
        for (int i = 0; i < tickMarks.Length; i++)
        {
            tickMarks[i] = new BoxView { Color = Color.Black };
            absoluteLayout.Children.Add(tickMarks[i]);
        }

        Device.StartTimer(TimeSpan.FromSeconds(1.0 / 60), OnTimerTick);
    }

    ···

}

Le dimensionnement et le positionnement de tous les BoxView éléments se produisent dans le SizeChanged gestionnaire pour le AbsoluteLayout. Une petite structure interne à la classe appelée HandParams décrit la taille de chacune des trois mains par rapport à la taille totale de l’horloge :

public partial class MainPage : ContentPage
{
    // Structure for storing information about the three hands.
    struct HandParams
    {
        public HandParams(double width, double height, double offset) : this()
        {
            Width = width;
            Height = height;
            Offset = offset;
        }

        public double Width { private set; get; }   // fraction of radius
        public double Height { private set; get; }  // ditto
        public double Offset { private set; get; }  // relative to center pivot
    }

    static readonly HandParams secondParams = new HandParams(0.02, 1.1, 0.85);
    static readonly HandParams minuteParams = new HandParams(0.05, 0.8, 0.9);
    static readonly HandParams hourParams = new HandParams(0.125, 0.65, 0.9);

    ···

 }

Le SizeChanged gestionnaire détermine le centre et le rayon du AbsoluteLayout, puis dimensionne et positionne les 60 BoxView éléments utilisés comme graduations. La for boucle se termine par la définition de la Rotation propriété de chacun de ces BoxView éléments. À la fin du SizeChanged gestionnaire, la LayoutHand méthode est appelée pour dimensionner et positionner les trois mains de l’horloge :

public partial class MainPage : ContentPage
{

    ···

    void OnAbsoluteLayoutSizeChanged(object sender, EventArgs args)
    {
        // Get the center and radius of the AbsoluteLayout.
        Point center = new Point(absoluteLayout.Width / 2, absoluteLayout.Height / 2);
        double radius = 0.45 * Math.Min(absoluteLayout.Width, absoluteLayout.Height);

        // Position, size, and rotate the 60 tick marks.
        for (int index = 0; index < tickMarks.Length; index++)
        {
            double size = radius / (index % 5 == 0 ? 15 : 30);
            double radians = index * 2 * Math.PI / tickMarks.Length;
            double x = center.X + radius * Math.Sin(radians) - size / 2;
            double y = center.Y - radius * Math.Cos(radians) - size / 2;
            AbsoluteLayout.SetLayoutBounds(tickMarks[index], new Rectangle(x, y, size, size));
            tickMarks[index].Rotation = 180 * radians / Math.PI;
        }

        // Position and size the three hands.
        LayoutHand(secondHand, secondParams, center, radius);
        LayoutHand(minuteHand, minuteParams, center, radius);
        LayoutHand(hourHand, hourParams, center, radius);
    }

    void LayoutHand(BoxView boxView, HandParams handParams, Point center, double radius)
    {
        double width = handParams.Width * radius;
        double height = handParams.Height * radius;
        double offset = handParams.Offset;

        AbsoluteLayout.SetLayoutBounds(boxView,
            new Rectangle(center.X - 0.5 * width,
                          center.Y - offset * height,
                          width, height));

        // Set the AnchorY property for rotations.
        boxView.AnchorY = handParams.Offset;
    }

    ···

}

La LayoutHand taille et la position de chaque main pour pointer directement jusqu’à la position 12:00. À la fin de la méthode, la AnchorY propriété est définie sur une position correspondant au centre de l’horloge. Cela indique le centre de rotation.

Les mains sont pivotées dans la fonction de rappel du minuteur :

public partial class MainPage : ContentPage
{

    ···

    bool OnTimerTick()
    {
        // Set rotation angles for hour and minute hands.
        DateTime dateTime = DateTime.Now;
        hourHand.Rotation = 30 * (dateTime.Hour % 12) + 0.5 * dateTime.Minute;
        minuteHand.Rotation = 6 * dateTime.Minute + 0.1 * dateTime.Second;

        // Do an animation for the second hand.
        double t = dateTime.Millisecond / 1000.0;

        if (t < 0.5)
        {
            t = 0.5 * Easing.SpringIn.Ease(t / 0.5);
        }
        else
        {
            t = 0.5 * (1 + Easing.SpringOut.Ease((t - 0.5) / 0.5));
        }

        secondHand.Rotation = 6 * (dateTime.Second + t);
        return true;
    }
}

La seconde main est traitée un peu différemment : une fonction d’accélération de l’animation est appliquée pour rendre le mouvement semble mécanique plutôt que lisse. Sur chaque tique, la seconde main tire un peu, puis dépasse sa destination. Ce petit peu de code ajoute beaucoup au réalisme du mouvement.