Xamarin.Forms BoxView

Beispiel herunterladen Das Beispiel herunterladen

BoxView rendert ein einfaches Rechteck mit einer angegebenen Breite, Höhe und Farbe. Sie können für Die Dekoration, rudimentäre Grafiken und für die Interaktion mit dem Benutzer durch Toucheingabe verwenden BoxView .

Da Xamarin.Forms nicht über ein integriertes Vektorgrafiksystem verfügt, hilft, die BoxView zu kompensieren. Einige der in diesem Artikel beschriebenen Beispielprogramme verwenden BoxView zum Rendern von Grafiken. Der BoxView kann einer Linie mit einer bestimmten Breite und Stärke ähneln und dann mithilfe der Rotation -Eigenschaft um einen beliebigen Winkel gedreht werden.

Obwohl BoxView einfache Grafiken imitiert werden können, sollten Sie die Verwendung von SkiaSharp in Xamarin.Forms für komplexere Grafikanforderungen untersuchen.

Festlegen von BoxView-Farbe und -Größe

In der Regel legen Sie die folgenden Eigenschaften von fest BoxView:

  • Color , um die Farbe festzulegen.
  • CornerRadius , um den Eckenradius festzulegen.
  • WidthRequest , um die Breite der BoxView in geräteunabhängigen Einheiten festzulegen.
  • HeightRequest , um die Höhe von BoxViewfestzulegen.

Die Color -Eigenschaft ist vom Typ Color. Die -Eigenschaft kann auf einen beliebigen Color Wert festgelegt werden, einschließlich der 141 statischen schreibgeschützten Felder benannter Farben, die alphabetisch von AliceBlue bis reichen YellowGreen.

Die CornerRadius -Eigenschaft ist vom Typ CornerRadius. Die -Eigenschaft kann auf einen einzelnen double einheitlichen Eckradiuswert oder eine CornerRadius Struktur festgelegt werden, die durch vier double Werte definiert wird, die oben links, oben rechts, unten links und unten rechts des BoxViewangewendet werden.

Die WidthRequest Eigenschaften und HeightRequest spielen nur dann eine Rolle, wenn die 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 das BoxView ein untergeordnetes Element einer Zelle mit automatischer Größe im Grid Layout ist. Ein BoxView ist auch nicht eingeschränkt, wenn seine HorizontalOptions Eigenschaften und VerticalOptions auf andere Werte als LayoutOptions.Fillfestgelegt sind. Wenn nicht BoxView eingeschränkt ist, aber die WidthRequest Eigenschaften und HeightRequest nicht festgelegt sind, wird 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 werden ignoriert, wenn die BoxView im Layout eingeschränkt ist. In diesem Fall erzwingt der Layoutcontainer seine eigene Größe für .BoxView

Eine BoxView kann in einer Dimension eingeschränkt und in einer anderen uneingeschränkt sein. Wenn BoxView z. B. ein untergeordnetes Element einer vertikalen StackLayoutist, ist die vertikale Dimension des uneingeschränkt und die BoxView horizontale Dimension im Allgemeinen eingeschränkt. Es gibt jedoch Ausnahmen für diese horizontale Dimension: Wenn die BoxViewHorizontalOptions -Eigenschaft auf etwas anderes als LayoutOptions.Fillfestgelegt ist, ist die horizontale Dimension ebenfalls uneingeschränkt. Es ist auch möglich, dass der StackLayout sich selbst eine uneingeschränkte horizontale Dimension hat, in diesem Fall wird auch horizontal BoxView uneingeschränkt sein.

Im BasicBoxView-Beispiel wird ein 1-Zoll-Quadrat ohne Einschränkungen BoxView in der Mitte der Seite angezeigt:

<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:

Basic BoxView

Wenn die VerticalOptions Eigenschaften und HorizontalOptions aus dem BoxView Tag entfernt oder auf Fillfestgelegt sind, wird die BoxView durch die Größe der Seite eingeschränkt und erweitert, um die Seite zu füllen.

