¿Sabes HTML, Javascript y CSS? ¿Has aprendido HTML5 recientemente? Entonces, ¡Ya sabes hacer aplicaciones móviles multiplataforma!
Una de las maneras más versátiles que tenemos para crear nuestra primera aplicación para múltiples dispositivos (IPhone, Android, WP7, etc.) es utilizar HTML + Javascript y adaptarlas un poco para cada uno de los dispositivos sobre los que queremos que funcione.
Como suele ocurrir, hay unas cuantas formas de crear aplicaciones HTML5 para móviles:
- Creando una aplicación HTML específica para pantallas pequeñas, podemos usar css condicionales para adaptar mejor el contenido. Al ser páginas con conexión no podremos usarlas cuando no dispongamos de conectividad en el dispositivo.
- Para solucionar el primer punto podemos utilizar el manifest.cache para almacenar la aplicación localmente. De esta manera podremos utilizar la aplicación web en modo desconectado, pero no funcionará en todos los dispositivos y tenemos un límite de memoria de 5 MB.
- La tercera opción, que además puede aprovechar características de las otras dos, es usar un control nativo para contener el HTML y distribuir la aplicación y todo su contenido como aplicación nativa desde el mercadillo de cada una de las plataformas.
Las dos primeras tienen la (dudosa) ventaja de poder olvidarnos de las normativas que exigen los fabricantes en sus tiendas online, pero dicha aplicación no podrá acceder a todos los elementos del dispositivo, sino sólo a los que nos permita el navegador de cada uno. Hay muchos estándares de HTML5 que aún están en borrador y por lo tanto cada fabricante decide si lo implementa o no.
Con la tercera opción tenemos la gran ventaja de poder ir más allá de las limitaciones del navegador y comunicarnos con las API del dispositivo de una forma más directa, pero manteniendo la ventaja de utilizar HTML5 y javascript para definir tanto el aspecto como el comportamiento de la aplicación.
Vamos a centrarnos en esta última opción y desarrollaremos un ejemplo sobre la plataforma Windows Phone 7. Para las otras plataformas de desarrollo sólo cambiará en el contenedor, la aplicación HTML será siempre igual.
Tutorial: La aplicación HTML5
Más adelante veremos cómo nuestra aplicación Silverlight se comunicará con la aplicación HTML. Por ahora veamos qué va a hacer nuestra aplicación HTML5. Vamos a crear un canvas donde dibujar una pelota que se mueva según la inclinación del dispositivo. Para ello necesitaremos usar el evento devicemotion definido en un draft de HTML5 y utilizaremos esos valores para dar aceleración a la pelota.
<!DOCTYPE html><html>
<head>
<title>Bouncing Ball Animation</title>
<link href="main.css" rel="stylesheet" />
<script type="text/javascript" src="WP7Bridge.js" ></script>
<script type="text/javascript">
//holds the drawing context of the canvas element
var context;
//loads the bounce sound for the ball
var sound;
//size of canvas
var width = 800;
var height = 800;
//position of the ball
var x = 400;
var y = 40;
//speed of the ball, initially 0
var dx = 0;
var dy = 0;
//ball radius
var ballRadius = 35;
function init() {
context = document.getElementById("playCanvas").getContext("2d");
sound = new Audio("107785__hans__tink.mp3");
sound.load();
playSound(0); //this avoids lag the first time
//keep calling draw function after every 20 milliseconds
setInterval(draw, 20);
}
//store old values to know if there's a real change
var oldx, oldy;
function draw() {
//change the position by delta
x += dx;
y += dy;
//have some small friction
dx *= 0.95;
dy *= 0.95;
var xcol = false;
var ycol = false;
//collision detection and bounce
if (x + ballRadius > width) {
dx = -dx/2.0;
x = width - ballRadius;
xcol = true;
} else if (x < ballRadius) {
dx = -dx/2.0;
x = ballRadius;
xcol = true;
}
if (y + ballRadius > height) {
dy = -dy / 2.0;
y = height - ballRadius;
ycol = true;
} else if (y < ballRadius) {
dy = -dy / 2.0;
y = ballRadius;
ycol = true;
}
if (xcol && x != oldx && Math.abs(dx) > 1) {
playSound(Math.min(1, 0.75 + (Math.abs(dx) * 0.05)));
}
else if (ycol && y != oldy && Math.abs(dy) > 1) {
playSound(Math.min(1, 0.75 + (Math.abs(dy) * 0.05)));
}
//store old values for comparing and perform click
oldx = x;
oldy = y;
//clears everything
clear();
//draws external rectangle
rectangle(0, 0, width, height);
//draws the ball at its current position
circle(x, y, ballRadius);
}
//draws the circle with center location at x,y
function circle(x, y, radius) {
context.fillStyle = "#444444";
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, true);
context.closePath();
context.fill();
}
//draws a rectangle with width(w) & height(h) with top left corner at (x,y)
function rectangle(x, y, w, h) {
context.fillStyle = "#000000";
context.lineWidth = "4";
context.strokeRect(x, y, w, h);
}
//clears the whole canvas
function clear() {
context.clearRect(0, 0, width, height);
}
function motionChanged(event) {
//change delta increment by gravity
dx += event.accelerationIncludingGravity.x/10.0;
dy += -event.accelerationIncludingGravity.y/10.0;
}
function playSound(volume) {
sound.volume = volume;
sound.play();
}
window.addEventListener("load", init, true);
window.addEventListener("devicemotion", motionChanged, true);
</script>
</head>
<body>
<header>Hello HTML5</header>
<div id="wrapper">
<canvas id="playCanvas" width="800" height="800" >
</canvas>
</div>
</body>
</html>
Hasta ahora hemos escrito una aplicación HTML simple y con sólo una línea "sospechosa", la que apunta a un script llamado WP7Bridge.js. Es el script que usaremos para hacer de puente entre el motor javascript y las API del teléfono.
Dentro de algún tiempo desde HTML5 tendremos acceso a gran parte de las apis y sensores que traen los nuevos dispositivos (cámara, brújula y giroscopio, etc.), pero hay que tener en cuenta que muchas de estas API aún están en borrador, algunos navegadores no la implementan y otros tienen que actualizarse muy a menudo porque el API va cambiando, dejando nuestras aplicaciones sin esa funcionalidad como pasó con el API de WebSocket no hace mucho.
En el caso de Windows Phone 7 tenemos acceso al API de geolocalización desde HTML5, pero aún no se ha implementado el acceso al acelerómetro. Para poder comunicarnos con el acelerómetro del dispositivo vamos a usar un método que ejecutará el evento devicemotion como si fuera un evento nativo, utilizando el método window.dispatchEvent:
// js receiver for the accelerometer
// from the phone, fires the devicemotion event
// as defined in http://www.w3.org/TR/orientation-event/
function wp7ExtensionsaccelerometerChanged(x, y, z, intervalms) {
//here we create a fake deviceorientation event!
var event = document.createEvent("Event");
event.initEvent("devicemotion", true, true);
event.acceleration = null;// new Acceleration();
event.accelerationIncludingGravity = new Acceleration();
event.accelerationIncludingGravity.x = parseFloat(x);
event.accelerationIncludingGravity.y = parseFloat(y);
event.accelerationIncludingGravity.z = parseFloat(z);
event.rotationRate = null;// new RotationRate();
event.interval = parseInt(intervalms);
window.dispatchEvent(event);
};
Este método lo almacenaremos en el fichero WP7Bridge.js y desde la parte nativa de la aplicación del dispositivo llamaremos a este método cada vez que se produzca un cambio en el acelerómetro. De esta manera podremos crear cualquier puente entre las capacidades del dispositivo y las aplicaciones HTML5. Para el caso del WP7 vamos a crear una aplicación Silverlight, que tiene acceso a los sensores del dispositivo, y que contendrá un control WebBrowser donde alojaremos nuestra aplicación.
Tutorial: La aplicación Silverlight
Como ya os he comentado, nuestra aplicación HTML5 tendrá que vivir dentro de una aplicación del dispositivo, de esta manera podremos empaquetarla y distribuirla como cualquier otra aplicación. Para ello crearemos una solución de tipo Windows Phone con las herramientas e insertaremos un elemento WebBrowser en el Grid principal.
<!--ContentPanel - place additional content here--><Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<phone:WebBrowser x:Name="webBrowser"
Navigated="webBrowser_Navigated"
IsScriptEnabled="True"
IsGeolocationEnabled="False"
Source="html/default.htm"
/>
</Grid>
Veréis que el control tiene una serie de propiedades:
- IsScriptEnabled: la necesitaremos activada para poder ejecutar javascript en la página.
- IsGeolocationEnabled: nos habilitará el GPS y podremos usar las funciones Javascript de geolocalización (navigator.geolocation.getCurrentPosition)
- Source: apunta a la página HTML que queremos abrir.
En el código de la página XAML que contiene al WebBrowser crearemos nuestro puente que nos permitirá realizar llamadas al código Javascript. Crearemos una instancia del acelerómetro y en cada evento llamaremos a un método del componente browser que nos permitirá llamar a cualquier script definido en la página WebBrowser.InvokeScript.
public class Html5Bridge:IDisposable{
Accelerometer _accelerometer;
WebBrowser _browser;
public Html5Bridge(WebBrowser browser, bool enableAccelerometer)
{
_browser = browser;
browser.ScriptNotify += browser_ScriptNotify;
if (enableAccelerometer)
{
_accelerometer = new Accelerometer();
_accelerometer.CurrentValueChanged += _accelerometer_CurrentValueChanged;
_accelerometer.Start();
}
}
void _accelerometer_CurrentValueChanged(object sender, SensorReadingEventArgs e)
{
Vector3 v = e.SensorReading.Acceleration;
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
_browser.InvokeScript("wp7ExtensionsaccelerometerChanged",
(v.X * 9.81).ToString(System.Globalization.CultureInfo.InvariantCulture),
(v.Y * 9.81).ToString(System.Globalization.CultureInfo.InvariantCulture),
(v.Z * 9.81).ToString(System.Globalization.CultureInfo.InvariantCulture),
_accelerometer.TimeBetweenUpdates.Milliseconds.ToString(System.Globalization.CultureInfo.InvariantCulture)
);
});
}
...
Para finalizar, en WP7.5 tendremos que usar algún truco más para poder cargar el contenido HTML local en el WebBrowser, pues no tenemos permiso para acceder directamente a los archivos que incluye nuestro archivo XAP. Para solucionarlo basta con copiar esos archivos al sistema de ficheros local justo en el momento de arranque de la aplicación:
private void Application_Launching(object sender, LaunchingEventArgs e)
{
string[] files = new string[] {
"107785__hans__tink.mp3",
"default.htm",
"WP7Bridge.js",
"main.css" };
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
store.CreateDirectory("html");
foreach (string file in files)
{
var info = Application.GetResourceStream(new Uri("html/" + file, UriKind.Relative));
if (info != null)
{
using (var writeStream = new IsolatedStorageFileStream("html" + file, FileMode.Create, store))
{
byte[] bytes;
using (var binaryReader = new BinaryReader(info.Stream))
{
bytes = binaryReader.ReadBytes((int)info.Stream.Length);
}
using (var writer = new BinaryWriter(writeStream))
{
writer.Write(bytes);
}
}
}
}
}
}
Conclusiones
Escribiendo un mínimo de código para la plataforma, el cual será común al resto de nuestras aplicaciones HTML5, hemos conseguido que nuestra aplicación HTML5 funcione en Windows Phone 7.5. Para los demás dispositivos será muy parecido. Así suelen hacerlo plataformas como phonegap para permitir ejecutar el mismo código en todos los dispositivos.
Juan Manuel Servera
Technical Manager en el Microsoft Innovation Center | Tourism Technologies