Xamarin Forms -  BoxView : Exibindo um relógio analógico
 Este artigo apresenta a view BoxView e mostra como simular um relógio analógico em uma aplicação Xamarin Forms.

A view BoxView processa um retângulo simples de uma largura, altura e cor especificadas. Você pode usar a BoxView para decoração, gráficos rudimentares e para interação com o usuário através do toque.

Como o Xamarin.Forms não possui um sistema de gráficos vetoriais incorporado, a BoxView ajuda a compensar essa deficiência. Assim, uma BoxView pode ser dimensionada para se assemelhar a uma linha de largura e espessura específica e, em seguida, girada por qualquer ângulo usando a propriedade Rotation. (A view BoxView é mais usada para gráficos simples para gráficos mais sofisticados veja o SkiaSharp))

Ao usar a view BoxView definimos as seguintes propriedades:

- Color - para definir a cor.
- WidthRequest para definir a largura do BoxView em unidades independentes do dispositivo.
- HeightRequest para definir a altura do BoxView.

A propriedade Color é de tipo Color e pode ser configurada para qualquer valor de Color, incluindo os 141 campos estáticos somente leitura de cores com nomes variando alfabeticamente de AliceBlue para YellowGreen.

As propriedades WidthRequest e HeightRequest apenas desempenham uma função se o BoxView não for restringido no layout. Este é o caso quando o contêiner de layout precisa conhecer o tamanho o elemento filho, por exemplo, quando o BoxView é um filho de uma célula de tamanho automático no layout de um Grid. Um BoxView também não é compatível quando suas propriedades HorizontalOptions e VerticalOptions são definidas para valores diferentes de LayoutOptions.Fill. Se o BoxView não for restringido, mas as propriedades WidthRequest e HeightRequest não estão definidas, a largura ou a altura estão configuradas para valores padrão de 40 unidades, ou cerca de 1/4 de polegada nos dispositivos móveis.

As propriedades WidthRequest e HeightRequest são ignoradas se o BoxView for restringido no layout, caso em que o contêiner de layout impõe seu próprio tamanho no BoxView.

Uma BoxView pode ser restringida em uma dimensão e não ter restrições na outra. Por exemplo, se a BoxView for filho de um StackLayout vertical, a dimensão vertical do BoxView não esta restrita e sua dimensão horizontal geralmente esta restringida.

A seguir temos uma página onde definimos 4 views BoxView definindo as propriedades: Color, WidthRequest, HeighRequest e Opacity

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XF_Analogico.BoxViewPage"
             BackgroundColor="White">    
    <StackLayout>
        <StackLayout Padding="20">
           <BoxView Color="Black" WidthRequest="150" HeightRequest="150"
             VerticalOptions="Center" 
             HorizontalOptions="Center" />
       </StackLayout>        
        <StackLayout Orientation="Horizontal">
            <BoxView Color="Red" WidthRequest="50" HeightRequest="50" Opacity="0.5" />
        </StackLayout>
        <StackLayout Orientation="Horizontal">
            <BoxView Color="Yellow" WidthRequest="50" HeightRequest="25" />
        </StackLayout>
        <StackLayout Orientation="Horizontal">
            <BoxView Color="Green" WidthRequest="25" HeightRequest="50" />
        </StackLayout>
    </StackLayout>
</ContentPage>

Neste artigo vamos usar a view BoxView para definir um desenho simulando um relógio análogico.

Nota:  O exemplo deste artigo foi baseado no original em : http://www.c-sharpcorner.com/article/how-to-box-view-clock-in-xamarin-forms/

Recursos usados:

Criando o projeto no Visual Studio 2017 Community

Abra o Visual Studio Community 2017 e clique em New Project;

Selecione Visual C#, o template Cross-Plataform e a seguir Cross-Platform App (Xamarin.Forms);

Informe o nome XF_Analogico e clique no botão OK;

Selecione as plataformas desejadas: Android, iOS e UWP, marque o item Xamarin.Forms e a seguir escolha .NET Standard e clique no botão OK.

Nota: A partira da versão 15.5 do Visual Studio o template PCL foi substituito pelo .NET Standard.

Usando a view BoxView

Abra a página MainPage.xaml e inclua o código abaixo nesta página:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XF_Analogico"
             x:Class="XF_Analogico.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="ponteiroHora"  
                Color="Black" />
        <BoxView x:Name="ponteiroMinuto"  
                Color="Black" />
        <BoxView x:Name="ponteiroSegundo"  
                Color="Black" />
    </AbsoluteLayout>
</ContentPage>

Usamos no código XAML acima o leiaute AbsoluteLayout e definimos 3 BoxViews representando cada um dos ponteiros do relógio.

