[Xamarin.Forms] Columnas y filas más fácil que nunca en un Grid

Creando columnas y filas en un Grid

XAML es increíble en muchas ocasiones y cumple bien su cometido, pero en ocasiones, puede llegar a ser excesivamente verboso. Un ejemplo de esto lo podemos ver al crear filas y columnas en un Grid:

<Grid>
     <Grid.ColumnDefinitions> 
          <ColumnDefinition Width="100" />
          <ColumnDefinition Width="Auto" />
          <ColumnDefinition Width="Auto" />
          <ColumnDefinition Width="*" />
     </Grid.ColumnDefinitions>
     <Grid.RowDefinitions> 
          <RowDefinition Height="24" />
          <RowDefinition Height= "64" />
          <RowDefinition Height="24" />
          <RowDefinition Height="*" />
          <RowDefinition Height="Auto" />
     </Grid.RowDefinitions>
</Grid>

Esta es la forma clásica de crear filas y columnas hasta la llegada de Xamarin.Forms 4.7…

Simplificando…

Ahora podemos crear filas y columnas de una forma mucho más sencilla:

<Grid ColumnDefinitions="100, Auto, Auto, *" RowDefinitions="24, 64, 24, *, Auto"/>

Fantástico!, ¿no crees?. Esto es posible gracias a un nuevo TypeConverter añadido. Este convertidor analiza el valor que está entrando, que será una cadena y lo convierte en algo que la propiedad entiende permitiéndonos crear filas y columnas de una forma mucho más sencilla.

Más información

[Xamarin.Forms] StateTriggers

Introducción

En forma de Preview acaba de llegar en Xamarin.Forms mejoras en VisualStateManager con el soporte a StateTriggers.

StateTriggers

Los StateTriggers representan un conjunto de reglas que aplican VisualStates basados ciertas condiciones. Ahora, cada VisualState cuenta con una colección de StateTriggerBase que indican cuando el VisualState se debe aplicar. Si alguno de los triggers esta activo, el VisualState será aplicado.

NOTA: Si has desarrollado previamente para UWP, el concepto de StateTriggers te será familiar.

StateTriggers es una Preview por lo que para usarlo, recuerda usar la siguiente etiqueta experimental:

Xamarin.Forms.Forms.SetFlags("StateTriggers_Experimental");

AdaptiveTrigger

Este trigger es muy versátil. Permite definir reglas en XAML basadas en el tamaño de la pantalla. Veamos un ejemplo:

Cuenta con dos posibles valores:

  • MinWindowHeight
  • MinWindowWidth

Veamos un ejemplo sencillo:

<Grid>
     <VisualStateManager.VisualStateGroups>
          <VisualStateGroup>
               <VisualState x:Name="Narrow">
                    <VisualState.StateTriggers>
                         <AdaptiveTrigger MinWindowWidth="0" />
                    </VisualState.StateTriggers>
                   <VisualState.Setters>
                        <Setter Property="BackgroundColor" Value="Blue" />
                   </VisualState.Setters>
              </VisualState>
              <VisualState x:Name="Medium">
                   <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="720" />
                   </VisualState.StateTriggers>
                   <VisualState.Setters>
                        <Setter Property="BackgroundColor" Value="Red" />
                   </VisualState.Setters>
               </VisualState>
               <VisualState x:Name="Large">
                    <VisualState.StateTriggers>
                         <AdaptiveTrigger MinWindowWidth="1000" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                         <Setter Property="BackgroundColor" Value="Green" />
                    </VisualState.Setters>
               </VisualState>
          </VisualStateGroup>
     </VisualStateManager.VisualStateGroups> 
</Grid>

El resultado:

AdaptiveTrigger

El color de fondo del Grid será azul. Si el ancho de la ventana es mayor que 720 pasará (automáticamente) a ser rojo. Por último, el color cambiará a verde si el ancho es mayor que 1000.

CompareStateTrigger

CompareStateTrigger activará el trigger si el valor de Property es igual a Value.

<Grid>
     <VisualStateManager.VisualStateGroups>
          <VisualStateGroup>
               <VisualState x:Name="Checked">
                    <VisualState.StateTriggers>
                         <CompareStateTrigger Property="{Binding IsChecked, Source={x:Reference CheckBox}}" Value="True" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                         <Setter Property="BackgroundColor" Value="Green" />
                    </VisualState.Setters>
               </VisualState>
               <VisualState x:Name="UnChecked">
                    <VisualState.StateTriggers>
                         <CompareStateTrigger Property="{Binding IsChecked, Source={x:Reference CheckBox}}" Value="False" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                         <Setter Property="BackgroundColor" Value="Red" />
                    </VisualState.Setters>
               </VisualState>
          </VisualStateGroup> 
     </VisualStateManager.VisualStateGroups> 
     <CheckBox
           x:Name="CheckBox"/>
</Grid>

Veamos el resultado:

CompareStateTrigger

