API de bajo nivel para Interfaz de usuario (javax.microedition.lcdui.Canvas) 

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

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

5.- API de bajo nivel

En el campo de aplicaciones en Java para teléfonos móviles, los juegos son las aplicaciones que copan el mercado. Con los componentes de la API de alto nivel poco se puede hacer, nos hace falta acceder con total libertad a la pantalla del dispositivo y la interacción del usuario. No podemos dejar al dispositivo que elija donde colocar las imágenes de nuestro juego, por ejemplo.

A diferencia del API de alto nivel aquí tendremos muchas menos clases con las que trabajar, pero habrá que cuidar más los detalles, hasta nivel de píxel. También tendremos que tener en cuenta que la aplicación puede ser no portable, si trabajamos con un dispositivo de 176 x 208 píxeles con 4096 colores (12bits) y queremos ejecutarla en un dispositivo de 96x54 píxeles y 2 colores (1bit).

Hay que tener en cuenta que se puede pasar del API de bajo nivel a alto nivel y viceversa, pero lo que no se puede es mostrar las dos a la vez. Estoy hay que tenerlo en cuenta, por ejemplo en un programa que necesite mostrar gráficas, pero el acceso a los datos se haga con un Form, al igual puede pasar con un juego donde el menú inicial del mismo se puede hacer con Form.

5.1.- La clase Canvas

Como vimos en el apartado 3 la clase abstracta Canvas es como un folio en blanco en el cual el Midlet puede dibujar directamente. A la hora de crear un interfaz de bajo nivel para nuestro Midlet tendremos que crear una subclase que herede de Canvas y redefinir el método paint(), para dibujar directamente en la pantalla del dispositivo lo que queramos que aparezca.

protected abstract void paint(Graphics g)

Este método es llamado con un sólo argumento, que es objeto de otra clase de bajo nivel, la clase Graphics. Esta clase nos proporciona métodos que nos van a permitir dibujar líneas, rectángulos y arcos; rellenar zonas con un color, y mostrar texto en la pantalla del dispositivo. La aplicación no tiene que ocuparse de llamar al método paint(), de ello se encarga el dispositivo.

Vimos que en el API de alto nivel todas las clases descendientes de Screen no era necesario preocuparse de repintarlas, en el caso de las clases que heredan de Canvas serán los Midlets los que se ocupen de hacerlo. Para ello la aplicación se encargará de llamar al método repaint() de Canvas, el cual llamará eventualmente al método paint(), esto es así, porque desde nuestra aplicación no podemos llamar a paint directamente (¿De dónde sacaríamos un objeto Graphics?). Repintar el Canvas es una operación asíncrona, por lo que varios repintados se pueden llevar a cabo mientras se ejecuta una operación paint(), de esta forma la pantalla estará repintada de forma óptima, lo cual es muy importante para no tener saltos en los juegos. Se puede solicitar que se finalicen todos los repintados pendiente, con el método serviceRepaints(), pero hay que tener cuidado porque bloquea la aplicación hasta que termina paint. También se puede solicitar repintar sólo una parte de la pantalla con el método repaint(int x, int y, int width, int heigth).

La clase Canvas tiene la posibilidad de tener asociados comandos (para la interacción con el usuario), pero no tiene la posibilidad de contener algún tipo de componentes.

Hay que tener en cuenta el origen del sistema de coordenadas de la clase Canvas, se sitúa en el punto superior izquierda de la pantalla del dispositivo, de esta forma la coordenada y crece hacia abajo y la coordenada x crece hacia la derecha, las coordenadas son números enteros positivos.

Podemos obtener el ancho y el alto de un Canvas con los métodos int getWidth() y int getHeight(), estos valores estarán condicionados por las características del dispositivo. También podemos ver si el dispositivo tiene un sistema de doble buffer para que las animaciones se hagan de una forma suave con el método boolean isDoubleBuffered(). Si el dispositivo no tiene sistema de doble buffer, probablemente será el Midlet el que tenga que implementarlo.

