[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] Recopilación de ejemplos con interfaz de usuario atractiva

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

La comunidad de Xamarin.Forms, en los últimos tiempos, ha realizado gran cantidad de ejemplos reproduciendo desde patrones de diseño muy habituales hasta aplicaciones destacadas en cada tienda de aplicaciones y muy utilizadas.

Recopilación de ejemplos

Cada ejemplo reproduciendo una aplicación real o un apartado concreto suele contener un gran conjunto de pequeños detalles, Custom Renderers o efectos. Aspectos como:

  • Añadir logo personalizado en la parte superior.
  • Barra superior transparente.
  • Personalizar mapas.
  • Etc.

El volumen de ejemplos está creciendo (¡y seguirá creciendo!) así que, ¿y si hacemos una recopilación?. Se ha creado un repositorio en GitHub donde se van a ir recopilando todos los ejemplos que busquen como objetivo mostrar como conseguir interfaces de usuario atractivas con Xamarin.Forms.

NOTA: Se buscan ejemplos de código abierto que puedan ayudar al resto de usuarios de la comunidad a conseguir resultados similares.

De cada ejemplo recopilado se buscará:

  • Incluir enlace al autor.
  • Incluir enlace a blog relacionado con el ejemplo (si existe).
  • Enlace al código fuente.
  • Imágenes, gifs o videos.
  • Descripción y principales características.

¿Qué te parece la idea?, ¿echas en falta más información en cada ejemplo?.

Más información