Como podemos ver, estamos modificando el BackgroundColor del Grid en base a si el CheckBox esta marcado o no. Si el ChechBox esta marcado, el color de fondo es verde, en otro caso será rojo.

Se puede utilizar enlace a datos por lo que la comparación puede ser con una propiedad de la ViewModel, una propiedad de un elemento visual, etc. abriendo una enorme variedad de posibilidades de forma muy sencilla.

DeviceStateTrigger

Un StateTrigger sencillo que nos permite activar VisualStates en base al dispositivo donde se ejecuta la App:

<Grid>
     <VisualStateManager.VisualStateGroups>
          <VisualStateGroup>
               <VisualState
                    x:Name="Android">
                    <VisualState.StateTriggers>
                         <DeviceStateTrigger Device="Android" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                         <Setter Property="BackgroundColor" Value="Blue" />
                    </VisualState.Setters>
               </VisualState>
               <VisualState
                    x:Name="iOS">
                    <VisualState.StateTriggers>
                         <DeviceStateTrigger Device="iOS" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                         <Setter Property="BackgroundColor" Value="Red" />
                    </VisualState.Setters>
               </VisualState>
          </VisualStateGroup> 
     </VisualStateManager.VisualStateGroups> 
</Grid>

Si ejecutamos en Android:

DeviceStateTrigger en Android

Y si la App se ejecuta en iOS:

DeviceStateTrigger en iOS

Sencillo, ¿verdad?.

OrientationStateTrigger

Este StateTrigger cuenta con la propiedad Orientation que nos permite aplicar VisualStates en base a la orientación del dispositivo:

<Grid>
     <VisualStateManager.VisualStateGroups>
          <VisualStateGroup>
               <VisualState
                    x:Name="Landscape">
                    <VisualState.StateTriggers>
                         <OrientationStateTrigger Orientation="Landscape" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                         <Setter Property="BackgroundColor" Value="Blue" />
                    </VisualState.Setters>
               </VisualState>
               <VisualState
                    x:Name="Portrait">
                    <VisualState.StateTriggers>
                         <OrientationStateTrigger Orientation="Portrait" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                         <Setter Property="BackgroundColor" Value="Red" />
                    </VisualState.Setters>
                </VisualState>
           </VisualStateGroup>
      </VisualStateManager.VisualStateGroups> 
</Grid>

El resultado:

OrientationStateTrigger

Sin necesidad de suscribirse a eventos o lógica extra.

StateTriggers y Surface Duo/Neo (Dual Screen)

Xamarin.Forms le da soporte a Surface Duo y Surface Neo (dispositivos con dos pantallas) con el paquete Xamarin.Forms.DualScreen principalmente gracias a TwoPaneView.

Sin embargo, en la librería Xamarin.Forms.DualScreen también tenemos StateTriggers específicos a la hora de gestionar la UI al trabajar con dos pantallas.

El SpanModeStateTrigger cuenta con la propiedad SpanMode que nos permite ajustar la UI usando VisualStates en base a si estamos usando una única pantalla, etc.

<Grid>
     <VisualStateManager.VisualStateGroups>
          <VisualStateGroup>
               <VisualState x:Name="NotSpanned">
                    <VisualState.StateTriggers>
                         <dualScreen:SpanModeStateTrigger SpanMode="SinglePane"/>
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                         <Setter Property="BackgroundColor" Value="Red" />
                    </VisualState.Setters>
               </VisualState>
               <VisualState x:Name="Spanned">
                    <VisualState.StateTriggers>
                         <dualScreen:SpanModeStateTrigger SpanMode="Wide" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                         <Setter Property="BackgroundColor" Value="Green" />
                    </VisualState.Setters>
               </VisualState>
          </VisualStateGroup>
     </VisualStateManager.VisualStateGroups>
</Grid>

Usando una única pantalla:

Utilizando una pantalla

Al usar dos:

Utilizando las dos pantallas

Han llegado StateTriggers a Xamarin.Forms y pronto llegarán más opciones relacionadas. En algunos casos, hay StateTriggers que nos permiten hacer algo que ya podíamos de forma sencilla (por ejemplo, ajustes en la UI al cambiar la orientación) mientras que en otros casos, abre muchas posibilidades. ¿Qué te parece esta nueva funcionalidad?. Recuerda, puedes dejar cualquier feedback en los comentarios de la entrada.

Más información

[Xamarin.Forms Challenge] PulseMusic

Introducción

La evolución de Xamarin.Forms es meritoria. En los últimos tiempos se han recibido novedades interesantes como efectos, vistas nativas, Forms Embedding, FlexLayout, etc. Sin embargo, en muchos casos se sigue asociado a desarrollos muy simples o formularios básicos.

Realmente, en el estado actual de Xamarin.Forms se pueden conseguir aplicaciones nativas de gran escala, con interfaces cuidadas y con alta integración con la plataforma. Hay que tener en cuenta el conjunto de Custom Renderers (código específico en cada plataforma) necesario para lograrlo.

