[Xamarin.Forms UI Challenge] TimelinePulse

Introducción

Según evoluciona de Xamarin.Forms, llegan más y más opciones que simplifican la creación de diferentes elementos de la interfaz de usuario.

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 Dribbble (por Anton Aheichanka), que intentaremos replicar con Xamarin.Forms paso a paso.

Timeline Profile

TimelinePulse

Vamos a intentar replicar la UI del diseño paso a paso en Xamarin.Forms.

Los retos del ejemplo

Vamos a comenzar haciendo un análisis de la interfaz de usuario desglosando los elementos que la componen:

  • Barra de navegación: Muestra el título centrado (y con una fuente específica) además de la imagen del perfil del usuario para permitir navegar rápidamente al perfil. Gracias a la propiedad TitleView de la NavigationPage, podemos añadir contenido personalizado y conseguir el resultado facilmente.
  • Cabecera: La cabecera muestra información relacionada con la fecha. Sin embargo, No hay nada complejo en la misma. Aplicar una imagen de fondo, y utilizar un Layout (por ejemplo, un Grid) para posicionar la información de la fecha con textos utilizando una fuente específica.
  • El botón para añadir: Aquí es donde vamos a tener la parte más compleja del ejemplo. ¿Por qué?. Queremos aplicar un efecto de pulso para llamar la atención del usuario. Tenemos varias formas de conseguir el resultado. Entre ellas contamos con el uso las APIs de animación de Xamarin.Forms junto con el uso de imágenes; SkiaSharp o bien Custom Renderers.
  • El listado: El listado cuenta con elementos que se pueden conseguir definiendo una celda personalizada. En la celda, en caso de reuniones se muestran los participantes. Ahora gracias a BindableLayout es algo sencillo.

NOTA: En este ejemplo nos hemos centrado en la vista con el timeline, por ese motivo el ejemplo no cuenta con el menu lateral deslizante u otras opciones.

Imágenes circulares

Tenemos muchas opciones para crear imágenes circulares. Entre las opciones disponibles destacan FFImageloading e ImageCirclePlugin. Sin embargo, no son las únicas opciones. A continuación, vamos a crear un pequeño control para tener imágenes circulares usando SkiaSharp.

Tras añadir SkiaSharp.Views.Forms a cada proyecto de la solución, creamos un nuevo control derivado de SKCanvasView:

public class CircularImage : SKCanvasView
{

}

Vamos a necesitar una propiedad para definir la imagen a utilizar:

public static readonly BindableProperty EmbeddedImageNameProperty =
     BindableProperty.Create(nameof(EmbeddedImageName), typeof(string), typeof(CircularImage), "", propertyChanged: OnPropertyChanged);

public string EmbeddedImageName
{
     get { return (string)GetValue(EmbeddedImageNameProperty); }
     set { SetValue(EmbeddedImageNameProperty, value); }
}

Lo que vamos a realizar, es aplicarle un Path a la imagen, recortando en la forma deseada, circular:

SKPath CircularPath = SKPath.ParseSvgPathData("M -1,0 A 1,1 0 1 1 1,0 M -1,0 A 1,1 0 1 0 1,0");

La clave es utilizar el método ClipPath junto con DrawBitmap:

canvas.Clear();

CircularPath.GetBounds(out SKRect bounds);
canvas.Translate(info.Width / 2, info.Height / 2);
canvas.Scale(0.98f * info.Height / bounds.Height);
canvas.Translate(-bounds.MidX, -bounds.MidY);
canvas.ClipPath(CircularPath);
canvas.ResetMatrix();

if (_resourceBitmap != null)
{
     canvas.DrawBitmap(_resourceBitmap, info.Rect);
}

Esto fue sencillo, ¿verdad?. Continuamos.

La barra de navegación

Pasamos a ver la barra de navegación. Utilizamos una NavigationPage donde con la propiedad BarBackgroundColor definimos de forma sencilla el color de fondo. ¿Y el contenido?.

Usamos la propiedad TitleView para definir el contenido personalizado:

<NavigationPage.TitleView>
     <Grid>
          <Label 
               Text="Timeline" 
               Style="{StaticResource BarTitleStyle}"/>
          <Grid
               HorizontalOptions="End"
               Margin="6, 0">
               <controls:CircularImage 
                    EmbeddedImageName="TimelinePulse.Resources.face1.jpg"/>
          </Grid>
     </Grid>
</NavigationPage.TitleView>

Listado

Llegamos al listado. No tiene nada especialmente complejo, pero vamos a ir desglosando cada bloque.

Cada elemento del listado es definido en el ItemTemplate del listado:

<ListView.ItemTemplate>
     <DataTemplate>
          <ViewCell>
               <templates:TaskItemTemplate />
          </ViewCell>
     </DataTemplate>
</ListView.ItemTemplate>

En la definición de cada elemento del listado tenemos una peculiaridad. En caso de reunión, mostramos las personas que asisten. Para ello, hacemos uso de BindableLayout introducido en Xamarin.Forms 3.5:

<StackLayout
     Orientation="Horizontal"
     BindableLayout.ItemsSource="{Binding People}">
     <BindableLayout.ItemTemplate>
          <DataTemplate>
               <Grid>
                    <controls:CircularImage
                         EmbeddedImageName="{Binding Photo}"
                         Style="{StaticResource PhotoStyle}"/>
               </Grid>
          </DataTemplate>
     </BindableLayout.ItemTemplate>
