Freigeben über


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:

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 BoxViewangewendet 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 StackLayoutDimension 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.Fillals 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:

Einfaches BoxView-Steuerelement

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:

Textdeko

Die stilvolle Kopfzeile oben auf der Seite wird mit einem AbsoluteLayout untergeordneten Element erreicht, dessen untergeordnete Elemente vier BoxView Elemente und eine Labelsind, 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 AbsoluteLayoutDatei 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 Fillfestgelegt ist. Die Breite der StackLayout Betreffenden wird dann durch die Breite der Label, die dann diese Breite auf BoxViewdie . 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 StackLayoutlinie 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:

ListView-Farben

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:

Spiel des Lebens

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:

Punktmatrixuhr

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 AbsoluteLayoutZeichenblatts sind, muss der SizeChanged Handler für die Seite nur einen HeightRequest der Folgenden AbsoluteLayoutfestlegen:

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:

BoxView Clock

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 AbsoluteLayoutHandlers 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.