> Manuales > Ayudas técnicas

¿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:

  1. 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.
  2. 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.
  3. 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.

embeddedIE9

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

Antes de comenzar, quiero informar que todo el código que vamos a tratar en este artículo está disponible a través de Codeplex.

wp7bounceball320

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

Nota: Para poder realizar este tutorial es necesario tener instaladas las últimas herramientas de Windows Phone 7. En el momento de escribir este artículo estamos en la Beta 2 de la versión 7.1, comprobad si hay una versión más nueva:
http://www.microsoft.com/download/en/details.aspx?id=26648

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:

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.

Nota: Puedes obtener el código completo de ejemplo en Codeplex.

Juan Manuel Servera

Technical Manager en el Microsoft Innovation Center | Tourism Technologies

Manual