Para ver si el dispositivo tiene colores (no es sólo en blanco y negro) y la cantidad de colores podemos utilizar los métodos boolean isColor() y int numColors() de la clase Display.

Cuando la visibilidad de un Canvas cambia, los siguientes métodos son invocados:

protected void showNotify()
protected void hideNotify()

El método showNotify() es llamado inmediatamente antes de que el Canvas sea visible en la pantalla. Por defecto esta vacío, pero se puede redefinir para ser usado en nuestra aplicación. El método hideNotify() es llamado después de que el Canvas se haya hecho visible. Obsérvese que el método paint() será llamado después de showNotify() y antes de hideNotify().

Veamos un ejemplo donde tenemos un Canvas donde el método paint se limita a pintar de negro el fondo

Ejemplo10.java Ejemplo de Canvas.

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

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

    //Constructor
    public Ejemplo10(  ) {

	//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);

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

    //Metodo 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");
        
   } 
}

Poner la clase Canvas en un fichero aparte con getWidth/getHeight en el constructor.

5.2.- La clase Graphics

La clase Graphics nos va a permitir, como hemos dicho, realizar operaciones para dibujar en la pantalla del dispositivo. Obsérvese que no tendremos que crearla, ya que el método paint() es llamado por el dispositivo.

La clase Graphics que recibimos como parámetro del método paint() tiene una serie de atributos que condicionarán como se llevarán a cabo las diferentes operaciones de dibujado.

En la Tabla 1 tenemos los atributos de la clase Graphics, uso y valor por defecto:

Atributo Uso Valor inicial
Color El color que se va a utilizar a la hora de dibujar en el Canvas Negro
Font La fuente que se va a usar para renderizar texto. La que tenga el dispositivo por defecto
Stroke Style Determina como se van a dibujar líneas, rectángulo y arcos. Usando líneas sólida o puntos discontinuos, etc. Línea sólida
Origen La posición del origen de coordenados (0,0), relativa a la posición arriba izquierda del Canvas La parte de arriba-izquierda del Canvas
Clip El clip de la región del Canvas donde las operaciones gráficas tienen efecto Depende del motivo por el cual paint() se ha llamado

Tabla 1. Propiedades de la clase Graphics.

Para obtener o cambiar el valor estos atributos de la clase Graphics tenemos los siguientes métodos:

5.2.1.- Primitivas gráficas: líneas, rectángulos y arcos

La clase Graphic nos permite dibujar líneas, rectángulo y arcos, dentro del método paint().

Para dibujar una línea tenemos el siguiente método:

void drawLine(int x1, int y1, int x2, int y2)

Los parámetros x1 e y1 indican el punto de comienzo de una línea, mientras que el punto x2 e y2 indican el punto final.

Para dibujar un rectángulo tenemos el siguiente método:

void drawRect(int x, int y, int width, int height)

Los parámetros x e y indican la localización de la esquina superior izquierda del rectángulo y los parámetros width y height especifican el ancho y el alto del rectángulo. También existe un método para dibujar rectángulos con las esquinas redondeadas:

public void drawRoundRect(int x, int y, int width, int height, 
                          int arcWidth, int arcHeight)

Donde aparecen dos nuevos parámetros arcWidth arcHeight que configuran el ancho y el alto del arco de las esquinas del rectángulo. Además también tenemos dos métodos para dibujar rectángulos rellenos:

public void fillRoundRect(int x, int y, int width, int height,
                          int arcWidth, int arcHeight)

public void fillRect(int x, int y, int width, int height)

Veamos un ejemplo donde dibujamos un par de líneas y un par de rectángulos de diferentes colores

