Xamarin.Forms BoxView
BoxView
esegue il rendering di un semplice rettangolo di larghezza, altezza e colore specificati. È possibile usare BoxView
per decorazione, grafica rudimentale e per l'interazione con l'utente tramite tocco.
Poiché Xamarin.Forms non dispone di un sistema di grafica vettoriale predefinito, aiuta BoxView
a compensare. Alcuni dei programmi di esempio descritti in questo articolo usano BoxView
per il rendering della grafica. Può BoxView
essere ridimensionato in modo da assomigliare a una linea di una larghezza e spessore specifici e quindi ruotata da qualsiasi angolo utilizzando la Rotation
proprietà .
Anche se BoxView
può simulare grafica semplice, è consigliabile esaminare l'uso di SkiaSharp in Xamarin.Forms per requisiti grafici più sofisticati.
Impostazione del colore e delle dimensioni di BoxView
In genere si impostano le proprietà seguenti di BoxView
:
Color
per impostarne il colore.CornerRadius
per impostare il raggio dell'angolo.WidthRequest
per impostare la larghezza dell'oggettoBoxView
in unità indipendenti dal dispositivo.HeightRequest
per impostare l'altezza dell'oggettoBoxView
.
La proprietà è di tipo Color
. La Color
proprietà può essere impostata su qualsiasi Color
valore, inclusi i campi statici di sola lettura 141 di colori denominati che vanno alfabeticamente da AliceBlue
a YellowGreen
.
La proprietà è di tipo CornerRadius
. La CornerRadius
proprietà può essere impostata su un singolo double
valore del raggio dell'angolo uniforme oppure su una CornerRadius
struttura definita da quattro double
valori applicati all'angolo superiore sinistro, in alto a destra, in basso a sinistra e in basso a destra dell'oggetto BoxView
.
Le WidthRequest
proprietà e HeightRequest
svolgono un ruolo solo se l'oggetto BoxView
non è vincolato nel layout. Questo è il caso in cui il contenitore di layout deve conoscere le dimensioni dell'elemento figlio, ad esempio quando BoxView
è figlio di una cella ridimensionata automaticamente nel Grid
layout. Un BoxView
oggetto è anche non vincolato quando HorizontalOptions
le relative proprietà e VerticalOptions
sono impostate su valori diversi da LayoutOptions.Fill
. Se l'oggetto BoxView
non è vincolato, ma le WidthRequest
proprietà e HeightRequest
non sono impostate, la larghezza o l'altezza vengono impostate sui valori predefiniti di 40 unità o circa 1/4 pollici nei dispositivi mobili.
Le WidthRequest
proprietà e HeightRequest
vengono ignorate se l'oggetto BoxView
è vincolato nel layout, nel qual caso il contenitore di layout impone le proprie dimensioni per .BoxView
Un BoxView
oggetto può essere vincolato in una dimensione e non vincolato nell'altro. Ad esempio, se BoxView
è un elemento figlio di un oggetto verticale, la dimensione verticale StackLayout
di BoxView
è non vincolata e la dimensione orizzontale è generalmente vincolata. Esistono tuttavia eccezioni per la dimensione orizzontale: se la BoxView
proprietà HorizontalOptions
è impostata su un valore diverso da LayoutOptions.Fill
, la dimensione orizzontale non è vincolata. È anche possibile che l'oggetto StackLayout
stesso abbia una dimensione orizzontale non vincolata, nel qual caso anche l'oggetto BoxView
sarà orizzontalmente non vincolato.
Nell'esempio viene visualizzato un vincolo quadrato BoxView
di un pollice al centro della pagina:
<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>
Il risultato è il seguente:
Se le VerticalOptions
proprietà e HorizontalOptions
vengono rimosse dal BoxView
tag o sono impostate su Fill
, l'oggetto BoxView
diventa vincolato dalle dimensioni della pagina e si espande per riempire la pagina.
Un BoxView
può anche essere un elemento figlio di un oggetto AbsoluteLayout
. In tal caso, sia la posizione che le dimensioni di BoxView
vengono impostate usando la LayoutBounds
proprietà associabile associata. L'oggetto AbsoluteLayout
è illustrato nell'articolo AbsoluteLayout.
Verranno visualizzati esempi di tutti questi casi nei programmi di esempio che seguono.
Rendering delle decorazioni di testo
È possibile utilizzare per BoxView
aggiungere alcune decorazioni semplici sulle pagine sotto forma di linee orizzontali e verticali. L'esempio illustra questa operazione. Tutti gli oggetti visivi del programma sono definiti nel file MainPage.xaml , che contiene diversi Label
elementi e BoxView
nell'oggetto StackLayout
illustrato di seguito:
<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>
Tutto il markup che segue è figlio di StackLayout
. Questo markup è costituito da diversi tipi di elementi decorativi BoxView
usati con l'elemento Label
:
L'intestazione elegante nella parte superiore della pagina viene ottenuta con un i AbsoluteLayout
cui figli sono quattro BoxView
elementi e un , Label
tutti assegnati posizioni e dimensioni specifiche:
<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>
Nel file XAML l'oggetto AbsoluteLayout
è seguito da un Label
oggetto con testo formattato che descrive l'oggetto AbsoluteLayout
.
È possibile sottolineare una stringa di testo racchiudendo sia l'oggetto Label
BoxView
che in un StackLayout
oggetto con il relativo HorizontalOptions
valore impostato su un valore diverso da Fill
. La larghezza di StackLayout
viene quindi governata dalla larghezza di Label
, che quindi impone tale larghezza su BoxView
. L'oggetto BoxView
viene assegnato solo un'altezza esplicita:
<StackLayout HorizontalOptions="Center">
<Label Text="Underlined Text"
FontSize="24" />
<BoxView HeightRequest="2" />
</StackLayout>
Non è possibile usare questa tecnica per sottolineare singole parole all'interno di stringhe di testo più lunghe o di un paragrafo.
È anche possibile usare un BoxView
oggetto per assomigliare a un elemento HTML hr
(regola orizzontale). È sufficiente lasciare che la larghezza dell'oggetto BoxView
venga determinata dal contenitore padre, che in questo caso è :StackLayout
<BoxView HeightRequest="3" />
Infine, è possibile disegnare una linea verticale su un lato di un paragrafo di testo racchiudendo sia e BoxView
Label
in un oggetto orizzontale StackLayout
. In questo caso, l'altezza di BoxView
è uguale all'altezza di StackLayout
, che è governata dall'altezza di Label
:
<StackLayout Orientation="Horizontal">
<BoxView WidthRequest="4"
Margin="0, 0, 10, 0" />
<Label>
···
</Label>
</StackLayout>
Elenco di colori con BoxView
è BoxView
utile per visualizzare i colori. Questo programma usa un ListView
oggetto per elencare tutti i campi di sola lettura statici pubblici della Xamarin.FormsColor
struttura:
Il programma di esempio include una classe denominata NamedColor
. Il costruttore statico usa la reflection per accedere a tutti i campi della Color
struttura e creare un NamedColor
oggetto per ognuno di essi. Queste vengono archiviate nella proprietà statica 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; }
}
Gli oggetti visivi del programma sono descritti nel file XAML. La ItemsSource
proprietà di ListView
è impostata sulla proprietà statica NamedColor.All
, il che significa che ListView
visualizza tutti i singoli NamedColor
oggetti:
<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>
Gli NamedColor
oggetti vengono formattati dall'oggetto ViewCell
impostato come modello di dati dell'oggetto ListView
. Questo modello include un oggetto BoxView
la Color
cui Color
proprietà è associata alla proprietà dell'oggetto NamedColor
.
Riproduzione del gioco della vita sottoclassando BoxView
The Game of Life è un automatone cellulare inventato dal matematico John Conway e popolare nelle pagine di Scientific American negli anni '70. Una buona introduzione è fornita dall'articolo di Wikipedia Game of Life di Conway.
Il Xamarin.Forms programma di esempio definisce una classe denominata LifeCell
che deriva da BoxView
. Questa classe incapsula la logica di una singola cella nel Game of Life:
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
aggiunge altre tre proprietà a BoxView
: le Col
proprietà e Row
archiviano la posizione della cella all'interno della griglia e la proprietà ne indica lo IsAlive
stato. La IsAlive
proprietà imposta inoltre la Color
proprietà dell'oggetto BoxView
su nero se la cella è attiva e bianca se la cella non è attiva.
LifeCell
installa anche un oggetto TapGestureRecognizer
per consentire all'utente di attivare o disattivare lo stato delle celle toccandole. La classe converte l'evento Tapped
dal riconoscimento movimento nel proprio Tapped
evento.
Il programma GameOfLife include anche una LifeGrid
classe che incapsula gran parte della logica del gioco e una MainPage
classe che gestisce gli oggetti visivi del programma. Questi includono una sovrimpressione che descrive le regole del gioco. Ecco il programma in azione che mostra un paio di centinaia LifeCell
di oggetti nella pagina:
Creazione di un orologio digitale
Il programma di esempio crea 210 BoxView
elementi per simulare i punti di una visualizzazione a matrice di 5-by-7 vecchio stile. È possibile leggere l'ora in modalità verticale o orizzontale, ma è più grande nel panorama:
Il file XAML esegue poco più di un'istanza dell'oggetto AbsoluteLayout
usato per l'orologio:
<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>
Tutto il resto si verifica nel file code-behind. La logica di visualizzazione della matrice di punti è notevolmente semplificata dalla definizione di diverse matrici che descrivono i punti corrispondenti a ognuna delle 10 cifre e due punti:
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];
···
}
Questi campi si concludono con una matrice tridimensionale di BoxView
elementi per archiviare i modelli di punti per le sei cifre.
Il costruttore crea tutti gli BoxView
elementi per le cifre e i due punti e inizializza anche la Color
proprietà degli elementi per i BoxView
due punti:
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();
}
···
}
Questo programma usa la funzionalità relativa di posizionamento e ridimensionamento di AbsoluteLayout
. La larghezza e l'altezza di ogni BoxView
oggetto vengono impostate su valori frazionari, in particolare l'85% di 1 diviso per il numero di punti orizzontali e verticali. Le posizioni sono impostate anche su valori frazionari.
Poiché tutte le posizioni e le dimensioni sono relative alle dimensioni totali di AbsoluteLayout
, il SizeChanged
gestore per la pagina deve impostare solo un HeightRequest
oggetto di 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 larghezza di AbsoluteLayout
viene impostata automaticamente perché si estende fino alla larghezza intera della pagina.
Il codice finale nella MainPage
classe elabora il callback timer e colora i punti di ogni cifra. La definizione delle matrici multidimensionali all'inizio del file code-behind consente di rendere questa logica la parte più semplice del programma:
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;
}
}
}
Creazione di un orologio analogico
Un orologio a matrice di punti potrebbe sembrare un'applicazione ovvia di BoxView
, ma BoxView
gli elementi sono anche in grado di realizzare un orologio analogico:
Tutti gli oggetti visivi nel programma di esempio sono figli di un oggetto AbsoluteLayout
. Questi elementi vengono ridimensionati utilizzando la LayoutBounds
proprietà associata e ruotati usando la Rotation
proprietà .
I tre BoxView
elementi per le mani dell'orologio vengono creati un'istanza nel file XAML, ma non posizionati o ridimensionati:
<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>
Il costruttore del file code-behind crea un'istanza degli elementi 60 BoxView
per i segni di graduazione intorno alla circonferenza dell'orologio:
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);
}
···
}
Il ridimensionamento e il posizionamento di tutti gli BoxView
elementi si verifica nel SizeChanged
gestore per .AbsoluteLayout
Una piccola struttura interna alla classe denominata HandParams
descrive le dimensioni di ognuna delle tre mani rispetto alle dimensioni totali dell'orologio:
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);
···
}
Il SizeChanged
gestore determina il centro e il raggio dell'oggetto AbsoluteLayout
e quindi ridimensiona e posiziona i 60 BoxView
elementi usati come segni di graduazione. Il for
ciclo termina impostando la Rotation
proprietà di ognuno di questi BoxView
elementi. Alla fine del SizeChanged
gestore, il LayoutHand
metodo viene chiamato per ridimensionare e posizionare le tre mani dell'orologio:
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;
}
···
}
Il LayoutHand
metodo ridimensiona e posiziona ogni mano in modo che punti direttamente alla posizione 12:00. Alla fine del metodo, la AnchorY
proprietà viene impostata su una posizione corrispondente al centro dell'orologio. Indica il centro di rotazione.
Le mani vengono ruotate nella funzione di callback timer:
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 seconda mano viene trattata in modo leggermente diverso: una funzione di interpolazione di animazione viene applicata per rendere il movimento sembra meccanico piuttosto che liscia. Su ogni graduazione, la seconda mano tira indietro un po 'e poi supera la destinazione. Questo piccolo frammento di codice aggiunge molto al realismo del movimento.