Cámara a página completa en Xamarin.Forms
Hace ya un tiempo desde que programé la aplicaciónCharpHat, que es una aplicación que permite tomar una foto de cualquier cosa y ponerle un birrete de C# en ella. La aplicación no era perfecta, pero me ayudó a practicar el uso de los custom page renderers.
Ahora, decidí retomar el proyecto, pero en esta ocasión con la meta de aislar el código necesario para crear la interfaz y funconalidad de la cámara, de tal modo que cualquiera que quisiera implementar una cámara en su aplicación lo pudiera reutilizar en sus proyectos. Asegúrate de descargar el código fuente para este post.
Abstracciones de Forms
Aquí está el código fuente para esta sección.
Comencemos creando la página de Forms que nos servirá como medio de interacción con el código de clada plataforma:
Nada fuera de lo normal, crea una clase que derive de ContentPage
. Añadí un event handler ya que que queremos obtener la foto tomada por el usuario. Ahora, agreguémos algunos métodos para llamar cada vez que el usuario realice una acción en la página (en este caso, el usuario podrá tomar una foto o cancelar la acción):
Como referencia, mira las propiedades dentro de la clase PhotoResultEventArgs
:
Ahora es momento de seguir al código específico de cada plataforma.
In Xamarin.iOS
Aquí está el código fuente para esta sección.
Para ser sincero, la implementación en iOS es la más sencilla por mucho. Comenzamos por crear una clase que derive de PageRenderer
y le añadimos el atributo ExportRenderer
:
Ahora, y esto es muy importante, tienes que sobreescribir el método ViewDidLoad
dado que es llamado tan pronto nuestra página es cargada por el sistema operativo. Para organizar un poco mejor el código, partamos el código en varios métodos:
SetupUserInterface
Tal y como el nombre lo dice, aquí es donde tenemos que construir la interfaz. Como podrás haber adivinado, todo se hace con código, pero no te preocupes, es muy sencillo en tanto tu interfaz no sea muy complicada, pero puedes hacer lo que tu quieras aquí.
Para este ejemplo la interfaz consistrá de un par de botones y una superficie donde la vista en vivo de la cámara se mostrará, así que declara las siguientes campos a nivel de clase:
Ahora para poner los controles en su lugar necesitas pensar como si estuvieras trabajando con un relative layout, lo que significa que hay que poner la posición del control en la pantalla. Por ejemplo, observa cómo se ubica la vista de la cámara:
SetupEventHandlers
Ahora que ya hemos terminado la interfaz, podemos conectar los manejadores de eventos a cada control, por suerte en nuestro ejemplo únicamente tenemos dos botones en la pantalla: para tomar fotos y para cancelar la captura.
La propiedad Element
contiene una referencia a la página asociada con el renderer y es nuestro medio de interacción con el proyecto de Forms. Si te preguntas sobre el método CapturePhoto
, lo veremos más adelante.
AuthorizeCameraUse
Ahora es momento de solicitar permiso al usuario para usar su cámara.
Pero antes de ejecutar el código anterior tienes que agregar la entrada Privacy - Camera Usage Description
al archivo Info.plist en tu proyecto.
SetupLiveCameraStream
Ahora la parte “complicada”.
Comienza delcarando a nivel de clase una AVCaptureSession
, AVCaptureDeviceInput
y AVCaptureStillImageOutput
, ya que estos nos ayudaran a acceder a la cámara, mostrarla en vivo, y capturar la imagen.
Entonces dentro de SetupLiveCameraStream
, inicializa una sesión de captura, crea una capa de previsualización del mismo tamaño que nuestra liveCameraStream
, y añádela como una subcapa de esta:
Después, “crea” un dispositivo de captura (que puedes configurar de acuerdo a tus necesidades). Y entonces de éste dispositivo crea una entrada para la sesión de captura:
Ya tenemos una entrada (la cámara del dispositivo), ahora necesitamos una salida, que será una fotografía en formato jpeg:
Terminamos estableciendo la entrada y salida de la sesión de captura y la iniciamos:
CapturePhoto
Finalmente la cereza del pastel. El código para capturar la foto. En sí, el código es bastante simple: Toma la salida y obten una imagen fija de ella, ya que nosotros solo necesitamos los bytes (NSData
) que contenga la foto tomada:
In Xamarin.Android
Aquí está el código fuente para esta sección.
Esta implementación no es tan limpia como en iOS. Principalmente porque Android hace más énfasis en el uso de listeners que en manejadores de evento. Como sea, ese no es un problema para nosotros.
Como en iOS, comienza creando una clase que derive de PageRenderer
y también haz que implmente la interfaz TextureView.ISurfaceTextureListener
. No olvides el atributo ExportRenderer
:
Después, sobreescribe el método OnElementChanged
(si ya has trabajado con custom renderers antes, este método te puede parecer familiar), ya que este será llamado cada vez que una CameraPage
es mostrada en la pantalla:
SetupUserInterface
En este método se debe crear la interfaz de la cámara, puedes hacerlo mediante un archivo axml e “inflarlo” con los mecanismos de Android… o, como en este ejemplo, crearlo en código.
Para este ejemplo vamos a necesitar un RelativeLayout
como contenedor, un TextureView
para mostrar la cámara y un botón para tomar la foto. Declara todo a nivel de clase:
Solamente falta instanciarlos y añadirlos a la pantalla, por ejemplo, observa cómo se crea el contenedor y se le añada el control TextureView
:
Antes de continuar hay otro método que debemos sobreescribir para darle al contenedor su tamaño (y también lo podemos usar para acomodar la interfaz correctamente):
SetupEventHandlers
Como dije antes, Android depende mucho de event listeners en lugar de event handlers, así que este método es muy sencillo. Necesitamos colocar un manejador de evento al botón que tomará la foto, asi como asignar un escuchador que estará atento al estado del control SurfaceTexture
(¿Recuerdas que nuestro renderer implementa una interfaz?):
Ah, y otra cosa, vamos a sobreescribir el comportamiento del botón “Atrás” para que funcione para cancelar la fotografía:
TextureView.ISurfaceTextureListener implementation
Ahora toca implementar el núcleo de la página. Comienza por escribir el código para el método OnSurfaceTextureAvailable
en donde vamos a preparar la salida de la cámara… pero primero necesitamos una cámara, ¿cierto?
A nivel de clase declara una Camera
:
Dentro del método abre la cámara (por default tomará la cámara trasera del dispositivo) y obtén sus parámetros. Los necesitamos para elegir el tamaño de previsualización correcto porque queremos que se vea bien en nuestra app:
Una vez que tenemos los parámetros a mano, podemos obtener los PreviewSizes
y de ellos elegir el que mejor se ajusta a nuestras necesidades. En este caso estoy usando una simple expresión linq para obtener el mejor tamaño de acuerdo a la relación de aspecto:
Termina estableciendo el valor de surface
como la textura de previsualización, una vez hecho esto, lo único que queda por hacer es iniciar la cámara:
Hay otro método en el que debemos escribir código, este es OnSurfaceTextureDestroyed
en donde debemos detener el uso de la cámara, así que únicamente escribe el código siguiente en él:
StartCamera and StopCamera
Este par de métodos son bastante simples también, para StartCamera
únicamente tenemos que rotar la previsualización para hacer que se vea correctamente en la pantalla (en este caso está establecido para que se vea verticalmente), y terminamos iniciando la cámara:
El métodod StopCamera
detiene la previsualización y libera la cámara para que otras aplicaciones puedan acceder a ella:
TakePhoto
Para tomar una foto, lo que hay que hacer es obtener una imagen fija de lo que se muestra en el video en vivo dentro de la TextureView
, aquí está el código para hacerlo y regresar los bytes correspondientes:
Y así es como después de tanto código podemos hacer uso de la cámara. Sigue leyendo para encontrar un ejemplo de uso:
Usage in Forms
Si descargas el código fuente y lo ejecutas, verás algo como esto:
Acknowledgements
El código para este post está basado completamente en el código de la CharpHat app, que a su vez está basado en el código de la app Moments de Pierce Boggan.