21.05.04 Benutzereingaben auf Low-Level Ebene

Bis jetzt sind Ihre selbst gezeichneten Oberflächen noch recht statisch und reagieren nicht auf Benutzereingaben. Dies wird sich in diesem Kapitel ändern. Sie lernen, wie Sie auf das Drücken einer Taste oder die Bedienung der Applikation mit einem evtl. vorhandenen Touchscreen reagieren können.

Drei Methoden für Tastatureingaben

Ein Canvas stellt Ihnen drei unterschiedliche Methoden zur Reaktion auf die Betätigung einer Taste auf der Handytastatur zur Verfügung, welche Sie nur noch überschreiben müssen.

Beim Drücken einer Taste wird die Methode keyPressed(int keyCode) aufgerufen, beim Aufruf von keyReleased(int keyCode) wurde die Taste hingegen wieder losgelassen. Und während eine Taste gehalten wird, wird in definierten Abständen die Methode keyRepeated(int keyCode) ausgeführt.

Als Übergabeparameter der Methoden ist jeweils eine Nummer/ein Code definiert, anhand dessen eine Taste eindeutig identifiziert werden kann. Für die Standardtasten (0-9, * und #) auf einer Handytastatur stehen hierfür bereits Konstanten in der Klasse Canvas zur Verfügung. KEY_NUM0 bis KEY_NUM9 stehen dabei für die Zahlen 0-9, KEY_POUND für # und KEY_STAR für *. Sie können sich jedoch auch den Namen der gedrückten Taste (des keyCodes) ausgeben lassen. Hierzu verwenden Sie die Methode getKeyName(int keyCode).

Ein kleines Beispiel, das immer dann den Anzeigetext entsprechend verändert, wenn eine der Tasten 2, 5, oder 8 gedrückt, gehalten oder losgelassen wird (wie in Java SE sorgt auch in Java ME der Aufruf von repaint() dafür, dass das Canvas neu gezeichnet wird):

package de.jbb.j2me.ll;

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Graphics;

public class ActionCanvas extends Canvas {

  private static final String PRESSED = " gedrückt";
  private static final String REPEATED = " gehalten";
  private static final String RELEASED = " losgelassen";

  private String drawingText = "";

  protected void paint(Graphics g) {

    g.setColor(0, 0, 0);
    g.fillRect(0, 0, getWidth(), getHeight());
    g.setColor(255, 255, 255);
    g.drawString(drawingText, getWidth() / 2, getHeight() / 2,
      Graphics.HCENTER|Graphics.BOTTOM);
  }

  private void setText(String text) {
    if (!text.equals(drawingText)) {
      drawingText = text;
      repaint();
    }
  }

  protected void keyPressed(int keyCode) {

    if (keyCode == KEY_NUM2 || 
         keyCode == KEY_NUM5 ||
         keyCode == KEY_NUM8) {
      setText(getKeyName(keyCode) + PRESSED);
    }
  }

  protected void keyReleased(int keyCode) {

    if (keyCode == KEY_NUM2 ||
         keyCode == KEY_NUM5 ||
         keyCode == KEY_NUM8) {
      setText(getKeyName(keyCode) + RELEASED);
    }
  }

  protected void keyRepeated(int keyCode) {

    if (keyCode == KEY_NUM2 ||
         keyCode == KEY_NUM5 ||
         keyCode == KEY_NUM8) {
      setText(getKeyName(keyCode) + REPEATED);
    }
  }
}

Die meisten modernen Handys besitzen jedoch mehr als diese zwölf Standardtasten. Möchten Sie auf nicht standardisierte Tasten reagieren, müssen Sie hier gerätespezifische Versionen Ihres Programms erstellen und jeweils den spezifischen keyCode abfragen. Dies ist jedoch vor allem für Spiele, die meistens mit einem ggf. vorhandenem Steuerkreuz bedient werden können, sehr umständlich. Hier vereinfachen die so genannten Game Actions Ihr Leben.

Game Action

Über die Methode getGameAction(int keyCode) kann ein keyCode in einen gerätespezifischen keyCode einer abstrakten Taste umgewandelt werden. Eine abstrakte Taste ist hierbei bspw. links oder Abschuss. Auf diese Weise können Sie relativ gerätespezifisch auf typische Eingaben, die für ein Spiel notwendig sind, reagieren. Den Rückgabewert der getGameAction-Methode können Sie anschließend mit einer der Konstanten LEFT, RIGHT, UP, DOWN (Richtungsanweisungen), FIRE (bestätigen/Abschuss/OK), GAME_A, GAME_B, GAME_C oder GAME_D (Tasten, die das jeweilige Gerät für Aktionstasten in Spielen vorsieht) der Klasse Canvas vergleichen.

Genauso können Sie umgekehrt eine gameAction über die Methode getKeyCode(int gameAction) wieder zurück in einen keyCode verwandeln. Anschließend könnten Sie sich dann die Bezeichnung der gameAction anhand deren keyCode über die bereits bekannte Methode getKeyName ausgeben lassen.

Ein kleines Beispiel:

protected void keyPressed(int keyCode) {

  String ext = " (" + getKeyName(keyCode) + ")";
  switch(getGameAction(keyCode)) {
    case LEFT:
      drawingText = "LEFT";
      break;
    case RIGHT:
      drawingText = "RIGHT";
      break;
    case UP:
      drawingText = "UP";
      break;
    case DOWN:
      drawingText = "DOWN";
      break;
    case FIRE:
      drawingText = "FIRE";
      break;
    case GAME_A:
      drawingText = "GAME_A";
      break;
    case GAME_B:
      drawingText = "GAME_B";
      break;
    case GAME_C:
      drawingText = "GAME_C";
      break;
    case GAME_D:
      drawingText = "GAME_D";
      break;
    default:
      drawingText = "No gameAction for";
  }
  drawingText += ext;
  repaint();
}

protected void keyReleased(int keyCode) {}
protected void keyRepeated(int keyCode) {}

Der springende Punkt

Zur Vertiefung Ihrer bisherigen Kenntnisse wird ein Canvas implementiert, durch welches man mittels Handytastatur einen kleinen Punkt steuern kann. Zusätzlich zur Navigation über die gameActions, ist diese auch über die Tasten 4 (links), 6 (rechts), 8 (unten) und 2 (oben) möglich. Bei der Betätigung von FIRE oder der 5, ändert der Punkt seine Farbe.

package de.jbb.j2me.ll;

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Graphics;

public class ActionCanvas extends Canvas {

  private boolean drawRed = true;
  private int pointSize = 10;
  private int x = 0;
  private int y = 0;
  private int pointStep = 5;

  protected void paint(Graphics g) {

    g.setColor(0, 0, 0);
    g.fillRect(0, 0, getWidth(), getHeight());
    if (drawRed) {
      g.setColor(255, 0, 0);
    }
    else {
      g.setColor(255, 255, 255);
    }
    g.fillArc(x, y, pointSize, pointSize, 0, 360);
   }

   public void movePoint(int keyCode) {

     switch (getGameAction(keyCode)) {
     case LEFT:
       x -= pointStep;
       break;
     case RIGHT:
       x += pointStep;
       break;
     case UP:
       y -= pointStep;
       break;
     case DOWN:
       y += pointStep;
       break;
     case FIRE:
       drawRed = !drawRed;
       break;
     default:
       switch (keyCode) {
       case KEY_NUM2:
         y -= pointStep;
         break;
       case KEY_NUM4:
         x -= pointStep;
         break;
       case KEY_NUM6:
         x += pointStep;
         break;
       case KEY_NUM8:
         y += pointStep;
         break;
       case KEY_NUM5:
         drawRed = !drawRed;
       }
     }
     if (x < 0) {
       x = 0;
     }
     else if (x + pointSize > getWidth()) {
       x = getWidth() - pointSize;
     }
     else if (y < 0) {
       y = 0;
     }
     else if (y + pointSize > getHeight()) {
       y = getHeight() - pointSize;
     }
     repaint();
   }

   protected void keyPressed(int keyCode) {
     movePoint(keyCode);
   }

   protected void keyRepeated(int keyCode) {
     movePoint(keyCode);
   }
}

Touchscreen Funktionalitäten

Wenn das Endgerät einen Touchscreen besitzt, können Sie auch auf Eingaben über den Bildschirm reagieren. Ähnlich wie bei Tastatureingaben gibt es auch hierfür Methoden, wenn auf den Bildschirm gedrückt (pointerPressed(int x, int y)), etwas verschoben (pointerDragged(int x, int y)) oder der Bildschirm nicht mehr berührt (pointerReleased(int x, int y)) wird, die in Ihrem Canvas überschrieben werden müssen. Die übergebenen Koordinaten spezifizieren, an welcher Stelle sich das Eingabegerät/der Finger momentan befindet.

So lässt sich der springende Punkt problemlos um Touch-Funktionalitäten erweitern:

private void setBallTo(int x, int y) {

  if (x < 0) {
    x = 0;
  }
  if (x + pointSize > getWidth()) {
    x = getWidth() - pointSize;
  }
  if (y < 0) {
    y = 0;
  }
  if (y + pointSize > getHeight()) {
    y = getHeight() - pointSize;
  }
  this.x = x;
  this.y = y;
  repaint();
}

protected void pointerDragged(int x, int y) {
  setBallTo(x - pointSize / 2, y - pointSize / 2);
}

protected void pointerPressed(int x, int y) {
  setBallTo(x - pointSize / 2, y - pointSize / 2);
}

Flüssigere Bewegungen

Vermutlich ist Ihnen beim Ausprobieren aufgefallen, dass sich der Punkt bei der Steuerung über die Tastatur mit gehaltener Taste nur sehr ruckelig und langsam bewegt. Dies liegt daran, dass Sie nicht spezifizieren können, in welchen Abständen die Methode keyRepeated aufgerufen wird. Es ist ein Workaround nötig.

Anstatt wiederholende Eingaben über die keyRepeated-Methode zu realisieren, können Sie einem separaten Thread, der in einer Endlosschleife parallel zum restlichen Programm läuft, den zuletzt gedrückten keyCode aus der keyPressed Methode setzen. In der Endlosschleife wird dann die movePoint Methode unseres Canvas in regelmäßigen Abständen aufgerufen, bis wieder im Canvas die Methode keyReleased ausgelöst, und somit die Wiederholung beendet wurde.

Zuerst benötigen Sie ein Interface Movable, das die Methode movePoint(int keyCode) vorschreibt.

package de.jbb.j2me.ll;

public interface Movable {

  void movePoint(int keyCode);
}

Anschließend können Sie den RepeatHelper mit oben genannter Funktionalität implementieren (für diese Zwecke ist ein sehr einfacher Helper ausreichend):

package de.jbb.j2me.ll;

public class RepeatHelper implements Runnable {

  private int keyCode = -1;
  private int waitingTime;
  private boolean repeat;

  private Movable movable;

  public RepeatHelper(Movable movable, int waitingTime) {
    this.movable = movable;
    this.waitingTime = waitingTime;
  }

  public void run() {
    while (true) {
      if (repeat) {
        movable.movePoint(keyCode);
      }
      try {
        Thread.sleep(waitingTime);
      }
      catch (InterruptedException ie) {
        ie.printStackTrace();
      }
    }
  }

  public void setKeyCode(int keyCode) {
    this.keyCode = keyCode;
    repeat = true;
  }

  public void stopRepeating() {
    repeat = false;
  }
}

Die Implementierung des Canvas muss nun noch an die Verwendung des RepeatHelpers angepasst werden. Hierzu muss das Canvas zuerst Movable implementieren (die dazugehörige Methode movePoint(int keyCode) wurde bereits ausprogrammiert).

public class ActionCanvas extends Canvas implements Movable

Anschließend deklarieren Sie eine Objektvariable des RepeatHelpers, initialisieren diese im Konstruktor, verpacken das Runnable-Objekt in einem neuen Thread, und starten diesen.

private RepeatHelper repeater;

public ActionCanvas() {
  repeater = new RepeatHelper(this, 40);
  new Thread(repeater).start();
}

Die keyPressed-Methode wird dahingehend angepasst, dass nach dem Aufruf der movePoint-Methode der keyCode dem RepeatHelper gesetzt wird – sofern der keyCode einer Navigationstaste entspricht.

protected void keyPressed(int keyCode) {
 movePoint(keyCode);
 if (getGameAction(keyCode) != FIRE && keyCode != KEY_NUM5) {
   repeater.setKeyCode(keyCode);
 }
}

keyRepeated fällt nun selbstverständlich weg. Dafür muss in der keyReleased Methode die Wiederholung des RepeatHelpers beendet/unterbrochen werden.

protected void keyReleased(int keyCode) {
 repeater.stopRepeating();
}

Auf der nächsten Seite finden Sie eine Zusammenfassung des in diesem Kapitel erarbeiteden Codes.

2 Replies to “21.05.04 Benutzereingaben auf Low-Level Ebene”

  1. Prüfer,Petra

    Hallo, ich bin Anfänger und programmiere Java über Eclipse. Wie kann ich bei „Graphics“ eine Eingabe einer ganzen Zahl programmieren?
    Danke für eine Hilfe.
    Mit vielen Grüßen
    P- Prüfer

  2. Stefan Kiesel

    Hallo Petra,

    erst einmal die Frage (da Sie Anfängerin sind), ob Sie wissen, dass Sie sich im Java ME – also Java für mobile Endgeräte wie Handys – Kapitel befinden, und nicht im Java SE (für ganz normale Computer)?

    Grüße
    Stefan

Schreibe einen Kommentar

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.