Xamarin.Forms BoxView
BoxView
rendert ein einfaches Rechteck mit einer angegebenen Breite, Höhe und Farbe. Sie können für Dekoration, rudimentäre Grafiken und für die Interaktion mit dem Benutzer durch Toucheingabe verwenden BoxView
.
Da Xamarin.Forms kein integriertes Vektorgrafiksystem vorhanden ist, hilft das BoxView
Kompensieren. Einige der in diesem Artikel beschriebenen Beispielprogramme verwenden BoxView
das Rendern von Grafiken. Die BoxView
Größe kann so angepasst werden, dass sie einer Linie mit einer bestimmten Breite und Stärke ähnelt, und dann mithilfe der Rotation
Eigenschaft um einen beliebigen Winkel gedreht wird.
Obwohl BoxView
einfache Grafiken nachahmen können, sollten Sie die Verwendung von SkiaSharp Xamarin.Forms nach komplexeren Grafikanforderungen untersuchen.
Festlegen von BoxView-Farbe und -Größe
In der Regel legen Sie die folgenden Eigenschaften von BoxView
:
Color
um die Farbe festzulegen.CornerRadius
um den Eckenradius festzulegen.WidthRequest
um die Breite derBoxView
geräteunabhängigen Einheiten festzulegen.HeightRequest
um die Höhe der .BoxView
Die Color
Eigenschaft ist vom Typ Color
; die Eigenschaft kann auf einen beliebigen Color
Wert festgelegt werden, einschließlich der statischen schreibgeschützten Felder mit benannten Farben, die alphabetisch von AliceBlue
bis zu YellowGreen
.
Die Eigenschaft ist vom Typ CornerRadius
. Die CornerRadius
Eigenschaft kann auf einen einzelnen double
einheitlichen Eckradiuswert oder eine CornerRadius
Struktur festgelegt werden, die durch vier double
Werte definiert wird, die auf die obere linke, obere rechte, untere linke und untere rechte Ecke des Objekts BoxView
angewendet werden.
Die WidthRequest
Eigenschaften HeightRequest
spielen nur eine Rolle, wenn dies BoxView
im Layout nicht eingeschränkt ist. Dies ist der Fall, wenn der Layoutcontainer die Größe des untergeordneten Elements kennen muss, z. B. wenn es BoxView
sich um ein untergeordnetes Element einer Zelle mit automatischer Größe im Grid
Layout handelt. A BoxView
ist auch nicht eingeschränkt, wenn seine HorizontalOptions
und VerticalOptions
Eigenschaften auf andere Werte festgelegt werden als LayoutOptions.Fill
. Wenn dies BoxView
nicht eingeschränkt ist, aber die WidthRequest
Eigenschaften HeightRequest
nicht festgelegt werden, werden die Breite oder Höhe auf Standardwerte von 40 Einheiten oder etwa 1/4 Zoll auf mobilen Geräten festgelegt.
Die WidthRequest
Eigenschaften und HeightRequest
Eigenschaften werden ignoriert, wenn das BoxView
Layout eingeschränkt ist. In diesem Fall legt der Layoutcontainer eine eigene Größe für die BoxView
.
Eine BoxView
kann in einer Dimension eingeschränkt und in einer anderen uneingeschränkt sein. Wenn es BoxView
sich z. B. um ein untergeordnetes Element einer vertikalen StackLayout
Dimension handelt, ist die vertikale Dimension der BoxView
Zugehörigen nicht eingeschränkt, und die horizontale Dimension ist in der Regel eingeschränkt. Es gibt jedoch Ausnahmen für diese horizontale Dimension: Wenn die BoxView
HorizontalOptions
Eigenschaft auf einen anderen Wert LayoutOptions.Fill
als festgelegt ist, ist die horizontale Dimension ebenfalls nicht eingeschränkt. Es ist auch möglich, dass sich selbst StackLayout
eine ungezwungene horizontale Dimension befindet, in diesem Fall BoxView
auch horizontal unbeschränkt.
Im Beispiel wird ein 1-Zoll-Quadrat angezeigt, das in der Mitte der Seite unbeschränkt ist BoxView
:
<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>
Das Ergebnis lautet wie folgt:
Wenn die VerticalOptions
Eigenschaften HorizontalOptions
aus dem BoxView
Tag entfernt oder auf Fill
festgelegt sind, wird dies BoxView
durch die Größe der Seite eingeschränkt und wird erweitert, um die Seite auszufüllen.
BoxView
kann auch ein untergeordnetes Element von AbsoluteLayout
sein. In diesem Fall werden sowohl die Position als auch die Größe des BoxView
Objekts mithilfe der LayoutBounds
angefügten bindungsfähigen Eigenschaft festgelegt. Dies AbsoluteLayout
wird im Artikel AbsoluteLayout erläutert.
Beispiele für all diese Fälle werden in den folgenden Beispielprogrammen angezeigt.
Rendern von Text-Dekorationen
Sie können dies BoxView
verwenden, um einige einfache Dekorationen auf Ihren Seiten in Form von horizontalen und vertikalen Linien hinzuzufügen. Im Beispiel wird dies veranschaulicht. Alle visuellen Elemente des Programms werden in der Datei "MainPage.xaml " definiert, die mehrere Label
elemente BoxView
in der StackLayout
hier gezeigten Datei enthält:
<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>
Alle folgenden Markups sind untergeordnete Elemente der StackLayout
. Dieses Markup besteht aus mehreren Arten von dekorativen BoxView
Elementen, die mit dem Label
Element verwendet werden:
Die stilvolle Kopfzeile oben auf der Seite wird mit einem AbsoluteLayout
untergeordneten Element erreicht, dessen untergeordnete Elemente vier BoxView
Elemente und eine Label
sind, denen alle bestimmten Positionen und Größen zugewiesen sind:
<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>
In der XAML-Datei folgt Label
ein AbsoluteLayout
formatierter Text, der die AbsoluteLayout
Datei beschreibt.
Sie können eine Textzeichenfolge unterstreichen, indem Sie sowohl die Label
als BoxView
auch in eine StackLayout
Zeichenfolge einschließen, deren HorizontalOptions
Wert auf einen anderen Wert als Fill
festgelegt ist. Die Breite der StackLayout
Betreffenden wird dann durch die Breite der Label
, die dann diese Breite auf BoxView
die . Es BoxView
wird nur eine explizite Höhe zugewiesen:
<StackLayout HorizontalOptions="Center">
<Label Text="Underlined Text"
FontSize="24" />
<BoxView HeightRequest="2" />
</StackLayout>
Sie können dieses Verfahren nicht verwenden, um einzelne Wörter innerhalb längerer Textzeichenfolgen oder eines Absatzes zu unterstreichen.
Es ist auch möglich, ein BoxView
HTML-Element hr
(horizontale Regel) zu verwenden. Lassen Sie einfach die Breite der BoxView
durch den übergeordneten Container bestimmt werden, was in diesem Fall die StackLayout
:
<BoxView HeightRequest="3" />
Schließlich können Sie eine vertikale Linie auf einer Seite eines Textabsatzes zeichnen, indem Sie sowohl die BoxView
als auch die Label
horizontale StackLayout
linie einschließen. In diesem Fall ist die Höhe der BoxView
Höhe identisch mit der Höhe von StackLayout
, die durch die Höhe der Label
:
<StackLayout Orientation="Horizontal">
<BoxView WidthRequest="4"
Margin="0, 0, 10, 0" />
<Label>
···
</Label>
</StackLayout>
Auflisten von Farben mit BoxView
Dies BoxView
ist praktisch zum Anzeigen von Farben. Dieses Programm verwendet eine ListView
Liste aller öffentlichen statischen schreibgeschützten Felder der Xamarin.FormsColor
Struktur:
Das Beispielprogramm enthält eine Klasse mit dem Namen NamedColor
. Der statische Konstruktor verwendet Spiegelung, um auf alle Felder der Color
Struktur zuzugreifen und für jedes Objekt ein NamedColor
Objekt zu erstellen. Diese werden in der statischen All
Eigenschaft gespeichert:
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; }
}
Die visuellen Programmelemente werden in der XAML-Datei beschrieben. Die ItemsSource
Eigenschaft der ListView
Eigenschaft wird auf die statische NamedColor.All
Eigenschaft festgelegt, was bedeutet, dass alle ListView
einzelnen NamedColor
Objekte angezeigt werden:
<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>
Die NamedColor
Objekte werden von dem ViewCell
Objekt formatiert, das als Datenvorlage der .ListView
Diese Vorlage enthält eine BoxView
Eigenschaft, deren Color
Eigenschaft an die Color
Eigenschaft des NamedColor
Objekts gebunden ist.
Spielen des Spiels von Life durch Unterklassifizierung von BoxView
Das Spiel des Lebens ist ein zellulärer Automaton, der von Mathematiker John Conway erfunden und in den Seiten von Scientific American in den 1970er Jahren populär wurde. Eine gute Einführung finden Sie im Wikipedia-Artikel Conway es Game of Life.
Das Xamarin.Forms Beispielprogramm definiert eine Klasse mit dem Namen LifeCell
, die von BoxView
. Diese Klasse kapselt die Logik einer einzelnen Zelle im Spiel des Lebens:
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
fügt drei weitere Eigenschaften hinzu BoxView
: Die Col
Und Row
Eigenschaften speichern die Position der Zelle innerhalb des Rasters, und die IsAlive
Eigenschaft gibt ihren Zustand an. Die IsAlive
Eigenschaft legt auch die Color
Eigenschaft des Werts BoxView
auf Schwarz fest, wenn die Zelle aktiv ist, und weiß, wenn die Zelle nicht aktiv ist.
LifeCell
installiert außerdem eine TapGestureRecognizer
Installation, mit der der Benutzer den Zustand von Zellen umschalten kann, indem er darauf tippt. Die Klasse übersetzt das Tapped
Ereignis aus der Gestikerkennung in ein eigenes Tapped
Ereignis.
Das GameOfLife-Programm enthält auch eine Klasse, die einen LifeGrid
Großteil der Logik des Spiels kapselt, und eine MainPage
Klasse, die die visuellen Elemente des Programms behandelt. Dazu gehört eine Überlagerung, die die Regeln des Spiels beschreibt. Hier ist das Programm in Aktion, das ein paar hundert LifeCell
Objekte auf der Seite zeigt:
Erstellen einer digitalen Uhr
Das Beispielprogramm erstellt 210 BoxView
Elemente, um die Punkte einer altmodisch dargestellten 5:7 Punktmatrix zu simulieren. Sie können die Zeit im Hoch- oder Querformat lesen, aber es ist im Querformat größer:
Die XAML-Datei instanziieren die AbsoluteLayout
für die Uhr verwendete Datei nur wenig:
<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>
Alles andere geschieht in der CodeBehind-Datei. Die Anzeigelogik der Punktmatrix wird durch die Definition mehrerer Arrays erheblich vereinfacht, die die Punkte beschreiben, die den einzelnen 10 Ziffern und einem Doppelpunkt entsprechen:
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];
···
}
Diese Felder schließen mit einem dreidimensionalen Array von BoxView
Elementen zum Speichern der Punktmuster für die sechs Ziffern ab.
Der Konstruktor erstellt alle BoxView
Elemente für die Ziffern und Doppelpunkte und initialisiert außerdem die Color
Eigenschaft der BoxView
Elemente für den Doppelpunkt:
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();
}
···
}
Dieses Programm verwendet die relative Positionierungs- und Größenanpassungsfunktion von AbsoluteLayout
. Die Breite und Höhe jeder BoxView
werden auf Bruchwerte festgelegt, insbesondere 85 % von 1 dividiert durch die Anzahl der horizontalen und vertikalen Punkte. Die Positionen werden auch auf Bruchwerte festgelegt.
Da alle Positionen und Größen relativ zur Gesamtgröße des AbsoluteLayout
Zeichenblatts sind, muss der SizeChanged
Handler für die Seite nur einen HeightRequest
der Folgenden AbsoluteLayout
festlegen:
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;
}
···
}
Die Breite der AbsoluteLayout
Seite wird automatisch festgelegt, da sie auf die volle Breite der Seite gestreckt wird.
Der letzte Code in der MainPage
Klasse verarbeitet den Timerrückruf und farben die Punkte jeder Ziffer. Die Definition der mehrdimensionalen Arrays am Anfang der CodeBehind-Datei trägt dazu bei, diese Logik zum einfachsten Teil des Programms zu machen:
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;
}
}
}
Erstellen einer analogen Uhr
Eine Punktmatrixuhr scheint eine offensichtliche Anwendung von BoxView
, aber BoxView
Elemente sind auch in der Lage, eine analoge Uhr zu realisieren:
Alle visuellen Elemente im Beispielprogramm sind untergeordnete Elemente eines AbsoluteLayout
. Diese Elemente werden mithilfe der LayoutBounds
angefügten Eigenschaft angepasst und mithilfe der Rotation
Eigenschaft gedreht.
Die drei BoxView
Elemente für die Hände der Uhr werden in der XAML-Datei instanziiert, jedoch nicht positioniert oder angepasst:
<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>
Der Konstruktor der CodeBehind-Datei instanziiert die 60 BoxView
Elemente für die Teilstriche um den Umfang der Uhr:
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);
}
···
}
Die Größe und Positionierung aller BoxView
Elemente erfolgt im SizeChanged
Handler für die AbsoluteLayout
. Eine kleine strukturinterne HandParams
Klasse beschreibt die Größe jeder der drei Hände relativ zur Gesamtgröße der Uhr:
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);
···
}
Der SizeChanged
Handler bestimmt die Mitte und den Radius des AbsoluteLayout
Handlers und positioniert dann die 60 BoxView
Elemente, die als Teilstriche verwendet werden. Die for
Schleife wird beendet, indem die Rotation
Eigenschaft der einzelnen BoxView
Elemente festgelegt wird. Am Ende des SizeChanged
Handlers wird die LayoutHand
Methode zur Größe aufgerufen und die drei Hände der Uhr positioniert:
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;
}
···
}
Die LayoutHand
Methodengrößen und positioniert jede Hand, um gerade bis zur 12:00-Position zu zeigen. Am Ende der Methode wird die AnchorY
Eigenschaft auf eine Position festgelegt, die der Mitte der Uhr entspricht. Dies gibt den Drehmittelpunkt an.
Die Hände werden in der Timerrückruffunktion gedreht:
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;
}
}
Die zweite Hand wird etwas anders behandelt: Eine Animation Beschleunigungsfunktion wird angewendet, damit die Bewegung mechanische und nicht glatt erscheinen kann. Bei jedem Tick zieht die zweite Hand ein wenig zurück und übergibt dann das Ziel. Dieses kleine Stück Code fügt viel zum Realismus der Bewegung hinzu.