</StackLayout>

Botón con animación

Y llegamos a quizás el «eje» del ejemplo. Sin duda, la parte más detacada, el botón con la animación.

¿Cómo lo conseguimos?

Tenemos diferentes opciones, pero al igual que antes con las imágenes circulares, vamos a usar SkiaSharp.

Comenzamos creando un nuevo control, una nueva clase que herede de SKCanvasView:

public class PulseButton : SKCanvasView
{

}

Definimos algunas propiedades como:

  • EmbeddedImageName: Para poder establecer la imagen del botón.
  • PulseColor: Para definir el color de fondo del botón (circulo).
  • PulseSpeed: Ya que la clave es la animación, mejor tener control sobre la misma.

NOTA: Podríamos definir otras propiedades interesantes como IsAnimating para parar o lanzar la animación, Command para ejecutar una acción, etc. Ten en cuenta que es un ejemplo destinado a cubrir la UI, no una App real.

Para definir el botón, dibujaremos un círculo con el método DrawCircle y en caso de establecer una imagen vía propiedad (creada en el control de forma similar a como hicimos en el control CircularImage), la dibujaremos usando el método DrawBitmap.

canvas.Clear();

SKPoint center = new SKPoint(info.Width / 2, info.Height / 2);

paint.Color = new SKColor(R, G, B);
canvas.DrawCircle(center.X, center.Y, 85, paint);

if (_resourceBitmap != null)
     canvas.DrawBitmap(_resourceBitmap, center.X - _resourceBitmap.Width / 2, center.Y - _resourceBitmap.Height / 2);

Hasta aquí todo muy sencillo. Unas propiedades básicas y un dibujado de un circula e imagen. Con esto tenemos el botón básico, pero…¿y la animación Pulse?.

Vamos a utilizar un StopWatcher para obtener un valor que irá cambiando en base al número de milisegundos que han pasado:

_time = (float)(_stopwatch.Elapsed.TotalMilliseconds % speed / speed);

NOTA: Cada X tiempo debemos refrescar la UI para que el efecto de la animación sea correcto. Esto lo conseguimos con el método InvalidateSurface.

Lo que queda es simple, debemos dibujar otro circulo de igual forma que el anterior pero con un par de detalles:

  • Será mayor a mayor con el paso del tiempo.
  • La opacidad será menor con el paso del tiempo.
SKPoint center = new SKPoint(info.Width / 2, info.Height / 2);
float radius = info.Width / 2 * _time;

paint.Color = new SKColor(R, G, B, (byte)(255 * (1 - _time)));
paint.Style = SKPaintStyle.Fill;
canvas.DrawCircle(center.X, center.Y, radius, paint);

Fíjate en el código anterior. Usamos la variable _time (recuerda el StopWatch), para ajustar la opacidad y el radio.

El resultado final:

El resultado

Llegamos hasta aquí. Estamos ante un UI Challenge no muy complejo pero con detalles interesantes.  Espero que te haya resultado interesante. El próximo será mucho más complejo…

Recuerda, cualquier comentario es bienvenida en el artículo!.

Más información

[VS4Mac] SkiaSharp Fiddle

Introducción

SkiaSharpes un sistema de gráficos 2D para .NET y C# que utiliza el motor de gráficos de código abierto Skia usado en los productos de Google. Recientemente, Matthew Leibowitz creo una herramienta llamada SkiaSharpFiddle. Se trata de una herramienta para Windows que permite ver cambios en código SkiaSharp al vuelo ademas de tener resultados de compilación. Fantástica!.

SkiaSharpFiddle

Tras probarla y hablar con Matthew, seria genial tener la herramienta también en macOS, ¿cierto?.

SkiaSharp Fiddle

Llega a Visual Studio para macOS un nuevo addin que añade SkiaSharp Fiddle integrado en Visual Studio.

addin SkiaSharp Fiddle para VS4Mac

¿Cómo instalar?

Para instalar el addin, accedemos al menu principal de Visual Studio y elegimos la opción Extensions…

Buscamos por «SkiaSharp»y encontramos SkiaSharp Fiddle:

Instalar SkiaSharp Fiddle

Tras pulsar Install…, tendremos el addin instalado.

¿Qué aporta?

la principal ventaja del addin es poder previsualizar al vuelo, de forma inmediata código de SkiaSharp. La herramienta es ideal para crear y prototipar al trabajar con SkiaSharp. Además:

  • Podemos personalizar la superfie de dibujado (ancho y alto).
  • Realmente compilamos el código, en caso de error tenemos visualización directa de la línea donde hay error (o errores) junto con el mensaje de error.

Para acceder a la herramienta basta con acceder al menu Tools y seleccionar SkiaSharp Fiddle.

¿Cómo la utilizo?

Lo próximo

Estamos ante la primera versión (v0.1) con la funcionalidad básica.

¿Qué hay pensado para la próxima versión?.

La principal novedad que se incluirá en la próxima versión es IntelliSenseen el editor de código. Recibir ayuda a la hora de escribir el código autocompletado y facilitando la escritura. Además se añadirá elección de modo de color y se realizarán correcciones a errores o feedback recibido.

Puedes encontrar el código del addin en GitHub:

Ver GitHub

¿Qué te parece el addin?, ¿qué añadiríais (o quitarías)?. Recuerda, puedes dejar comentarios directamente en la entrada!

Más información