Ein BoxView kann auch ein untergeordnetes Element eines AbsoluteLayoutsein. In diesem Fall werden sowohl die Position als auch die BoxView Größe des mithilfe der LayoutBounds angefügten bindbaren Eigenschaft festgelegt. Die AbsoluteLayout wird im Artikel AbsoluteLayout erläutert.

Beispiele für all diese Fälle finden Sie in den folgenden Beispielprogrammen.

Rendern von Textdekorationen

Sie können verwenden BoxView , um ihren Seiten einige einfache Dekorationen in Form von horizontalen und vertikalen Linien hinzuzufügen. Das TextDecoration-Beispiel veranschaulicht dies. Alle Visuals des Programms sind in der Datei MainPage.xaml definiert, die mehrere Label - und BoxView -Elemente im StackLayout hier gezeigten 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 von StackLayout. Dieses Markup besteht aus mehreren Arten von dekorativen BoxView Elementen, die mit dem Label -Element verwendet werden:

Textdekoration

Die stilvolle Kopfzeile oben auf der Seite wird mit einem AbsoluteLayout erreicht, dessen untergeordnete Elemente vier BoxView Elemente sind, und ein Label, denen alle bestimmten Speicherorten 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 ein AbsoluteLayoutLabel mit formatiertem Text, der die AbsoluteLayoutbeschreibt.

Sie können eine Textzeichenfolge unterstreichen, indem Sie sowohl die Label als BoxView auch in eine StackLayout umschließen, deren HorizontalOptions Wert auf etwas anderes als Fillfestgelegt ist. Die Breite von StackLayout wird dann durch die Breite von Labelbestimmt, die dann diese Breite für das BoxViewaufzwingt. Dem BoxView wird nur eine explizite Höhe zugewiesen:

<StackLayout HorizontalOptions="Center">
    <Label Text="Underlined Text"
           FontSize="24" />
    <BoxView HeightRequest="2" />
</StackLayout>

Sie können diese Technik nicht verwenden, um einzelne Wörter in längeren Textzeichenfolgen oder einem Absatz zu unterstreichen.

Es ist auch möglich, ein BoxView zu verwenden, um einem HTML-Element hr (horizontale Regel) zu ähneln. Lassen Sie einfach die Breite des BoxView durch den übergeordneten Container bestimmen, der in diesem Fall der StackLayoutist:

<BoxView HeightRequest="3" />

Schließlich können Sie eine vertikale Linie auf einer Seite eines Textabsatzs zeichnen, indem Sie sowohl die BoxView als auch die Label in einer horizontalen StackLayouteinschließen. In diesem Fall ist die Höhe des BoxView identisch mit der Höhe von StackLayout, die von der Höhe des Labelbestimmt wird:

<StackLayout Orientation="Horizontal">
    <BoxView WidthRequest="4"
             Margin="0, 0, 10, 0" />
    <Label>

        ···

    </Label>
</StackLayout>

Auflisten von Farben mit BoxView

Eignet BoxView sich für die Anzeige von Farben. Dieses Programm verwendet ein ListView , um alle öffentlichen statischen schreibgeschützten Felder der Xamarin.FormsColor -Struktur auflisten:

ListView-Farben

Das ListViewColors-Programm enthält eine Klasse mit dem Namen NamedColor. Der statische Konstruktor verwendet Reflektion, 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 Programmvisuals werden in der XAML-Datei beschrieben. Die ItemsSource -Eigenschaft von ListView wird auf die statische NamedColor.All Eigenschaft festgelegt, d. h., 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 des ListViewfestgelegt ist. Diese Vorlage enthält eine BoxView , deren Color Eigenschaft an die Color -Eigenschaft des NamedColor -Objekts gebunden ist.

Spielen des Lebensspiels durch Unterklassifizierung von BoxView

The Game of Life ist ein zellulärer Automat, der vom Mathematiker John Conway erfunden und in den Seiten von Scientific American in den 1970er Jahren populär gemacht wurde. Eine gute Einführung bietet der Wikipedia-Artikel Conways Spiel des Lebens.

