Midlets Gráficos e interfaz de usuario (javax.microedition.lcdui) 

F. Javier García Castellano
Web: http://decsai.ugr.es/~fjgc, Mail: fjgc@decsai.ugr.es
IndiceInicioPerl

(C) Decsai
Web: http://decsai.ugr.es

1.- Introducción al Interfaz de usuario de MIDP

J2SE nos proporciona dos bibliotecas gráficas bastante ricas: AWT y SWING, pero utilizar estas dos bibliotecas en dispositivos móviles es impracticable. Dejando a un lado los recursos de memoria que pueden suponer estas toolkits gráficos, tenemos que pensar que los dispositivos sobre los que vamos a trabajar, tienen unas características mínimas como son una pantalla 96x54 píxeles y 1 bit de profundidad de color (blanco y negro). También tenemos que pensar que el estándar MIDP no soporta ningún dispositivo apuntador, como un ratón, por lo que en estas condiciones se puede hacer bastante complicado trabajar con varias ventanas a la vez. Es por ello que la especificación MIDP dejó de un lado AWT y SWING y creo un pequeño conjunto de clases adaptadas a los dispositivos móviles, un conjunto reducido y bastante sencillo de utilizar.

Hay que tener en cuenta que aunque el dispositivo permita la ejecución de más de un midlet a la vez, sólo uno puede tener el control de la pantalla. Si queremos que nuestra aplicación permite tener más de una "ventana" seremos nosotros quien tenga que programar la capacidad de nuestra aplicación de mover de una ventana a otra.

Dentro de MIDP tenemos dos alternativas a la hora de realizar el interfaz gráfico de nuestros midlets, usar componentes del API de alto nivel para interfaces de usuario o el API de interfaces de usuario de bajo nivel.

Con el API de interfaz de usuario de alto nivel es el dispositivo (o más bien la máquina virtual del dispositivo) quien se encarga de colocar los componentes, las barras de desplazamiento, la navegación y características visuales como el color, las formas, los fuentes y los elementos a visualizar. Además tiene un sistema de entrada de alto nivel asociado.

Con el API de interfaz de usuario de bajo nivel la aplicación tiene un control mayor sobre la pantalla. El motivo de este API es para aquel tipo de aplicaciones que necesitan un control y precisión absoluto a la hora de dibujar elementos en pantalla, como son los juegos (que son el tipo de aplicaciones que copan la mayor parte del mercado del software para móviles). Un mayor control, significa también una mayor responsabilidad: hay que dibujar todo lo que aparece en pantalla e interpretar cualquier entrada del usuario. Evidentemente, este API también tiene asociado un sistema de entrada de bajo nivel. También hay que tener en cuenta que cualquier aplicación que use este API, puede no ser portable o muy poco portable, debido a que tiene que comprobar los recursos del dispositivo, por ejemplo, tenemos que saber el tamaño de la pantalla para no dibujar píxels más allá de los límites.

La biblioteca de interfaz de usuario, tanto la de alto nivel como la de bajo nivel se encuentran en el paquete javax.microedition.lcdui. Antes de entrar en cada una de las partes mencionadas es necesario que aprendamos la forma de acceder a la pantalla del dispositivo.

2.- Las clases Display y Displayable

La clase javax.microedition.lcdio.Display representa el controlador lógico de la pantalla del dispositivo, donde el Midlet colocará su interfaz. Es la clase responsable de controlar la pantalla y la interacción con el usuario. El objeto Display no se crea, se obtiene con el método getDisplay(). Dicho método nos devuelve una referencia al objeto Display y para cada Midlet que se esté ejecutando en el dispositivo hay una sóla instancia de Display, es decir, si ejecutamos más de una vez el método getDisplay() nos devolverá siempre el mismo valor.

public static Display getDisplay (MIDlet midlet);

El método getDisplay() se suele ejecutar una sóla vez en el constructor y almacenar la referencia al Display, en lugar de ejecutar continuamente el método getDisplay(). Posteriormente en el método startApp() se utiliza el Display para crear la interfaz de usuario del Midlet.

Cada pantalla que el Midlet necesita dibujar usando componentes del interfaz gráfico ( o, mejor dicho, ítems en el argot de MIDP) o usando el API de bajo nivel, es una pantalla derivada de la clase abstracta Displayable, es decir, en el Display, sólo se puden usar objetos que herden de ls clase Displayable. Una clase Displayable no será visible hasta que no se use el método setCurrent() de la clase Display.

public void setCurrent (Displayable displayable);

Si se quiere obtener la pantalla que había antes se obtiene con el método getCurrent() de la clase Display.

public void Displayable getCurrent ();

Hay que tener en cuenta que el efecto de llamar a setCurrent() no está garantizado que sea inmediato, sino cuando el dispositivo pueda. Es decir, si encadenamos los métodos setCurrent() y getCurrent(), puede que el segundo no nos devuelva el mismo Displayable que le que hemos colocado. También puede implicar que llamar a setCurrent() y realizar acto seguido una operación que consuma muchos recursos del dispositivo, por ejemplo una conexión a red, nos puede producir como resultado que se vea la pantalla anterior, por ello, para este tipo de casos sería aconsejable lanzar una hebra que se encargara de realizar la operación.