NOTA: La elección entre Xamarin Classic o Xamarin.Forms es importante. Es necesario evaluar la aplicación a desarrollar, el conjunto de características específicas de cada plataforma (que pueden requerir un Custom Renderer), etc. 

En este artículo, vamos a tomar como referencia un diseño de Dribble, que intentaremos replicar con Xamarin.Forms paso a paso.

Music Player

Carátula circular

Comenzamos por una de las partes principales de la vista, la carátula circular. Vamos a utilizar FFImageLoading junto a las opciones de transformación disponibles:

xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
xmlns:fftransformations="clr-namespace:FFImageLoading.Transformations;assembly=FFImageLoading.Transformations"

Utilizamos CircleTransformation para conseguir la imagen circular:

<ffimageloading:CachedImage 
     Aspect="AspectFit"
     Source="{Binding Song.Cover}">
     <ffimageloading:CachedImage.Transformations>
          <fftransformations:CircleTransformation />
     </ffimageloading:CachedImage.Transformations>
</ffimageloading:CachedImage>

Sencillo, ¿verdad?.

Botón de reproducción

Tenemos un botón totalmente circular. Podríamos al igual que la barra de progreso circular, crearlo con SkiaSharp facilmente. Sin embargo, si queremos contar con un botón, debemos crear un Custom Renderer.

Contamos con un botón circular preparado para utilizar con ButtonCirclePlugin. Tras añadir el paquete NuGet, añadimos el namespace:

xmlns:buttonCircle="clr-namespace:ButtonCircle.FormsPlugin.Abstractions;assembly=ButtonCircle.FormsPlugin.Abstractions"

Y utilizamos el control:

<buttonCircle:CircleButton
     Command="{Binding PlayCommand}"
     FontIcon="FontAwesome"
     Icon="{Binding Icon}" 
     FontSize="{StaticResource FontSize16}"
     TextColor="{StaticResource WhiteColor}" 
     HeightRequest="60" 
     WidthRequest="60" 
     BackgroundColor="{StaticResource PlayerRedColor}" />

De nuevo, muy sencillo esta parte de la interfaz, ¿verdad?.

Animación de la carátula

Xamarin.Forms cuenta con una potente API de animaciones. Entre las animaciones incluidas, encontramos las de rotación:

await CoverImage.RelRotateTo(360, AppSettings.CoverAnimationDuration, Easing.Linear);

La única parte con mayor complejidad de la animación, es que:

  • Por un lado mientras se esta reproduciendo la música, debe estar en ejecución en bucle.
  • Por otro lado, si el usuario pausa la reproducción o bien, la canción termina; se debe pausar o detener la animación.

Para la reproducción en bucle utilizamos una Task donde repetimos la misma animación. Para cancelar la Task hacemos uso de CancellationTokenSource. Para pausar la animación usamos:

ViewExtensions.CancelAnimations(CoverImage);

Progreso circular

Sin duda alguna, una de las claves de la vista es la barra de progreso circular. Por un lado, podemos irnos a por Custom Renderer y aprovechar código nativo en cada plataforma. Si queremos conseguir todo sólo con código compartido podríamos conseguir una barra circular de forma sencilla con imágenes y rotaciones, por ejemplo.

En este caso, vamos a utilizar SkiaSharp.

public class CircleProgress : SKCanvasView
{

}

Para dibujar el progreso circular, utilizaremos el método DrawPath para dibujar un Path al que le daremos la forma deseada utilizando el método AddArc.

path.AddArc(rect, StartAngle, angle);

canvas.DrawPath(path, paint);

Para utilizar el control, definimos el namespace:

xmlns:controls="clr-namespace:PulseMusic.Controls"

Y utilizamos el control:

<controls:CircleProgress 
     VerticalOptions="FillAndExpand"
     HorizontalOptions="FillAndExpand"
     Progress="{Binding Progress}"
     LineBackgroundColor ="{StaticResource BlackColor}"
     ProgressColor="{StaticResource PlayerRedColor}"
     StrokeWidth="12"/>

El progreso lo tenemos enlazado a una propiedad en la ViewModel donde se irá realizando el cálculo del progreso.

Fondo con degradado circular

De nuevo, SkiaSharp al rescate!. Definimos algunas propiedades que nos permitan gestionar la apariencia del control:

  • Radius: Propiedad de tipo entero que nos permitirá controlar el radio del gradiente radial.
  • StartColor: Color inicial del degradado.
  • EndColor: Color final del degradado.

Crearemos un shader para definir el degradado. Para crear el degradado, usamos el método CreateRadialGradient:

var colors = new SKColor[] { StartColor.ToSKColor(), EndColor.ToSKColor() };
SKPoint startPoint = new SKPoint(info.Width / 2, info.Height / 2);
var shader = SKShader.CreateRadialGradient(startPoint, Radius, colors, null, SKShaderTileMode.Clamp);

SKPaint paint = new SKPaint
{
     Style = SKPaintStyle.Fill,
     Shader = shader
};

