Xamarin.Forms Visualização de caixa
BoxView
renderiza um retângulo simples de uma largura, altura e cor especificadas. Você pode usar BoxView
para decoração, gráficos rudimentares e para interação com o usuário através do toque.
Porque Xamarin.Forms não tem um sistema de gráficos vetoriais embutido, o BoxView
ajuda a compensar. Alguns dos programas de exemplo descritos neste artigo usam BoxView
para renderizar elementos gráficos. O BoxView
pode ser dimensionado para se assemelhar a uma linha de uma largura e espessura específicas e, em seguida, girado por qualquer ângulo usando a Rotation
propriedade.
Embora BoxView
possa imitar gráficos simples, convém investigar Usando o SkiaSharp para Xamarin.Forms obter requisitos gráficos mais sofisticados.
Definindo a cor e o tamanho do BoxView
Normalmente, você definirá as seguintes propriedades de BoxView
:
Color
para definir sua cor.CornerRadius
para definir seu raio de canto.WidthRequest
para definir aBoxView
largura das unidades independentes do dispositivo.HeightRequest
para definir a altura doBoxView
.
A Color
propriedade é do tipo Color
; a propriedade pode ser definida como qualquer Color
valor, incluindo os 141 campos estáticos somente leitura de cores nomeadas que variam alfabeticamente de AliceBlue
até YellowGreen
.
A CornerRadius
propriedade é do tipo CornerRadius
, a propriedade pode ser definida como um único double
valor de raio BoxView
de canto uniforme ou uma CornerRadius
estrutura definida por quatro double
valores que são aplicados à parte superior esquerda, superior direita, inferior esquerda e inferior direita do .
As WidthRequest
propriedades e HeightRequest
só desempenham uma função se o BoxView
não estiver restrito no layout. Esse é o caso quando o contêiner de layout precisa saber o tamanho da criança, por exemplo, quando é BoxView
filho de uma célula de tamanho automático no Grid
layout. A BoxView
também não é restrito quando suas HorizontalOptions
propriedades e VerticalOptions
são definidas como valores diferentes de LayoutOptions.Fill
. Se o BoxView
não estiver restrito, mas as WidthRequest
propriedades e HeightRequest
não estiverem definidas, a largura ou altura serão definidas como valores padrão de 40 unidades ou cerca de 1/4 de polegada em dispositivos móveis.
As WidthRequest
propriedades e HeightRequest
serão ignoradas se o BoxView
for restrito no layout, caso em que o contêiner de layout impõe seu próprio tamanho ao BoxView
.
A BoxView
pode ser restringido em uma dimensão e irrestrito na outra. Por exemplo, se o BoxView
é filho de uma vertical StackLayout
, a dimensão vertical do BoxView
é irrestrita e sua dimensão horizontal é geralmente restrita. Mas há exceções para essa dimensão horizontal: se o BoxView
tem sua HorizontalOptions
propriedade definida para algo diferente de LayoutOptions.Fill
, então a dimensão horizontal também é irrestrita. Também é possível que o StackLayout
próprio tenha uma dimensão horizontal irrestrita, caso em que o BoxView
mesmo também será horizontalmente irrestrito.
O exemplo exibe um quadrado de uma polegada sem restrições BoxView
no centro de sua página:
<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>
Eis o resultado:
Se as VerticalOptions
propriedades e HorizontalOptions
forem removidas da BoxView
tag ou estiverem definidas como Fill
, o BoxView
se tornará restrito pelo tamanho da página e se expandirá para preencher a página.
Um BoxView
também pode ser filho de um AbsoluteLayout
. Nesse caso, o local e o tamanho do BoxView
são definidos usando a LayoutBounds
propriedade vinculável anexada. O AbsoluteLayout
é discutido no artigo AbsoluteLayout.
Você verá exemplos de todos esses casos nos programas de exemplo a seguir.
Renderização de decorações de texto
Você pode usar o BoxView
para adicionar algumas decorações simples em suas páginas na forma de linhas horizontais e verticais. A amostra demonstra isso. Todos os elementos visuais do programa são definidos no arquivo MainPage.xaml , que contém vários Label
elementos e BoxView
no StackLayout
mostrado aqui:
<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>
Todas as marcações a seguir são filhas do StackLayout
. Esta marcação consiste em vários tipos de elementos decorativos BoxView
utilizados com o Label
elemento:
O cabeçalho elegante na parte superior da página é obtido com um AbsoluteLayout
cujos filhos são quatro BoxView
elementos e um Label
, todos os quais são atribuídos locais e tamanhos específicos:
<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>
No arquivo XAML, o AbsoluteLayout
é seguido por um Label
texto formatado que descreve o AbsoluteLayout
.
Você pode sublinhar uma cadeia de caracteres de texto colocando o Label
e BoxView
em um StackLayout
que tem seu HorizontalOptions
valor definido como algo diferente de Fill
. A largura do é então governada StackLayout
pela largura do Label
, que então impõe essa largura ao BoxView
. Ao BoxView
é atribuída apenas uma altura explícita:
<StackLayout HorizontalOptions="Center">
<Label Text="Underlined Text"
FontSize="24" />
<BoxView HeightRequest="2" />
</StackLayout>
Você não pode usar essa técnica para sublinhar palavras individuais em cadeias de texto mais longas ou em um parágrafo.
Também é possível usar um BoxView
elemento para hr
se assemelhar a um HTML (regra horizontal). Basta deixar que a largura do BoxView
seja determinada pelo seu recipiente pai, que neste caso é o StackLayout
:
<BoxView HeightRequest="3" />
Finalmente, você pode desenhar uma linha vertical em um lado de um parágrafo de texto colocando o BoxView
e o Label
em uma horizontal StackLayout
. Neste caso, a altura do BoxView
é a mesma que a altura do StackLayout
, que é governada pela altura do Label
:
<StackLayout Orientation="Horizontal">
<BoxView WidthRequest="4"
Margin="0, 0, 10, 0" />
<Label>
···
</Label>
</StackLayout>
Listando cores com BoxView
O BoxView
é conveniente para exibir cores. Este programa usa um ListView
para listar todos os campos públicos estáticos somente leitura da Xamarin.FormsColor
estrutura:
O programa de exemplo inclui uma classe chamada NamedColor
. O construtor estático usa reflexão para acessar todos os campos da Color
estrutura e criar um NamedColor
objeto para cada um. Eles são armazenados na propriedade estática 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; }
}
Os elementos visuais do programa são descritos no arquivo XAML. A propriedade do ListView
é definida como a propriedade estáticaNamedColor.All
, o que significa que o ListView
exibe todos os objetos individuaisNamedColor
:ItemsSource
<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>
Os NamedColor
objetos são formatados pelo ViewCell
objeto que é definido como o modelo de dados do ListView
. Este modelo inclui uma BoxView
propriedade cuja Color
está vinculada à Color
propriedade do NamedColor
objeto.
Jogando o Jogo da Vida subclassificando BoxView
O Jogo da Vida é um autômato celular inventado pelo matemático John Conway e popularizado nas páginas da Scientific American na década de 1970. Uma boa introdução é fornecida pelo artigo da Wikipédia Conway's Game of Life.
O Xamarin.Forms programa de exemplo define uma classe chamada LifeCell
que deriva de BoxView
. Esta classe encapsula a lógica de uma célula individual no Jogo da Vida:
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
Adiciona mais três propriedades a BoxView
: As propriedades e Row
armazenam Col
a posição da célula dentro da grade e a IsAlive
propriedade indica seu estado. A IsAlive
propriedade também define a Color
propriedade do BoxView
para preto se a célula estiver viva e branco se a célula não estiver viva.
LifeCell
Também instala um TapGestureRecognizer
para permitir que o usuário alterne o estado das células tocando nelas. A classe traduz o Tapped
evento do reconhecedor de gestos em seu próprio Tapped
evento.
O programa GameOfLife também inclui uma LifeGrid
classe que encapsula grande parte da lógica do jogo, e uma MainPage
classe que lida com os visuais do programa. Estes incluem uma sobreposição que descreve as regras do jogo. Aqui está o programa em ação mostrando algumas centenas LifeCell
de objetos na página:
Criando um relógio digital
O programa de exemplo cria 210 BoxView
elementos para simular os pontos de uma exibição de matriz de pontos 5 por 7 à moda antiga. Você pode ler a hora no modo retrato ou paisagem, mas ela é maior no modo paisagem:
O arquivo XAML faz pouco mais do que instanciar o AbsoluteLayout
usado para o relógio:
<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>
Todo o resto ocorre no arquivo code-behind. A lógica de exibição da matriz de pontos é bastante simplificada pela definição de várias matrizes que descrevem os pontos correspondentes a cada um dos 10 dígitos e dois pontos:
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];
···
}
Esses campos terminam com uma matriz tridimensional de elementos para armazenar os padrões de BoxView
pontos para os seis dígitos.
O construtor cria todos os BoxView
elementos para os dígitos e dois pontos e também inicializa a Color
propriedade dos BoxView
elementos para os dois pontos:
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();
}
···
}
Este programa usa o posicionamento relativo e o recurso de dimensionamento do AbsoluteLayout
. A largura e a altura de cada um BoxView
são definidas para valores fracionários, especificamente 85% de 1 dividido pelo número de pontos horizontais e verticais. As posições também são definidas para valores fracionários.
Como todas as posições e tamanhos são relativos ao tamanho total do , o SizeChanged
manipulador da AbsoluteLayout
página só precisa definir um HeightRequest
dos 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;
}
···
}
A largura do AbsoluteLayout
é definida automaticamente porque se estende até a largura total da página.
O código final na classe processa MainPage
o retorno de chamada do temporizador e colore os pontos de cada dígito. A definição das matrizes multidimensionais no início do arquivo code-behind ajuda a tornar essa lógica a parte mais simples do programa:
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;
}
}
}
Criando um relógio analógico
Um relógio matricial pode parecer uma aplicação óbvia do BoxView
, mas BoxView
os elementos também são capazes de realizar um relógio analógico:
Todos os elementos visuais no programa de exemplo são filhos de um AbsoluteLayout
arquivo . Esses elementos são dimensionados usando a propriedade anexada LayoutBounds
e girados usando a Rotation
propriedade.
Os três BoxView
elementos para os ponteiros do relógio são instanciados no arquivo XAML, mas não posicionados ou dimensionados:
<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>
O construtor do arquivo code-behind instancia os 60 BoxView
elementos para as marcas de escala ao redor da circunferência do relógio:
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);
}
···
}
O dimensionamento e posicionamento de todos os BoxView
elementos ocorre no SizeChanged
manipulador para o AbsoluteLayout
. Uma pequena estrutura interna à classe chamada HandParams
descreve o tamanho de cada um dos três ponteiros em relação ao tamanho total do relógio:
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);
···
}
O SizeChanged
manipulador determina o centro e o raio do , e então dimensiona e posiciona os 60 BoxView
elementos usados como marcas de AbsoluteLayout
seleção. O for
loop é concluído definindo a Rotation
propriedade de cada um desses BoxView
elementos. No final do SizeChanged
manipulador, o LayoutHand
método é chamado para dimensionar e posicionar os três ponteiros do relógio:
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;
}
···
}
O LayoutHand
método dimensiona e posiciona cada mão para apontar diretamente até a posição 12:00. No final do método, a AnchorY
propriedade é definida como uma posição correspondente ao centro do relógio. Isso indica o centro de rotação.
Os ponteiros são girados na função de retorno de chamada do temporizador:
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;
}
}
A segunda mão é tratada de forma um pouco diferente: uma função de flexibilização de animação é aplicada para fazer o movimento parecer mecânico em vez de suave. Em cada carrapato, a segunda mão recua um pouco e depois ultrapassa seu destino. Esse pouco de código acrescenta muito ao realismo do movimento.