Ejemplo11.java Ejemplo de primitivas gráficas.

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

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

    //Constructor
    public Ejemplo11(  ) {

	//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 linea horizontal blanca 
		  g.setColor(255,255,255);
		  g.setStrokeStyle(Graphics.SOLID);
		  g.drawLine(0,height/2,width-1,height/2);

		  //Pintamos una linea punteada diagonal verde
		  g.setColor(0,255,0);
		  g.setStrokeStyle(Graphics.DOTTED);
		  g.drawLine(0,0,width-1,height-1);

		  //Pintamos un rectangulo punteado y rojo
		  g.setColor(255,0,0);
		  g.setStrokeStyle(Graphics.DOTTED);

		  g.drawRect(width/4,0,width/2,height/4);

		  //Pintamos un rectangulo solido y azul, dentro
		  //del anterior y con esquinas redondeadas
		  g.setColor(0,0,255);
		  g.setStrokeStyle(Graphics.SOLID);
		  g.drawRoundRect(width/4 + 4,4,width/2 -8,height/4 -8,8,8 );

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

    //Metodo 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");
        
   } 
}

Otra primitiva gráfica son los arcos. Un arco es una parte de una elipse, el método que dibuja un arco es el siguiente:

public void drawArc(int x, int y, int width, int height,
                    int startAngle, int arcAngle)

Este método dibuja un arco que comienza en startAngle y se extiende por arcAngle grados. Ángulos con 0 grados están a la posición de las 3 en punto. Un valor positivo para los arcos indica un sentido en las agujas del reloj y un valor negativo indica en sentido contrario a las agujas del reloj. El arco está dentro de un rectángulo y el centro de este rectángulo está dado por las coordenadas (x, y) y su alto y ancho viene dado por width y height (si estos valores son menores a cero, no se dibuja nada). En la figura 3, se puede ver un ejemplo.

Figura 3. Ejemplo de drawArc.

Para crear una elipse cerrada se puede hacer poniendo el parámetro arcAngle a 360 grados. También podemos crear arcos rellenados con el método fillArch

public void fillArc(int x, int y, int width, int height,
                    int startAngle, int arcAngle)

Veamos un ejemplo donde trabajos con drawArc y fillArc

Ejemplo12.java Ejemplo de Arcos.

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

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

    //Constructor
    public Ejemplo12(  ) {

	//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 circulo blanco relleno
		  g.setColor(255,255,255);
		  g.setStrokeStyle(Graphics.SOLID);
		  g.fillArc(0,0,height-5,width-5, 0, 360);

		  //Pintamos un arco rojo de una 
		  g.setColor(255,0,0);
		  g.drawArc(10,10,height-10,width-35, -90, 180);

		  //Pintamos un arco verde completando la elipse 
		  g.setColor(0,255,0);
		  g.drawArc(10,10,height-10,width-35, 90, 180);
	      }//fin metodo paint
        };
	//Creamos el comando de salir
        salir=new Command("Salir",Command.EXIT, 3);
	
        //anadimos el comando al Canvas y activamos el oyente
	micanvas.addCommand(salir);
	micanvas.setCommandListener(this);
    }
 
    //Metodo que se llama cuando pasamos de Pausado a Activo
    protected void startApp(  ) {
        display.setCurrent(micanvas);
    }
 
    //Metodo que se llama cuando pasamos de Activo a Pausado
    protected void pauseApp(  ) {
    }
 
    //Metodo que se llama cuando se destruye el midlet
    protected void destroyApp(boolean incondicional) {
    }

    //Metodo 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");
   } 
}
5.2.2.- Fuentes

La clase Graphics tiene cuatro métodos para dibujar texto en un Canvas:

public void drawChar(char character, int x, int y, int anchor)

Este primer método escribe un carácter en la posición x, y. El parámetro anchor es lo que se denomina anclaje del texto, el anclaje del texto está formado por dos constantes (una horizontal y otra vertical) que nos va a dar un punto alrededor del texto. De esta forma, la coordenada x, y es la coordenada el punto de anclaje del texto y este punto de anclaje se puede especificar con anchor y es una parte de los rectángulos que envuelve el texto, tal y como se ve en la Figura 4.

Figura 4. Constantes usadas para situar el anclaje (anchor).

Como se puede ver en la Figura 4, las constantes horizontales que se utilizan para definir el anchor son:

El segundo método para dibujar texto en la pantalla es:

public void drawChars(char[] data, int offset, int length,
                      int x, int y, int anchor)