La clase Display no se corresponde exactamente con la pantalla del dispositivo, ya que cuando se está en estado Pausado aunque se intente escribir en la pantalla no tendremos acceso a ella, y el resultado no será visible hasta que el Midlet esté otra vez activo.

Podemos saber si un objeto Displayable está visible usando el método isShown() de la clase abstracta Displayable. Que devuelve true sólo cuando el objeto Displayable se ha mostrado en la pantalla del dispositivo.

public boolean isShown ();

3.- La clase Displayable

Displayable es la clase base para todos los interfaces de usuario de un Midlet, pero como es abstracta no la podemos utilizar. Las clases que realmente podemos utilizar para realizar interfaces de usuario son sus clases derivadas, cuya jerarquía se muestra en la figura 2.

Figura 2. Jerarquía de clases Displayable.

Las dos clases que descienden de Displayable: Screen y Canvas, son también abstractas. Estas clases se corresponden con el API de alto nivel o el de bajo nivel del interfaz de usuario, respectivamente.

La clase Canvas es una pantalla en la cual el Midlet puede dibujar directamente. A la hora de crear un interfaz de bajo nivel para nuestro Midlet tendremos que crear una subclase de Canvas y redefinir el método paint(), para dibujar directamente en la pantalla del dispositivo.

La clase Screen es la clase base que usaremos en nuestro API de alto nivel del interfaz de usuario. No tenemos que heredar de ella, a diferencia de Canvas y extiende la clase Displayable para que muestre un título de aplicación y un mensaje deslizante (ticker). La subclase más utilizada de Screen es Form, donde podremos colocar nuestros ítems. Aunque del objeto Display sólo puede haber una instancia, del objeto Screen puede haber muchas, aunque sólo se puede mostrar una pantalla cada vez.

La clase Displayable define los métodos addCommand(Command cmd), removeCommand(Command cmd) y setCommandListener(CommandListener l) que heredan todas sus subclases y que nos va a permitir tratar los eventos que se produzcan.

Ejemplo de Hola Mundo con el API de alto nivel.

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

public class HolaMundoAlto extends MIDlet implements CommandListener {
    private Display display;
    private Form form;
    private Command salir; 

    //Constructor
    public HolaMundoAlto(  ) {

	//Cogemos el display
	display=Display.getDisplay(this);

        //Creamos el form 
        form = new Form ("Ejemplo");
        form.append("Hola Mundo\n");

	//Creamos el comando de salir
	salir=new Command("Salir",Command.EXIT, 3);

        //añadimos el comando al form y activamos el oyente
	form.addCommand(salir);
        form.setCommandListener(this);
    }
 
    //Método que se llama cuando pasamos de Pausado a Activo
    protected void startApp(  ) {
        display.setCurrent(form);
    }
 
    //Método que se llama cuando pasamos de Activo a Pausado
    protected void pauseApp(  ) {
    }
 
    //Método que se llama cuando se destruye el midlet
    protected void destroyApp(boolean incondicional) {
    }

    //Método para el tratamiento de datos de teclado 
    public void commandAction(Command c, Displayable d) {
	//Miramos si nos salimos o mostramos la alerta
	if (c ==salir) {
	   destroyApp(true);
	   notifyDestroyed();
        } else System.out.println("Otro comando pulsado");
        
   } 
}

Ejemplo de Hola Mundo con el API de bajo nivel.

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

public class HolaMundoBajo extends MIDlet implements CommandListener {
    private Display display;
    private Command salir; 
    private Canvas micanvas; 

    //Constructor
    public HolaMundoBajo(  ) {

	//Cogemos el display
	display=Display.getDisplay(this);

	//Creamos la pantalla principal
	micanvas = new Canvas() {
	      private int width;
	      private int height;

   	      public void paint (Graphics g){
		  width=getWidth();
		  height=getHeight();
		  
		  //pintamos la pantalla de negro
		  g.setColor(0,0,0);
		  g.fillRect(0,0,width,height);

		  //Pintamos una Hola en el centro y de blanco 
		  g.setColor(255,255,255);
		  g.setStrokeStyle(Graphics.SOLID);
		  g.drawString("Hola Caracola",width/2,height/2,(Graphics.BASELINE | Graphics.HCENTER));

	      }//fin método paint
        };
	//Creamos el comando de salir
        salir=new Command("Salir",Command.EXIT, 3);
	
        //añadimos el comando al Canvas y activamos el oyente
	micanvas.addCommand(salir);
	micanvas.setCommandListener(this);
    }
 
    //Método que se llama cuando pasamos de Pausado a Activo
    protected void startApp(  ) {
        display.setCurrent(micanvas);
    }
 
    //Método que se llama cuando pasamos de Activo a Pausado
    protected void pauseApp(  ) {
    }
 
    //Método que se llama cuando se destruye el midlet
    protected void destroyApp(boolean incondicional) {
    }

    //Método para el tratamiento de datos de teclado 
   public void commandAction(Command c, Displayable d) {
	//Miramos si nos salimos o mostramos la alerta
	if (c ==salir) {
	   destroyApp(true);
	   notifyDestroyed();
        } else System.out.println("Otro comando pulsado");
   } 
}