Agora vamos definir o código para desenhar os ponteiros e as marcações dos segundos.

Abra o arquivo MainPage.xaml.cs e inclua o código abaixo:

using System;
using Xamarin.Forms;
namespace XF_Analogico
{
   public partial class MainPage : ContentPage
  {
        struct PonteiroParametros
        {
            public PonteiroParametros(double largura, double altura, double offset) : this()
            {
                Width = largura;
                Height = altura;
                Offset = offset;
            }
            public double Width { private set; get; }   // fração do raio  
            public double Height { private set; get; }  // 
            public double Offset { private set; get; }  // relativo ao centro
        }
        static readonly PonteiroParametros segundoParametros = new PonteiroParametros(0.02, 1.1, 0.85);
        static readonly PonteiroParametros minutoParametros = new PonteiroParametros(0.05, 0.8, 0.9);
        static readonly PonteiroParametros horaParametros = new PonteiroParametros(0.125, 0.65, 0.9);
        BoxView[] tickMarcas = new BoxView[60];
        public MainPage()
       {
	  InitializeComponent();
            // Cria um marca de tick (para ser dimensionada e posicionada mais tarde).  
            for (int i = 0; i < tickMarcas.Length; i++)
            {
                tickMarcas[i] = new BoxView { Color = Color.Black };
                absoluteLayout.Children.Add(tickMarcas[i]);
            }
            Device.StartTimer(TimeSpan.FromSeconds(1.0 / 60), OnTimerTick);
        }
        void OnAbsoluteLayoutSizeChanged(object sender, EventArgs args)
        {
            // Pega o centro e o raio do AbsoluteLayout.  
            Point centro = new Point(absoluteLayout.Width / 2, absoluteLayout.Height / 2);
            double raio = 0.45 * Math.Min(absoluteLayout.Width, absoluteLayout.Height);
            // Posicao, tamanho, e rotação  60 ticks
            for (int index = 0; index < tickMarcas.Length; index++)
            {
                double tamanho = raio / (index % 5 == 0 ? 15 : 30);
                double radians = index * 2 * Math.PI / tickMarcas.Length;
                double x = centro.X + raio * Math.Sin(radians) - tamanho / 2;
                double y = centro.Y - raio * Math.Cos(radians) - tamanho / 2;
                AbsoluteLayout.SetLayoutBounds(tickMarcas[index], new Rectangle(x, y, tamanho, tamanho));
                tickMarcas[index].Rotation = 180 * radians / Math.PI;
            }
            // Posicao e tamanho 
            LayoutPonteiro(ponteiroSegundo, segundoParametros, centro, raio);
            LayoutPonteiro(ponteiroMinuto, minutoParametros, centro, raio);
            LayoutPonteiro(ponteiroHora, horaParametros, centro, raio);
        }
        void LayoutPonteiro(BoxView boxView, PonteiroParametros ponteiroParametros, Point centro, double raio)
        {
            double largura = ponteiroParametros.Width * raio;
            double altura = ponteiroParametros.Height * raio;
            double offset = ponteiroParametros.Offset;
            AbsoluteLayout.SetLayoutBounds(boxView,
                new Rectangle(centro.X - 0.5 * largura,
                                      centro.Y - offset * altura,
                                      largura, altura));
            // Atribui a propriedade AnchorY para rotação
            boxView.AnchorY = ponteiroParametros.Offset;
        }
        bool OnTimerTick()
        {
            // Define o angulo de rotação para hora e minutos
            DateTime dataHora = DateTime.Now;
            ponteiroHora.Rotation = 30 * (dataHora.Hour % 12) + 0.5 * dataHora.Minute;
            ponteiroMinuto.Rotation = 6 * dataHora.Minute + 0.1 * dataHora.Second;
            // Faz uma animação para segundo
            double t = dataHora.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));
            }
            ponteiroSegundo.Rotation = 6 * (dataHora.Second + t);
            return true;
        }
    }
}

Definimos uma Struct para cada ponteiro e um array de BoxView com 60 elementos representando os segundos do relógio; depois acionamos a thread  Device.StartTimer.

Executando o projeto iremos obter:

Pegue o projeto completo aqui :  XF_Analogico.zip (sem as referências)

"E esta é a mensagem que dele ouvimos, e vos anunciamos: que Deus é luz, e não há nele trevas nenhumas. "
1 João 1:5

Veja os Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique e confira !

Quer migrar para o VB .NET ?

Quer aprender C# ??

Quer aprender os conceitos da Programação Orientada a objetos ?

Quer aprender o gerar relatórios com o ReportViewer no VS 2013 ?

Referências:


José Carlos Macoratti