Este método nos va a permitir escribir los caracteres que hay de data[offset] a data[offset+length-1].

Los siguientes métodos no trabajan con arrays de caracteres sino con cadenas (String):

public void drawString(String str, int x, int y, int anchor)
public void drawSubstring(String str, int offset, int len,
                          int x, int y, int anchor)

De estos dos métodos para trabajar con cadenas, el primero escribe en pantalla la cadena que se le pasa como parámetro. Y en el segundo escribimos la subcadena que va desde offset a offset+length-1.

El texto que se escribe mediante la clase Graphics, estará configurado por el atributo font que se ha visto en el apartado 5.2. Este atributo se puede cambiar u obtener con:

public Font getFont()
public void setFont(Font font)

Para usar una clase Font se usa el método estático getFont(...) de Font (no confundir con el getFont de Graphics):

public static Font getFont(int face, int style, int size)

donde los parámetros indican la forma, estilo y tamaño de la fuente. Veamos que valores puede tomar esos parámetros.:

Veamos un ejemplo sencillo de como mostrar texto en un Canvas.

Ejemplo13.java Ejemplo de escribir textos.

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

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

    //Constructor
    public Ejemplo13(  ) {

	//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 metodo paint
        };
	//Creamos el comando de salir
        salir=new Command("Salir",Command.EXIT, 3);
	
        //anadimos el comando al Canvas y activamos el oyente
	micanvas.addCommand(salir);
	micanvas.setCommandListener(this);
    }
 
    //Metodo que se llama cuando pasamos de Pausado a Activo
    protected void startApp(  ) {
        display.setCurrent(micanvas);
    }
 
    //Metodo que se llama cuando pasamos de Activo a Pausado
    protected void pauseApp(  ) {
    }
 
    //Metodo que se llama cuando se destruye el midlet
    protected void destroyApp(boolean incondicional) {
    }

    //Metodo 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");
   } 
}

Cambia el anterior ejemplo para que cambie la clase usando un comando.

5.2.3.- Imágenes

Para poder trabajar con una clase Image tendremos que cargarla en nuestro Midlet, normalmente las imágenes están en ficheros externos. La claseImage tiene cuatro métodos estáticos para crear una imagen:

public static Image createImage(String name) throws java.io.IOException
public static Image createImage(byte[] imageData, int imageOffset, int imageLength)
public static Image createImage(int width, int height)
public static Image createImage(Image source)

El primer constructor crea una imagen leyéndola desde un fichero. El segundo crea la imagen a partir de un array de datos (que puedes ser una imagen descargada de la red, por ejemplo). Estos dos primeros métodos crean imágenes inmutables, es decir, que no se pueden modificar. Las imágenes inmutables son necesarias para los ítem de alto nivel del API como ImageItem, que no se tienen que preocupar de repintar la imagen si ha sido modificada.

El tercer método crea una imagen cuyo tamaño se le pasa como parámetro y en la que todos los píxeles están inicialmente en blanco. Esta imagen es mutable, es decir, se puede modificar con un objeto Graphics igual que se hace con el Canvas, usando el método getGraphics() de Image. El cuarto método nos permite crear una copia inmutable de una imagen mutable.

Para dibujar una imagen (mutable o inmutable) el Canvas en su método paint(), se usa el siguiente método de la clase Graphics:

public void drawImage(Image img, int x, int y, int anchor)

Donde el primer parámetro es la imagen, el segundo y tercero nos dan las coordenadas del punto de anclaje y el cuarto parámetro nos da el anclaje de la imagen. El anclaje en las imágenes funciona de forma análoga a como funciona en el texto, pero teniendo en cuenta que BASELINE no se puede usar (no tendría sentido) y en cambio tenemos VCENTER que no es válido para texto y que nos da el centro en vertical de la imagen.

Las imágenes mutables nos pueden servir para trabajar con doble buffer, si nuestro dispositivo no lo soporta. También se podría crear una imagen mutable, pasarla a inmutable y usarla en el API de alto nivel como un ítem ImageItem.