Das Xamarin.FormsGameOfLife-Programm definiert eine Klasse namens LifeCell , die von abgeleitet wird 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 Eigenschaften und Row speichern die Position der Zelle innerhalb des Rasters, und die -Eigenschaft gibt ihren IsAlive Zustand an. Die IsAlive -Eigenschaft legt auch die Color -Eigenschaft von BoxView auf schwarz fest, wenn die Zelle aktiv ist, und weiß, wenn die Zelle nicht aktiv ist.

LifeCell installiert außerdem ein TapGestureRecognizer , damit der Benutzer den Zustand der Zellen durch Tippen umschalten kann. Die -Klasse übersetzt das Tapped Ereignis von der Gestenerkennung in ein eigenes Tapped Ereignis.

Das GameOfLife-Programm enthält auch eine LifeGrid Klasse, die einen Großteil der Logik des Spiels kapselt, und eine MainPage Klasse, die die Visuals des Programms behandelt. Dazu gehört ein Overlay, das 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 DotMatrixClock-Programm erstellt 210 BoxView Elemente, um die Punkte einer altmodisch 5-mal-7-Punktmatrix-Anzeige zu simulieren. Sie können die Zeit entweder im Hoch- oder Querformat lesen, aber sie ist im Querformat größer:

Punktmatrixuhr Punktmatrixuhr

Die XAML-Datei führt kaum mehr aus, als die für die AbsoluteLayout Uhr verwendete instanziieren:

<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 jeder der 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 den Doppelpunkt und initialisiert auch 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 der einzelnen BoxView Werte sind 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 von AbsoluteLayoutsind, muss der SizeChanged Handler für die Seite nur einen HeightRequest von 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 von AbsoluteLayout wird automatisch festgelegt, da sie auf die volle Breite der Seite gestreckt wird.

Der endgültige Code in der MainPage -Klasse verarbeitet den Timerrückruf und ordnet die Punkte jeder Ziffer ein. 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 BoxViewzu sein, aber BoxView Elemente sind auch in der Lage, eine analoge Uhr zu realisieren:

BoxView Clock

Alle Visuals im BoxViewClock-Programm sind untergeordnete Elemente von AbsoluteLayout. Diese Elemente werden mithilfe der LayoutBounds angefügten Eigenschaft dimensioniert und mithilfe der Rotation -Eigenschaft gedreht.

Die drei BoxView Elemente für die Zeiger der Uhr werden in der XAML-Datei instanziiert, aber nicht positioniert oder dimensioniert:

<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ößenanpassung und Positionierung aller BoxView Elemente erfolgt im SizeChanged Handler für .AbsoluteLayout Eine kleine interne Struktur der Klasse namens HandParams beschreibt die Größe jeder der drei Zeiger im Verhältnis 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 von AbsoluteLayout, und dann werden die 60 BoxView Elemente, die als Häkchen verwendet werden, größen und positioniert. Die for Schleife schließt durch Festlegen der Rotation Eigenschaft jedes dieser BoxView Elemente. Am Ende des SizeChanged Handlers wird die LayoutHand -Methode aufgerufen, um die drei Zeiger der Uhr zu vergrößern und zu positionieren:

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;
    }

    ···

}

Mit LayoutHand der Methode wird jede Hand so groß und positioniert, dass sie gerade auf die 12:00-Position zeigt. Am Ende der -Methode wird die AnchorY -Eigenschaft auf eine Position festgelegt, die der Mitte der Uhr entspricht. Dies gibt den Drehpunkt an.

Die Zeiger werden in der Timer-Rü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 Animationslockerungsfunktion wird angewendet, um das Uhrwerk mechanisch statt glatt erscheinen zu lassen. Bei jedem Tick zieht der Zweite ein wenig zurück und überschießt dann sein Ziel. Dieses wenig Code trägt viel zum Realismus der Bewegung bei.