canvas.DrawRect(new SKRect(0, 0, info.Width, info.Height), paint);

A la hora de usar el control:

<controls:CircleGradientBackground 
     VerticalOptions="FillAndExpand"
     HorizontalOptions="FillAndExpand"
     Radius="600"
     StartColor="{StaticResource LightBackgroundColor}"
     EndColor="{StaticResource BackgroundColor}" />

¿Qué plugins o componentes se han utilizado?

Se ha utilizado:

  • FFImageLoading – Con el objetivo principal de gestionar la carátula circular. Esta librería también nos facilita importante funcionalidad relacionada con el cacheo de imágenes, etc. Aunque recuerda, en este ejemplo todas las imágenes son locales.
  • ButtonCirclePlugin – De forma sencilla permite crear botones circulares. Ideal para el botón central que gestiona Play/Pause. Además, soporta directamente iconos con algunas de las fuentes más utilizadas.

Y llegamos a la parte final del artículo. Es un concepto de artículo que ya hemos realizado previamente con la creación de la interfaz de Netflix, por ejemplo. Es sumamente divertido preparar un diseño con cierto nivel de “reto” e intetar “desgranar” cada paso a realizar. Pero, ¿qué te parece este tipo de artículos?.

Cualquier duda o comentario es bienvenido en los comentarios!

El resultado

Pulse Music

Puedes encontrar el ejemplo en GitHub:

Ver GitHub

Más información

[Xamarin.Forms Challenge] My Trip Countdown

Introducción

La evolución de Xamarin.Forms es meritoria. En los últimos tiempos se han recibido novedades interesantes como efectos, vistas nativas, Forms Embedding, FlexLayout, etc. Sin embargo, en muchos casos se sigue asociado a desarrollos muy simples o formularios básicos.

Realmente, en el estado actual de Xamarin.Forms se pueden conseguir aplicaciones nativas de gran escala, con interfaces cuidadas y con alta integración con la plataforma. Hay que tener en cuenta el conjunto de Custom Renderers (código específico en cada plataforma) necesario para lograrlo.

NOTA: La elección entre Xamarin Classic o Xamarin.Forms es importante. Es necesario evaluar la aplicación a desarrollar, el conjunto de características específicas de cada plataforma (que pueden requerir un Custom Renderer), etc. 

En este artículo, vamos a tomar como referencia un diseño de Dribble, que intentaremos replicar con Xamarin.Forms paso a paso.

Countdown Timer

Lo primero que debemos realizar, es un breve análisis de la pantalla:

  • Simplificando, estamos ante un contador hacia atrás, en concreto, hacia una fecha. En Xamarin.Forms contamos con la clase Device, una clase importante que nos facilita información importante como si estamos en una plataforma u otra, o si ejecutamos la App en teléfono o tableta. Además, Device cuenta con un Timer que podemos utilizar para la cuenta atrás, entre otras opciones.
  • De lo que más llama la atención de la simple interfaz de usuario, son los degradados. La línea de progreso circular es candidata a realizarse con SkiaSharp. A pesar de poder abordarla con un Custom Renderer, SkiaSharp nos facilitará dibujarla una única vez para todas las plataformas. El botón, a pesar de poder abordarlo también con SkiaSharp, vamos a realizarlo en nativo para poder contar con los diferentes estados, etc. En este caso, vamos a necesitar Custom Renderer o plugin de comunidad.
  • La carátula circular no es compleja. Entre en conjunto de posibilidades que tenemos, FFImageLoading gana enteros por su facilidad de uso, opciones de cache además de transformaciones.
  • La rotación de la carátula es una sencilla animación de rotación. Xamarin.Forms cuenta con una API de animaciones bastante completa.

¿Manos a la obra?

Imagen circular

Comenzamos por una de las partes principales de la vista, la imagen circular. Vamos a utilizar FFImageLoading junto a las opciones de transformación disponibles:

xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
xmlns:fftransformations="clr-namespace:FFImageLoading.Transformations;assembly=FFImageLoading.Transformations"

Vamos a utilizar CircleTransformation para conseguir la imagen circular:

<ffimageloading:CachedImage 
     Aspect="AspectFit"
     Source="{Binding MyTrip.Picture}">
     <ffimageloading:CachedImage.Transformations>
          <fftransformations:CircleTransformation />
     </ffimageloading:CachedImage.Transformations>
</ffimageloading:CachedImage>

Sencillo, ¿verdad?.

Progreso circular con degradado

Sin duda alguna, la clave de la vista es la barra de progreso circular… y con degradado!. Por un lado, podemos irnos a por Custom Renderer y aprovechar código nativo en cada plataforma. Si queremos conseguir todo sólo con código compartido podríamos conseguir una barra circular de forma sencilla con imágenes y rotaciones, por ejemplo. Sin embargo, necesitamos diferentes recursos gráficos para adaptarnos a diferentes resoluciones. Por otro lado, el degradado añade complejidad.

¿La solución sencilla?

