Xamarin.Forms BoxView
BoxView
Renderuje prosty prostokąt o określonej szerokości, wysokości i kolorze. Można użyć BoxView
do dekoracji, rudimentarnej grafiki i interakcji z użytkownikiem za pośrednictwem dotyku.
Ponieważ Xamarin.Forms nie ma wbudowanego systemu grafiki wektorowej, BoxView
pomaga zrekompensować. Niektóre przykładowe programy opisane w tym artykule są używane BoxView
do renderowania grafiki. Rozmiar BoxView
może przypominać linię o określonej szerokości i grubości, a następnie obracany według dowolnego kąta Rotation
przy użyciu właściwości .
Chociaż BoxView
można naśladować prostą grafikę, warto zbadać użycie biblioteki SkiaSharp w celu Xamarin.Forms uzyskania bardziej zaawansowanych wymagań graficznych.
Ustawianie koloru i rozmiaru elementu BoxView
Zazwyczaj należy ustawić następujące właściwości :BoxView
Color
aby ustawić jego kolor.CornerRadius
aby ustawić promień rogu.WidthRequest
aby ustawić szerokośćBoxView
elementu w jednostkach niezależnych od urządzenia.HeightRequest
aby ustawić wysokość obiektuBoxView
.
Właściwość Color
jest typu Color
; właściwość można ustawić na dowolną Color
wartość, w tym 141 statycznych pól tylko do odczytu nazwanych kolorów, począwszy od alfabetycznie od AliceBlue
do YellowGreen
.
Właściwość CornerRadius
jest typu CornerRadius
; właściwość można ustawić na pojedynczą double
jednolitą wartość promienia rogu lub strukturę zdefiniowaną CornerRadius
przez cztery double
wartości, które są stosowane do lewej górnej, prawej górnej, dolnej lewej i dolnej prawej strony BoxView
.
Właściwości WidthRequest
i HeightRequest
odgrywają rolę tylko wtedy, gdy BoxView
element nie jest ograniczony w układzie. Dzieje się tak, gdy kontener układu musi znać rozmiar elementu podrzędnego, na przykład gdy BoxView
element podrzędny jest elementem podrzędnym komórki o automatycznym rozmiarze Grid
w układzie. Element A BoxView
jest również nieprzyciągnięty, gdy jego HorizontalOptions
właściwości i VerticalOptions
są ustawione na wartości inne niż LayoutOptions.Fill
. Jeśli właściwość BoxView
jest niezwiązana, ale WidthRequest
właściwości i HeightRequest
nie są ustawione, szerokość lub wysokość są ustawione na wartości domyślne 40 jednostek lub około 1/4 cala na urządzeniach przenośnych.
Właściwości WidthRequest
i HeightRequest
są ignorowane, jeśli BoxView
element jest ograniczony w układzie, w tym przypadku kontener układu nakłada własny rozmiar na BoxView
.
Element BoxView
może być ograniczony w jednym wymiarze i nieskrępowany w drugim. Jeśli na przykład BoxView
element jest elementem podrzędnym w pionie StackLayout
, pionowy wymiar obiektu BoxView
jest nieskrępowany, a jego wymiar poziomy jest zwykle ograniczony. Istnieją jednak wyjątki dla tego wymiaru poziomego: jeśli BoxView
właściwość ma ustawioną HorizontalOptions
właściwość na inną niż LayoutOptions.Fill
, wymiar poziomy również nie jest ograniczony. Możliwe jest również, aby sam miał StackLayout
niekonseksowany wymiar poziomy, w tym przypadku BoxView
również będzie on poziomo nieprzyciągnięty.
W przykładzie pokazano jeden-calowy kwadrat bez ograniczeń BoxView
w środku strony:
<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>
Oto wynik:
VerticalOptions
Jeśli właściwości i HorizontalOptions
zostaną usunięte z tagu BoxView
lub są ustawione na Fill
, BoxView
właściwość staje się ograniczona przez rozmiar strony i rozwija się w celu wypełnienia strony.
Element BoxView
może być również elementem podrzędnym elementu AbsoluteLayout
. W takim przypadku zarówno lokalizacja, jak i rozmiar BoxView
obiektu są ustawiane przy użyciu dołączonej właściwości możliwej LayoutBounds
do powiązania. Element AbsoluteLayout
jest omówiony w artykule AbsoluteLayout.
Zobaczysz przykłady wszystkich tych przypadków w poniższych przykładowych programach.
Renderowanie dekoracji tekstu
Możesz użyć polecenia BoxView
, aby dodać kilka prostych dekoracji na stronach w postaci linii poziomych i pionowych. W przykładzie pokazano to. Wszystkie wizualizacje programu są zdefiniowane w pliku MainPage.xaml , który zawiera kilka Label
elementów i BoxView
w pokazanym StackLayout
tutaj:
<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>
Wszystkie poniższe znaczniki to elementy podrzędne .StackLayout
Ten znacznik składa się z kilku typów elementów dekoracyjnych BoxView
używanych z elementem Label
:
Stylowy nagłówek w górnej części strony jest osiągany z elementami AbsoluteLayout
, których elementy podrzędne są czterema BoxView
elementami i Label
, z których wszystkie mają przypisane określone lokalizacje i rozmiary:
<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>
W pliku XAML następuje AbsoluteLayout
Label
ciąg z sformatowanym tekstem opisujący element AbsoluteLayout
.
Możesz podkreślić ciąg tekstowy, ujętą zarówno element , jak i BoxView
w obiekcieStackLayout
, który ma jego HorizontalOptions
wartość ustawioną na inną wartość niż Fill
.Label
Szerokość StackLayout
obiektu jest następnie określana przez szerokość Label
obiektu , która następnie nakłada na wartość BoxView
. Obiekt BoxView
ma przypisaną tylko jawną wysokość:
<StackLayout HorizontalOptions="Center">
<Label Text="Underlined Text"
FontSize="24" />
<BoxView HeightRequest="2" />
</StackLayout>
Nie można użyć tej techniki, aby podkreślić poszczególne wyrazy w dłuższych ciągach tekstowych lub akapicie.
Można również użyć elementu BoxView
, aby przypominać element HTML hr
(reguła pozioma). Po prostu pozwól, aby szerokość BoxView
obiektu została określona przez jego kontener nadrzędny, w tym przypadku jest to StackLayout
:
<BoxView HeightRequest="3" />
Na koniec możesz narysować pionową linię po jednej stronie akapitu tekstu, ujętą zarówno element , jak BoxView
i Label
w poziomy StackLayout
. W takim przypadku wysokość BoxView
obiektu jest taka sama jak wysokość StackLayout
obiektu , która jest określana przez wysokość Label
obiektu :
<StackLayout Orientation="Horizontal">
<BoxView WidthRequest="4"
Margin="0, 0, 10, 0" />
<Label>
···
</Label>
</StackLayout>
Wyświetlanie listy kolorów za pomocą kontrolki BoxView
Jest BoxView
wygodny w wyświetlaniu kolorów. Ten program używa elementu , ListView
aby wyświetlić listę wszystkich publicznych pól Xamarin.FormsColor
statycznych tylko do odczytu struktury:
Przykładowy program zawiera klasę o nazwie NamedColor
. Konstruktor statyczny używa odbicia, aby uzyskać dostęp do wszystkich pól Color
struktury i utworzyć NamedColor
obiekt dla każdego z nich. Są one przechowywane we właściwości statycznej 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; }
}
Wizualizacje programu są opisane w pliku XAML. Właściwość ItemsSource
ListView
obiektu jest ustawiona na właściwość statyczną NamedColor.All
, co oznacza, że ListView
wyświetla wszystkie poszczególne NamedColor
obiekty:
<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>
Obiekty NamedColor
są formatowane przez ViewCell
obiekt, który jest ustawiany jako szablon danych obiektu ListView
. Ten szablon zawiera BoxView
właściwość, której Color
właściwość jest powiązana z właściwością Color
NamedColor
obiektu.
Gra w grę życia według podklasy BoxView
Game of Life to automat komórkowy wymyślony przez matematyka Johna Conwaya i spopularyzowany na stronach Scientific American w 1970 roku. Dobre wprowadzenie jest dostarczane przez artykuł Wikipedii Conway's Game of Life.
Przykładowy Xamarin.Forms program definiuje klasę o nazwie LifeCell
, która pochodzi z BoxView
klasy . Ta klasa hermetyzuje logikę pojedynczej komórki w grze życia:
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
Dodaje trzy kolejne właściwości do BoxView
: Col
właściwości i Row
przechowują położenie komórki w siatce, a IsAlive
właściwość wskazuje jego stan. Właściwość IsAlive
ustawia Color
również właściwość BoxView
na, jeśli komórka jest aktywna, i biały, jeśli komórka nie jest aktywna.
LifeCell
Program instaluje również element , TapGestureRecognizer
aby umożliwić użytkownikowi przełączenie stanu komórek przez ich naciśnięcie. Klasa tłumaczy Tapped
zdarzenie z rozpoznawania gestów na własne Tapped
zdarzenie.
Program GameOfLife zawiera również klasę LifeGrid
, która hermetyzuje znaczną część logiki gry i klasę MainPage
, która obsługuje wizualizacje programu. Obejmują one nakładkę, która opisuje reguły gry. Oto program w akcji pokazujący kilkaset LifeCell
obiektów na stronie:
Tworzenie zegara cyfrowego
Przykładowy program tworzy 210 BoxView
elementów, aby symulować kropki staroświeckiego wyświetlacza macierzy 5-do-7-krotnej. Czas można odczytać w trybie pionowym lub poziomym, ale jest większy w poziomie:
Plik XAML wykonuje niewiele więcej niż utworzenie wystąpienia AbsoluteLayout
używanego dla zegara:
<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>
Wszystkie inne elementy występują w pliku za pomocą kodu. Logika wyświetlania kropki macierzy jest znacznie uproszczona przez definicję kilku tablic, które opisują kropki odpowiadające każdemu z 10 cyfr i dwukropka:
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];
···
}
Te pola kończą się trójwymiarową tablicą BoxView
elementów do przechowywania wzorców kropki dla sześciu cyfr.
Konstruktor tworzy wszystkie BoxView
elementy cyfr i dwukropka, a także inicjuje Color
właściwość BoxView
elementów dwukropka:
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();
}
···
}
Ten program korzysta z funkcji pozycjonowania względnego i określania rozmiaru elementu AbsoluteLayout
. Szerokość i wysokość każdego z nich BoxView
są ustawione na wartości ułamkowe, a w szczególności 85% z 1 podzielone przez liczbę kropek poziomych i pionowych. Pozycje są również ustawione na wartości ułamkowe.
Ponieważ wszystkie pozycje i rozmiary są względne względem całkowitego rozmiaru AbsoluteLayout
strony, SizeChanged
program obsługi dla strony musi ustawić tylko element HeightRequest
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;
}
···
}
Szerokość AbsoluteLayout
obiektu jest ustawiana automatycznie, ponieważ rozciąga się na pełną szerokość strony.
Końcowy kod w MainPage
klasie przetwarza wywołanie zwrotne czasomierza i koloruje kropki każdej cyfry. Definicja tablic wielowymiarowych na początku pliku kodu pomaga uczynić tę logikę najprostszą częścią programu:
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;
}
}
}
Tworzenie zegara analogowego
Zegar dot-matrix może wydawać się oczywistym zastosowaniem BoxView
, ale BoxView
elementy są również w stanie zrealizować zegar analogowy:
Wszystkie wizualizacje w przykładowym programie są elementami podrzędnymi elementu AbsoluteLayout
. Te elementy mają rozmiar przy użyciu dołączonej LayoutBounds
właściwości i są obracane przy użyciu Rotation
właściwości .
Trzy BoxView
elementy dla rąk zegara są tworzone wystąpienia w pliku XAML, ale nie są ustawione ani wielkości:
<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>
Konstruktor pliku stojącego za kodem tworzy wystąpienie 60 BoxView
elementów znaczników wokół obwodu zegara:
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);
}
···
}
Ustalanie rozmiaru i pozycjonowanie wszystkich BoxView
elementów odbywa się w procedurze SizeChanged
obsługi dla elementu AbsoluteLayout
. Niewielka struktura wewnętrzna klasy o nazwie HandParams
opisuje rozmiar każdej z trzech rąk względem całkowitego rozmiaru zegara:
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);
···
}
Procedura SizeChanged
obsługi określa środek i promień AbsoluteLayout
elementu , a następnie rozmiary i pozycje 60 BoxView
elementów używanych jako znaczniki znaczników. Pętla for
kończy się przez ustawienie Rotation
właściwości każdego z tych BoxView
elementów. Na końcu SizeChanged
procedury obsługi LayoutHand
metoda jest wywoływana w celu rozmiaru i położenia trzech rąk zegara:
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;
}
···
}
Metoda LayoutHand
ustawia rozmiary i pozycje każdej ręki, aby wskazać prosto do pozycji 12:00. Na końcu metody AnchorY
właściwość jest ustawiona na pozycję odpowiadającą środkowi zegara. Wskazuje to środek obrotu.
Ręce są obracane w funkcji wywołania zwrotnego czasomierza:
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;
}
}
Druga ręka jest traktowana nieco inaczej: Funkcja złagodzenia animacji jest stosowana, aby ruch wydawał się mechaniczny, a nie gładki. Na każdym kleszczu druga ręka cofnie się trochę, a następnie przesłania swoją lokalizację docelową. Ten trochę kodu dodaje wiele do realizmu ruchu.