Veamos un ejemplo sencillo de como mostrar una imagen no mutable en un Canvas.

Ejemplo14.java Ejemplo de mostrar una imagen en un Canvas.

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

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

    //Constructor
    public Ejemplo14 ( ) {

	//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);

		  //Leemos una imagen desde un fichero y la mostramos
		  try {
		      Image imagen= Image.createImage("/logo.png");
		      g.drawImage(imagen,width/2,height/2,(Graphics.VCENTER | Graphics.HCENTER));		      
		  } catch (java.io.IOException e) {
		      g.setColor(255,255,255);
		      g.setStrokeStyle(Graphics.SOLID);
		      g.drawString("Fallo al leer logo.png",0,height/2,(Graphics.BASELINE | Graphics.LEFT));

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

    //Metodo 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");
   } 
}

Cambia el anterior ejemplo para que con una serie de botones se cambie el anclaje horizontal y el vertical.

5.3.- Gestión de Comandos

En el API de bajo nivel nos tenemos que encargar nosotros de manejar y procesar todas las entradas del usuario, es decir, no pasa como cuando usábamos un TextBox, donde conforme vamos escribiendo se refleja en la pantalla y además se va almacenando en la memoria del ítem.

En la sección de bajo nivel trataremos con dos tipos de entradas las provenientes de teclado que las poseen todos los dispositivos y las de puntero, que no todos la soportan.

5.3.1.- Tratamiento de las teclas

Para realizar el tratamiento de teclas desde la API de bajo nivel, la única forma que tenemos es sobrescribir los siguientes métodos de la clase Canvas:

protected void keyPressed(int keyCode)
protected void keyReleased(int keyCode)
protected void keyRepeated(int keyCode)

Los métodos keyPressed y keyReleased son fáciles de entender, se llaman cuando el usuario presiona una tecla y cuando la libera, respectivamente. EL método keyRepeated se activa cuando el usuario tiene pulsada una tecla por ciento tiempo sin liberarla, no todos los dispositivos soportan esto, para ello tendremos que mirarlo con el método boolean hasRepeatEvents().

El parámetro que reciben los tres anteriores métodos es el código de la tecla que es pulsada, liberada o repetida. Los códigos que existen para este tipo de dispositivos se muestran en la Tabla 2

