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’appareilBoxView
.HeightRequest
pour définir la hauteur duBoxView
.
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 :
Si les propriétés et les VerticalOptions
propriétés sont supprimées de la BoxView
balise ou Fill
dé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
:
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
Label
est 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 StackLayout
suivante :
<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 :
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 :
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 :
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 AbsoluteLayout
la 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 :
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.