SkiaSharp. Engine gráfico 2D disponible para iOS, Android, UWP, macOS, Windows, etc.

Nos permite trabajar de forma sencilla desde co n figuras básicas a efectos y shaders más complejos.

En este caso, vamos a crear un control personalizado que herede de la clase SKCanvasView.

SKCanvasView es una vista Xamarin.Forms donde podremos dibujar utilizando SkiaSharp.

public class CircleCountdown : SKCanvasView
{

}

Vamos a crear varias BindableProperties para gestionar el comportamiento y apariencia de la barra de progreso:

public static readonly BindableProperty StrokeWidthProperty =
     BindableProperty.Create(nameof(StrokeWidth), typeof(float), typeof(CircleCountdown), 10f, propertyChanged: OnPropertyChanged);

public static readonly BindableProperty ProgressProperty =
     BindableProperty.Create(nameof(Progress), typeof(float), typeof(CircleCountdown), 0f, propertyChanged: OnPropertyChanged);

public static readonly BindableProperty ProgressStartColorProperty =
     BindableProperty.Create(nameof(ProgressStartColor), typeof(Color), typeof(CircleCountdown), Color.Blue, propertyChanged: OnPropertyChanged);

public static readonly BindableProperty ProgressEndColorProperty =
     BindableProperty.Create(nameof(ProgressEndColor), typeof(Color), typeof(CircleCountdown), Color.Red, propertyChanged: OnPropertyChanged);

public float StrokeWidth
{
     get { return (float)GetValue(StrokeWidthProperty); }
     set { SetValue(StrokeWidthProperty, value); }
}

public float Progress
{
     get { return (float)GetValue(ProgressProperty); }
     set { SetValue(ProgressProperty, value); }
}

public Color ProgressStartColor
{
     get { return (Color)GetValue(ProgressStartColorProperty); }
     set { SetValue(ProgressStartColorProperty, value); }
}

public Color ProgressEndColor
{
     get { return (Color)GetValue(ProgressEndColorProperty); }
     set { SetValue(ProgressEndColorProperty, value); }
}

Contamos con:

  • StrokeWidth: Permite controlar el grosor de la barra de progreso.
  • Progress: Determina el progreso. Acepta un valor numérico entre 0  y 1.
  • ProgressStartColor: ¿Recuerdas la necesidad de crear degradado?. Por ese motivo, vamos a contar con esta propiedad para gestionar el color de inicio del degradado.
  • ProgressEndColor: Color final del degradado.

Para dibujar el progreso circular, utilizaremos el método DrawPath para dibujar un Path al que le daremos la forma deseada utilizando el método AddArc.

using (SKPath path = new SKPath())
{
     path.AddArc(rect, StartAngle, angle);

     canvas.DrawPath(path, paint);
}

¿Y el degradado?.

En lugar de utilizar un SKColor (color en SkiaSharp) sólido, vamos a crear un shader. Utilizaremos el método CreateSweepGradient para crear el degradado:

var shader = SKShader.CreateSweepGradient(
     new SKPoint(size / 2, size / 2),
     new[]
     {
          ProgressStartColor.ToSKColor(),
          ProgressEndColor.ToSKColor(),
          ProgressStartColor.ToSKColor()
     },
     new[]
     {
          StartAngle / 360,
          (StartAngle + progressAngle + 1) / 360,
          (StartAngle + progressAngle + 2) / 360
});

Para utilizar el control, definimos el namespace:

xmlns:controls="clr-namespace:MyTripCountdown.Controls"

Y utilizamos el control:

<controls:CircleCountdown 
     VerticalOptions="FillAndExpand"
     HorizontalOptions="FillAndExpand"
     Progress="{Binding Progress}"
     ProgressStartColor="{StaticResource ProgressPinkColor}"
     ProgressEndColor="{StaticResource ProgressBlueColor}"
     StrokeWidth="10"/>

El progreso lo tenemos enlazado a una propiedad en la ViewModel donde se irá realizando el cálculo del progreso.

Botón con degradado

Llegamos al botón con degradado. Podríamos al igual que la barra de progreso circular, crearlo con SkiaSharp facilmente. Sin embargo, un botón cuenta con estados que tienen importancia. Habilitado, deshabilitado, pulsado, etc. Si queremos contar con un botón, debemos crear un Custom Renderer. No es complejo haciendo un Custom Renderer del botón y utilizando RippleDrawable con GradientDrawable en Android, y CAGradientLayer en iOS.

Sin embargo, la comunidad de Xamarin es fantástica. Contamos con un botón con degradado preparado para utilizar con Skor.UI. Tras añadir el paquete NuGet, añadimos el namespace:

xmlns:skor="clr-namespace:Skor.Controls;assembly=Skor.Controls"

Y utilizamos el control:

<skor:GradientButton 
     HeightRequest="60" 
     CornerRadius="96"
     StartColor="{StaticResource ProgressPinkColor}"
     EndColor="{StaticResource ProgressBlueColor}"
     Command="{Binding RestartCommand}"/>