Key Code Significado
KEY_NUM0 Tecla número 0 del teléfono
KEY_NUM1 Tecla número 1 del teléfono
KEY_NUM2 Tecla número 2 del teléfono
KEY_NUM3 Tecla número 3 del teléfono
KEY_NUM4 Tecla número 4 del teléfono
KEY_NUM5 Tecla número 5 del teléfono
KEY_NUM6 Tecla número 6 del teléfono
KEY_NUM7 Tecla número 7 del teléfono
KEY_NUM8 Tecla número 8 del teléfono
KEY_NUM9 Tecla número 9 del teléfono
KEY_STAR Tecla asterisco (*) del teléfono
KEY_POUND Tecla sostenido (#) del teléfono
UP Tecla de juego Arriba
DOWN Tecla de juego Abajo
LEFT Tecla de juego Izquierda
RIGHT Tecla de juego Derecha
FIRE Tecla de juego Disparo
GAME_A Tecla de juego A
GAME_B Tecla de juego B
GAME_C Tecla de juego C
GAME_D Tecla de juego D

Tabla 2 Códigos estándar de teclas.

Veamos un ejemplo sencillo de como mover un cuadradito con las teclas de arriba, abajo, izquierda y derecha.

Ejemplo15.java Ejemplo de mover un cuadrado.

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

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

    //Constructor
    public Ejemplo15(  ) {
	//Cogemos el display
	display=Display.getDisplay(this);

	//Creamos la pantalla principal
	micanvas = new Canvas() {
	      private int x=0;
	      private int y=0;

	      private int bloquew;
	      private int bloqueh;

   	      public void paint (Graphics g){
		  //pintamos la pantalla de blanco
		  g.setColor(255,255,255);
		  g.fillRect(0,0,getWidth(),getHeight());

		  //El tamano del bloque sera la 20 parte de la pantalla
		  int bloquew=getWidth()/10;
		  int bloqueh=getHeight()/10;

		  //Pintamos un rectangulo solido y negro
		  g.setColor(0,0,0);
		  g.setStrokeStyle(Graphics.SOLID);
		  g.drawRect(x,y,bloquew,bloqueh);
		  System.out.println("Pinto en "+x+","+y+" de "+getWidth()+" por "+getHeight());

	      }//fin metodo paint

  	      //Metodo que se llama cuando pulsamos una tecla
	      protected void keyPressed(int keyCode) {
		  int arriba=getKeyCode(UP);
		  int abajo=getKeyCode(DOWN);
		  int izq=getKeyCode(LEFT);
		  int dcha=getKeyCode(RIGHT);

		  if (keyCode == arriba)     { if (y>0) y-=1;repaint(); }
		  else if (keyCode == abajo) { if (y<getWidth()) y+=1;repaint();}
		  else if (keyCode == izq  ) { if (x>0) x-=1;repaint();}
		  else if (keyCode == dcha ) { if (x<getHeight()) x+=1;repaint();}
		  
	      }//fin metodo keyPressed
        };
	//Creamos el comando de salir
	salir=new Command("Salir",Command.EXIT, 3);
	
        //anadimos el comando al Canvas y activamos el oyente
	micanvas.addCommand(salir);
	micanvas.setCommandListener(this);
    }

    //Metodo que se llama cuando pasamos de Pausado a Activo
    protected void startApp(  ) {
        display.setCurrent(micanvas);
    }
 
    //Metodo que se llama cuando pasamos de Activo a Pausado
    protected void pauseApp(  ) {
    }
 
    //Metodo que se llama cuando se destruye el midlet
    protected void destroyApp(boolean incondicional) {
    }

    //Metodo 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");
   } 
}

Cambia el anterior ejemplo para que se utilice un doble buffer.

5.3.2.- Tratamiento del dispositivo apuntador

Aquellos dispositivos que tengan apuntador y quieran tratarlo, deben sobrecargar los siguientes métodos de la clase Canvas:

protected void pointerPressed(int x, int y)
protected void pointerDragged(int x, int y)
protected void pointerReleased(int x, int y)

En los tres casos los argumentos x a y dan la posición del apuntador relativa a la esquina superior izquierda de la clase Canvas. Se puede determinar si el apuntador generará eventos pointerPressed y pointerReleased si el método boolean hasPointerEvents() de Canvas es cierto. Si el método boolean hasPointerMotionEvents() de Canvas es cierto, entonces se soportan eventos pointerDragged, que indican que se está moviendo el apuntador, pero no tiene porqué saltar por cada píxel que se mueve.

5.4.- Ejercicios

Ejercicio: Haz el esqueleto del Midlet que debe seguir toda clase que herede de Canvas.

Ejercicio: Haz un Form que pregunte por las características de un texto y lo dibuje sobre un Canvas.

Ejercicio: Modificar el anterior ejemplo, para en lugar de dibujar en un Canvas, dibuje en una imagen modificable, la pase a no modificable y se pueda ver en el Form.

Ejercicio: Dibuja en pantalla una serpiente, muros y ratones a partir de una matriz con la posición de muros y ratones y a partir de un vector con las posiciones de la serpiente. Tiene que ser de forma proporcional a la pantalla que se tenga.

Ejercicio: Añádele movimiento a la serpiente del ejercicio anterior, que sólo se moverá cuando se pulsan las teclas. Usa las teclas de los cursores.

Ejercicio: Añádele movimiento a la serpiente del ejercicio anterior de forma que se vaya moviendo hacia delante sin que se pulsen las teclas.

Ejercicio: Chequea los casos en los que se de con un muro o consigo misma y muestra un mensaje de fin de juego. Trata también el caso en el que pase por un ratón, éste desaparezca, se cree otro y la serpiente se alargue.

Ejercicio: Añádele un marcador de puntos al juego.