Con CharpHat toma una foto y muéstrale al mundo que te gusta C#, CharpHat te permite colocarte un birrete con mucho estilo.
Ha pasado ya un buen rato desde que tuve el deseo de hacer esta aplicación para móvil, y es que la idea me llegó desde que comencé en el mundo de Xamarin. Pero al tener muy poca experiencia siempre había retrasado la idea. En fin, en este post te voy a explicar cómo es que hice la app con Xamarin.Forms y ayuda de los `custom renderers`. ### El proyecto Para comenzar, el proyecto es una aplicación de Xamarin.Forms utilizando un proyecto compartido que cree desde Visual Studio. Una vez creado, deja cuatro proyectos en la solución, esta vez me enfocaré solamente en el proyecto de Android ya que es el que ya está desarrollado por completo. Primero, les muestro el proyecto compartido:

Para darle un poco de organización dividí el código en carpetas, están:

  • Controls, contiene las abstracciones de controles que se deben implementar específicamente para cada plataforma, como es el caso de StickerableImage, que veremos más adelante.
  • Helpers, contiene recursos que se usan en toda la aplicación, como colores o estilos.
  • Pages, contiene las páginas que se usan a lo largo de toda la app, incluyéndo CameraPage que debe ser implementada por nosotros para cada plataforma, que veremos más adelante.
  • Services, contiene las abstracciones de caracterísitcas que también deben ser implementadas para cada plataforma.
  • ViewModels, contiene las clases que definen la lógica de la aplicación.
### CameraPage La funcionalidad de la aplicación depende completamente de la posibilidad de tomar fotografías para después poderlas modificar, aquí es donde la cosa se pone un poco seria, puesto que dentro de todas las bondades que nos ofrece Xamarin.Forms, no está la posibilidad de tomar fotografías con código 100% compartido. Por ello se tiene que escribir código para cada plataforma, en este caso usando Xamarin.Android, es decir, **sigue siendo código en C#**.

La implementación de la cámara es completamente una implementación basada en la que hizo Pierce Boggan en su aplicación Moments. Para usar un custom renderer básicamente lo que se requiere es:

Declarar la abastracción, en este caso es dentro de la carpeta Pages del proyecto compartido, en este caso BasePage deriva de ContentPage, esto es importante ya que es una manera de indicar qué es lo que vamos a implementar en la plataforma.

public class CameraPage : BasePage
{
	public CameraPage() { }
}

Implementar individualmente, para realizar esto se debe crear una clase dentro del proyecto para el cual queremos implementar el control dentro de la plataforma, a notar que CameraPage deriva de PageRenderer (e implementa ISurfaceTextureListener, pero ese ya es otro tema):

public class CameraPage : PageRenderer, TextureView.ISurfaceTextureListener

Dentro de esta clase, debemos sobreescribir el método OnElementChanged, ya que es donde se debe modificar el el control de acuerdo a nuestras necesidades, para este caso, en ese método de carga un layout que contiene la definición de la página en XML para Android y se añaden los manejadores de eventos. Para terminar, debemos indicar a Xamarin.Forms que nuestra clase derivada de PageRenderer debe ser usada para renderizar la página de la cámara en el dispositivo, esto se hace mediante el atributo ExportRenderer:

[assembly: ExportRenderer(typeof(CharpHat.Pages.CameraPage), typeof(CharpHat.Droid.Pages.CameraPage))]

La implementación de la cámara usa las APIs viejas de Android, y no es para nada distinto de lo que se haría para implementar una funcionalidad similar usando Java, pero en este caso estarías usando fabuloso C#.

StickerableImage

Otra de las características que tuvo que ser implementada de manera individual para cada plataforma fue la capacidad de añadir y manipular "stickers" usando nuestros dedos. Para ello, se deben seguir los pasos anteriores, es decir:

Declarar la abstracción

public class StickerableImage : View

En este caso, añadí dos propiedades a esta clase que permitirán manipular el tamaño (ScaleFactor) y el ángulo (RotationFactor) en el código del proyecto compartido, además las declaré como propiedades "bindeables", para poderlas usar desde XAML o con los ViewModels.

BindableProperty ScaleFactorProperty;
BindableProperty RotationFactorProperty;

Implementar individualmente, para este caso, no se usa un renderizador conocido (como podría ser PageRenderer o ButtonRenderer), sino que se utiliza ViewRenderer en el que debemos especificar: el tipo del control en Xamarin.Forms y el tipo de control en la plataforma nativa (en este caso es Xamarin.Android)

public class StickerableImageRenderer : ViewRenderer<StickerableImage, StickerView>

Con el código anterior, lo que estamos diciendo es algo como: Cuando sea Android, reemplaza StickerableImage por StickerView, donde StickerView es un control del tipo Android.Views.View. Una vez hecho esto, únicamente resta indicarle a Forms que queremos usar ese Renderer para ese tipo de control, para ello usamos fuera del namespace:

[assembly: ExportRenderer(typeof(CharpHat.Controls.StickerableImage), typeof(CharpHat.Droid.Controls.StickerableImageRenderer))]

StickerView

Es un View que lo que hace es estar al pendiente de cuando el usuario toca la pantalla y dibuja en ese punto el peculiar birrete de C#, también permite modificar el tamaño y el ángulo de este, para poder acomodarlo de acuerdo a lo que indique el usuario.

IPictureManager

Almacenar imagenes en el dispositivo también es algo que requiere de código específico para cada plataforma, es por eso que cree esta interfaz que será implementada independientemente y hará uso del Servicio de Dependencias de Xamarin.Forms. La interfaz expone un único método SavePictureToDisk, que toma los bytes de la imagen, el nombre que se le asignará y la carpeta en que se debe guardar. De nuevo, seguimos los pasos de siempre:

Declarar la abstracción

public interface IPictureManager
{
	string SavePictureToDisk (string filename, string folder, byte[] imageData);
	// ...

Implementar individualmente

public class PictureManager : IPictureManager
{
	public string SavePictureToDisk(string filename, string folder, byte[] imageData)
	{
		var dir = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryPictures);
		string picsFolder = System.String.Empty;
		// ...

Para usar el servicio de dependencias de Xamarin.Forms, debemos utilizar DependencyAttribute fuera del nombre de espacio en nuestra clase, como en la siguientes líneas:

[assembly: Xamarin.Forms.Dependency(typeof(CharpHat.Droid.Services.PictureManager))]
namespace CharpHat.Droid.Services

IScreenshotService

Para el caso de IScreenshotService se realizan pasos similares que para IPictureManager. La implementación de IScreenshotService permite capturar la pantalla que está viendo el usuario, úitil para cuando el birrete está en su posición y se quiere guardar esa imagen.

Conclusiones

Como podemos ver, hay ciertas veces en que no nos podemos librar de escribir código para cada plataforma, sin embargo la gran mayoría de nuestra aplicación permanece compartida entre las tres plataformas. Son las peculiaridades como el acceso a la cámara del dispositivo y la captura de pantalla las que requieren de código C# aparte.

Código compartido

Al momento de la publicación de este post, más de la mitad del código es compartido por la aplicación de Android, sin embargo estoy buscando maneras de reducir el porcentaje de código específico para cada plataforma.

Por último, no te olvides de descargar la aplicación en Google Play o descargar el código fuente en GitHub. Y espera pronto la versión para iOS y Windows Phone.