De nuevo, muy sencillo esta parte de la interfaz, ¿verdad?.

Otros

Y no hay que olvidar que estamos haciendo un contador hacia atrás. ¿Formas de conseguirlo?. Tenemos diferentes opciones, directamente en Xamarin.Forms, tenemos el método StartTimer en la clase Device:

Device.StartTimer(TimeSpan.FromSeconds(seconds), () =>
{
     RemainTime = (EndDate - DateTime.Now);

     var ticked = RemainTime.TotalSeconds > 1;

     if (ticked)
     {
          Ticked?.Invoke();
     }
     else
     {
          Completed?.Invoke();
     }

     return ticked; 
});

¿Qué plugins o componentes se han utilizado?

Se ha utilizado:

  • FFImageLoading – Con el objetivo principal de gestionar la imágen circular. Esta librería también nos facilita importante funcionalidad relacionada con el cacheo de imágenes, etc. Aunque recuerda, en este ejemplo todas las imágenes son locales.
  • SKOR.UI – Nos facilita la creación de botones con degradados evitandonos crear un Custom Renderer.

Y llegamos a la parte final del artículo. Es un concepto de artículo que ya hemos realizado previamente con la creación de la interfaz de Netflix, por ejemplo. Es sumamente divertido preparar un diseño con cierto nivel de “reto” e intetar “desgranar” cada paso a realizar. Pero, ¿qué te parece este tipo de artículos?.

Cualquier duda o comentario es bienvenido en los comentarios!

El resultado

My Trip Countdown

Puedes encontrar el ejemplo en GitHub:

Ver GitHub

Más información

[Xamarin.Forms] Extensiones de marcado personalizadas

Introducción

Las extensiones de marcado son una forma de obtener un valor que no sea específico de tipo primitivo o un objeto XAML. Mediante la apertura y cierre de llaves, se define en su interior lo que se conoce como extensión de marcado.

En este artículo, vamos a conocer como crear nuestras propias extensiones de marcado.

RECUERDA: Ya vimos en un artículo anterior el concepto de extensión de marcado así como las extensiones de marcado disponibles.

 

Extensiones de marcado personalizadas

Las extensiones de marcado implementan la interfaz IMarkupExtension. Esta interfaz sólo cuenta con un método a implementar:

public object ProvideValue(IServiceProvider serviceProvider)

Cuando se realiza el análisis del código XAML y el compilador encuentra una extensión de marcado, se lanza este método antes de establecer el valor de la extensión.

Crear extensiones

Vamos a realizar una de las extensiones de marcado más sencillas posibles con el objetivo de centrarnos a fondo en los conceptos clave de la extensión. Nuestra extensión nos permitirá utilizar colores RGB estableciendo directamente los valores para rojo, verde y azul.

En el método ProvideValue, por defecto, no obtenemos valores con IServiceProvider. Por lo tanto, para obtener los valores necesarios en nuestra extensión (rojo, verde y azul) vamos a necesitar utilizar propiedades.

public class RGBColor : IMarkupExtension
{
     public int Red { get; set; }

     public int Green { get; set; }

     public int Blue { get; set; }

     public object ProvideValue(IServiceProvider serviceProvider)
     {
          return Color.FromRgb(Red, Green, Blue);
     }
}

Creamos una clase que hereda de IMarkupExtension, implementamos el método ProvideValue que se lanzará a la hora de evaluar la expresión. Gracias a propiedades capturamos los valores deseados. La lógica del método ProvideValue se encarga de crear el valor devuelto deseado, un color.

Todo listo!

Utilizar extensiones

Utilizar la extensión es sencillo. Necesitamos acceder a la misma, por lo tanto, debemos declarar el espacio de nombres en XAML en el lugar a utilizar:

xmlns:extensions="clr-namespace:CustomMarkupExtension.Extensions;assembly=CustomMarkupExtension"

Y utilizarlo:

<Label
     Text="Test"
     TextColor="{extensions:RGBColor Red=255, Green=0, Blue=255}"
     BackgroundColor="{extensions:RGBColor Red=200, Green=100, Blue = 0}" />

Fíjate que todo el código queda bastante simple y muy legible. Tenéis el código fuente del ejemplo utilizado disponible en GitHub:

Ver GitHub

Mediante la implementación de IMarkupExtension podemos crear nuestras propias extensiones de marcado. Las extensiones de marcado personalizadas nos dan una forma sencilla y potente de ejecutar código, realizar validaciones y conversiones de valores antes de establecer el valor de una propiedad.

Más información

[Xamarin.Forms] Extensiones de marcado

Introducción

Xamarin.Forms añade una capa de abstracción en la capa de UI que nos permite definir la misma una única vez para todas las plataformas. Podemos definir esta interfaz con código C# o XAML.

A la hora de trabajar con la interfaz en XAML con casi toda seguridad utilizarás alguna extensión de marcado.

Extensiones de marcado

Las extensiones de marcado son una forma de obtener un valor que no sea específico de tipo primitivo o un objeto XAML. Mediante la apertura y cierre de llaves, se define en su interior lo que se conoce como extensión de marcado.

Existen extensiones de marcados muy usadas y conocidas como Binding o StaticResource. Pero… ¿conoces todas las extensiones disponibles y su uso?.

En este artículo, vamos a conocer todas las extensiones básicas existentes.

Binding

Estamos ante la expresión de marcado más utilizada. Cuando el código XAML se encuentra con un literal entre llaves, lo interpreta y no se limita a mostrarlo como si fuera un simple texto. En este caso hace una evaluación encontrando la palabra reservada Binding, lo que indica que debe enlazar el valor de una propiedad. Pero…¿cómo sabe dónde se encuentra la propiedad?, todos los elementos que componen la interfaz de usuario descienden de una importante jerarquía de clases base que otorgan funcionalidad vital.

Contamos con la propiedad BindingContext que actúa como contexto de datos del elemento. Si la propiedad fuese nula en el elemento, se buscaría de forma ascendente en la jerarquía de elementos. Es decir, si la propiedad BindingContext del elemento visual es nula, buscará en el elemento padre inmediato, etc.

Veamos un ejemplo.

public class Person
{
     public string Name { get; set; }
     public string SurName { get; set; }
}

Establecemos el BindingContext:

BindingContext = person;

A la hora de utilizar el Binding:

<Label Text={Binding Name}" />

En un enlace a datos, la palabra reservada Mode indica la forma en la que se comporta el mismo. En Xamarin.Forms contamos con los siguientes modos:

  • OneWay: es el valor por defecto. Indica que el enlace se realiza en un único sentido, de lectura. Aunque el valor del elemento visual cambie, no viajará hasta el  ViewModel.
  • OneWayToSource: en un único sentido generalmente de la View al ViewModel.
  • TwoWay: en este caso el enlace es bidireccional. La View toma el valor del ViewModel, pero si lo editamos en la vista, el cambio se envía de vuelta al ViewModel.
  • Default: el modo utilizado por defecto, que como hemos visto es OneWay.
<Entry Text={Binding Username, Mode=TwoWay}" />

Por otro lado, en ocasiones, lo que se desea mostrar en la interfaz de usuario no equivale al valor enlazado. Podríamos extender modelos añadiendo más propiedades para este propósito, pero no es lo ideal. Para solventar este problema contamos con las propiedades de formateo de texto.

<Label Text="{Binding Count, StringFormat='Number of Records = {0:N}'}"/>

Veamos otro ejemplo:

<Label Text="{Binding Date, StringFormat='{0:dd MM yyyy}'}" />

Converters

En ocasiones, el valor enlazado no es el deseado. Por ejemplo, tenemos enlazado un valor de tipo DateTime pero deseamos mostrar una cadena de texto al usuario con un formato de fecha aplicado. Justo para este objetivo, tenemos los TypeConverters.

En lugar de extender nuestros modelos con más y más valores, los Converters nos permiten transformar unos valores a otros directamente desde XAML.

<Label Text="{Binding Date}" />

En este caso veríamos el resultado de lanzar el método ToString del DateTime. Podemos crear un Converter:

public class DatetimeToStringConverter : IValueConverter
{
     public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
     {
          if (value == null)
               return string.Empty;

          var datetime = (DateTime)value;
          return datetime.ToLocalTime().ToString("g");
     }

     public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
     {
          throw new NotImplementedException(); 
     }
}

El Converter es una clase que herede de la interfaz IValueConverter, se implementa el método Convert y ConvertBack. Para utilizar el converter se debe definir primero como recurso:

<ResourceDictionary>
 <local:DatetimeToStringConverter x:Key="DateTimeConverter" />
</ResourceDictionary>

Y posteriormente, utilizar la palabra reservada Converter para acceder al mismo:

<Label Text="{Binding Date, Converter={StaticResource DateTimeConverter}}" />

NOTA: Se suelen utilizar los Converters para transformaciones más complejas.

TemplateBinding

Con la versión 2.1.0 de Xamarin.Forms se introdujo el concepto de Control Templates y también el de TemplateBinding. Un TemplateBinding es similar a un Binding, excepto que el origen de un TemplateBinding siempre se establece automáticamente en el padre de la vista de destino que posee la plantilla de control.

<ControlTemplate x:Key="PageTemplate">
     <StackLayout>
          <Label Text="{TemplateBinding BindingContext.HeaderText}" /> 
          <ContentPresenter />
     </StackLayout>
</ControlTemplate>

StaticResource

En cada elemento visual podemos definir un conjunto de recursos. Utilizamos ResourceDictionary para a gestión de recursos. Con StaticResource podemos acceder a objetos estáticos definidos como recursos y disponibles en tiempo de compilación.

<ContentPage.Resources>
 <ResourceDictionary>

     <Style x:Key="ErrorLabelStyle" TargetType="Label">
          <Setter Property="TextColor" Value="Red" />
     </Style>

</ResourceDictionary>
</ContentPage.Resources>

<Label Text="Hello!" Style="{StaticResource ErrorLabelStyle}" />

DynamicResource

Esta extensión de marcado es sumamente similar a la anterior pero cuenta con una gran diferencia. DynamicResource permite acceder a recursos añadidos en tiempo de ejecución. Es decir, si un recurso es eliminado / añadido en un diccionario de recursos en tiempo de ejecución, DynamicResource es tu extensión.

var errorLabelStyle = new Style(typeof(Label));

errorLabelStyle.Setters.Add(new Setter() 
{ 
     Property = Label.BackgroundColorProperty, Value = Color.Red 
});

this.Resources.Add(errorLabelStyle);

Tras añadir dinámicament el tiempo de ejecución el estilo, accedemos al mismo:

<Label Text="Hello!" Style="{DynamicResource ErrorLabelStyle}" />

Otras extensiones de marcado

Existen estensiones de marcado intrínsecas de XAML y soportadas en archivos XAML de Xamarin.Forms.

x:Array

Se pueden definir arrays directamente en XAML gracias a esta extensión de marcado.

<x:Array x:Key="StringArray" Type="{x:Type x:String}">
     <x:String>Hello</x:String>
     <x:String>World</x:String>
</x:Array>

x:Null

Extensión de marcado que permite establecer valor nulo. Si una propiedad cuenta con soporte a valores no nulos y se desea establecer por defecto a nulo, {x:Null} es la extensión de marcado idónea.

x:Reference

Los enlaces de datos se pueden definir para vincular propiedades de dos elementos visuales en la misma página. En este caso, se establece el  BindingContext del objeto de destino utilizando la extensión de marcado x: Reference.

<StackLayout>
     <Slider 
          x:Name="slider"
          Maximum="100" />
     <Label 
          BindingContext="{x:Reference slider}"
          Text="{Binding Value,
          StringFormat='The angle is {0:F0} degrees'}" />
 </StackLayout>

x:Static

A pesar de lo que pueda dar a pensar, x:Static y StaticResource NO son iguales. Mientras que StaticResource nos permite acceder a un objeto definido en un diccionario de recursos, x:Static nos permite el acceso a una propiedad o constante estática.

<Label 
     Text="Awesome!"
     TextColor="{x:Static Color.Blue}" />

NOTA: Esta extensión de marcado es más potente de lo que pueda parecer. También puedes acceder a campos o propiedades estáticas de tu propio código. Si se cuenta con una clase estática con constantes, se podrían utilizar.

x:Type

Permite establecer el tipo del objeto utilizando {x:Type Class}.

Extensiones de marcado personalizadas

Llegamos hasta aquí!. Cuando se comienza a desarrollar en XAML algunas son muy utilizadas y bien conocidas pero no así con todas. Xamarin.Forms también permite crear nuevas extensiones de marcado personalizadas aunque es algo que veremos en un próximo artículo.

Hasta la próxima!

Más información

[Tips and Tricks] Compilando XAML en Xamarin.Forms

Link - 05Introducción

Data binding es un mecanismo mediante el cual podemos enlazar los elementos de la interfaz de usuario con los objetos que contienen la información a mostrar. Cuando realizamos data binding, creamos una dependencia entre el valor de una propiedad llamada target con el valor de otra propiedad llamada source. Donde normalmente, la propiedad target recibirá el valor de la propiedad source.

En Xamarin.Forms es el mecanismo base que nos permite utilizar el patrón MVVM. Sin embargo, el mecanismo no es perfecto, cuenta con limitaciones, los errores de Binding no se producían en tiempo de compilación de la App además de tener diferentes mejoras relacionadas con el rendimiento.

Con la llegada de Xamarin.Forms 2.0 tenemos la posibilidad de compilar XAML.

XAMLC

Podemos compilar el XAML en lugar de ser interpretado (XAMLC). Las ventajas son múltiples:

  • Podemos detectar errores en marcado en tiempo de compilación.
  • Los tiempos de inicialización y carga se reducen.
  • Se reduce tamaño del ensamblado.

El compilado de XAML es una opción que por defecto viene deshabilitada con el objetivo de mantener con facilidad compatibilidad hacia atrás.

NOTA: En estos momentos XAMLC no es compatible con la plataforma Windows.

Podemos indicar que deseamos compilar una vista utilizando el atributo XamlCompilation a nivel de clase.

[XamlCompilation (XamlCompilationOptions.Compile)]

Le indicamos el valor XamlCompilationOptions.Compile. Si lo que queremos es compilar todas las vistas de una App, utilizaremos el prefijo assembly para el atributo XamlCompilation.

[assembly: XamlCompilation (XamlCompilationOptions.Compile)]

Todas las vistas incluidas en el namespace utilizado con el atributo quedarán afectadas.

Sencillo, ¿cierto?

Podéis acceder al código fuente directamente en GitHub:

Ver GitHubMás información