J2ME v kostce – kreslíme na displej

15. listopadu 2002

Dnes nebudeme kreslit fixem, jak by mohl název evokovat, ale budeme se věnovat uživatelskému rozhraní v Javě na mobilní telefony. Podíváme se, jaké má možnosti, nedostatky a záludnosti a jak se s tím vším vypořádat při psaní aplikace. Jako příklad použijeme jednoduchou aplikaci – házení mincí.

Struktura uživatelského rozhraní

V MIDP jsou k dispozici pro kreslení na displej komponenty dvou základních typů:

  • Nízkoúrovňové:
    • Aplikace přímo kreslí na displej.
    • Komponenta dostává události typu stisknutí klávesy (nebo u displejů ovládaných dotykově události stylusu).
    • Pokud nebude kreslení dostatečně promyšlené, stane se aplikace nepřenositelnou.
  • Vysokoúrovňové:
    • Jsou nezávislé na typu a velikosti displeje.
    • Veškeré konkrétní kreslení a definice vlastností, jako je např. velikost fontu nebo barva čehokoli, řeší implementace javy, programátor na ně nemá žádný vliv.

Skoro bezproblémová přenositelnost programu používajícího pouze vysokoúrovňové komponenty je jistě pozitivní vlastností. U telefonů s malým displejem a pouze dvěmi barvami nebylo při implementaci těchto komponent moc co zkazit. Ovšem třeba u takového telefonu Nokia 7650 nejspíš zjistíte, že vzhled např. formuláře, který patří mezi vysokoúrovňové komponenty, neuspokojuje dostatečně vaše estetické cítění, a budete nuceni úroveň „snížit“.

Pozn.: Třídy, u kterých v následujícím textu neuvádím balík, ze kterého jsou, hledejte v balíku javax.microedition.lcdui.

Displej

Základní třídou, která spravuje displej, je (překvapivě) třída Display. Tato třída je typický jedináček (singleton). V rámci jednoho midletu existuje právě v jedné instanci, kterou lze získat použitím statické metody Display.getDisplay().

Objekty, které se zobrazují na displeji, musí být potomkem třídy Displayable. Třída Display má na nastavení aktuálního Displayable objektu metodu setCurrent() a na jeho získání metodu getCurrent(). Pokud je aktuální Displayable objekt zobrazen na displeji, běží aplikace na popředí a dostává vstupní události z klávesnice či dotykového displeje. Neběží-li aplikace na popředí (běží na pozadí), nemá přístup k žádným vstupním zařízením. U většiny telefonů (ne-li u všech) ovšem nemá smysl zabývat se během na pozadí, protože tato zařízení neumožňují běh více aplikací najednou.

Přehled komponent

Následující obrázek ukazuje hierarchii všech komponent, které lze zobrazit na displej:

Jak sami vidíte, není jich mnoho. Potomci třídy Screen patří mezi vysokoúrovňové komponenty. Chcete-li použít nějakou nízkoúrovňovou komponentu, musíte si ji sami napsat jako potomka abstraktní třídy Canvas.

Ve zbytku článku se budu věnovat příkladu použití komponenty Canvas.

Příkazy a události

Třída Canvas má s vysokoúrovňovými komponentami společné ovládání pomocí abstraktních příkazů. Umístění těchto příkazů je zcela na libovůli implementátora J2ME. Programátor pouze příkaz vytvoří, určí jeho typ, na kterém může záviset, kde je tento příkaz umístěn na displeji, a nastaví posluchače na jeho události. Tento posluchač se nastavuje metodou setCommandListener(CommandListener listener) a může být (narozdíl od standardní Javy) pouze jeden.

Na odchytávání nízkoúrovňových událostí máme k dispozici několik metod, které mají všechny v třídě Canvas prázdnou implementaci, takže pokud nás tyto události nezajímají, nemusíme dělat vůbec nic. Pro nás asi nejzajímavější (a použitá v příkladu) je metoda keyPressed(int keyCode). Tuto metodu vyvolá stisk klávesy, jejíž kód dostane jako parametr, a obvykle bude její implementace obsahovat příkaz switch, který podle kódu klávesy rozhodne, co dál. Všechny použitelné kódy kláves jsou statické proměnné třídy Canvas a začínají řetězcem „KEY“.

Aby to nebylo na první pohled tak jednoduché, obsahuje Canvas ještě druhou úroveň kódů kláves, nazývanou herní akce (game actions). Tyto akce jsou namapovány na klávesy, které má telefon k dispozici, takže mohou být u každého telefonu namapovány jinak v závislosti na jeho klávesnici. Například akce nahoru je u telefonů se šipkou nahoru přiřazena této šipce, u jiných telefonů klávese s číslem 2. Odpovídající herní akci zjistíme metodou getGameAction(int keyCode). Definovány jsou herní akce UP, DOWN, LEFT, RIGHT, FIRE, GAME_A, GAME_B, GAME_C a GAME_D

Kreslíš, kreslím, kreslíme

Různé telefony jsou různě rychlé. Obzvláště v rychlosti kreslení na displej může být u aplikací s velkým množstvím grafických operací kámen úrazu, proto je důležité tyto operace optimalizovat, aby jich bylo co nejméně.

V následujícím příkladu jsem použila metodu dvojitého zásobníku (double buffering – pokud znáte někdo nějaký vhodný český termín, který se používá, ráda se nechám poučit). V tomto příkladě to sice zrovna není potřeba, ale na překreslování složitějších obrázků na některých telefonech by se bez kreslení nejprve do „záložního obrázku“ a pak teprve na displej nedalo dívat.

Celá třída na zobrazení mince

import java.util.Random; import javax.microedition.lcdui.Canvas; import javax.microedition.lcdui.Command; import javax.microedition.lcdui.CommandListener; import javax.microedition.lcdui.Displayable; import javax.microedition.lcdui.Graphics; import javax.microedition.lcdui.Image; public class CoinCanvas extends Canvas implements CommandListener { /* * Názvy obrázků s rubem a lícem mince. Tyto obrázky * se musí nacházet v kořeni JAR souboru, do kterého * aplikaci zabalíte. Obrázky, které jsem použila já, * jsou tak špatné kvality, že jsem je raději ani * k článku nepřiložila. Jistě si namalujete * nějaké lepší. */ private static final String panna = „/panna.png“; private static final String orel = „/orel.png“; CoinMIDlet midlet; Command cmdExit; Command cmdThrow; Image[] coin = new Image[2]; Random rand; int value = 0; Image image; Graphics buffer; /** * V konstruktoru se inicializují všechny proměnné, * přidají příkazy a nastaví instance třídy typu * CommandListener, která zpracovává události příkazů. */ public CoinCanvas(CoinMIDlet midlet) { this.midlet = midlet; rand = new Random(); try { coin[0] = Image.createImage(panna); coin[1] = Image.createImage(orel); } catch (Exception e) { e.printStackTrace(); } image = Image.createImage(getWidth(), getHeight()); buffer = image.getGraphics(); buffer.setColor(0, 0, 0); buffer.fillRect(0, 0, getWidth(), getHeight()); buffer.setColor(255, 255, 255); cmdExit = new Command(„Konec“, Command.EXIT, 2); addCommand(cmdExit); cmdThrow = new Command(„Hazej“, Command.SCREEN, 1); addCommand(cmdThrow); setCommandListener(this); } /** * Nakreslí do dané grafiky podle hodnoty parametru sign * buď líc nebo rub mince. */ private void drawCoin(Graphics g, int sign) { g.drawImage( coin[sign], getWidth() / 2, getHeight() / 2, Graphics.VCENTER | Graphics.HCENTER); } /** * Metoda, která vykresluje celou komponentu. */ protected void paint(Graphics g) { drawCoin(buffer, value); g.drawImage( image, 0, 0, Graphics.TOP | Graphics.LEFT); } /** * Metoda rozhraní CommandListener. Zavolá se, když * uživatel aktivuje nějaký příkaz. */ public void commandAction(Command c, Displayable d) { if (c.equals(cmdThrow)) { value = Math.abs(rand.nextInt()) % 2; repaint(); } else if (c.equals(cmdExit)) { try { midlet.destroyApp(true); } catch (Exception e) { } midlet.notifyDestroyed(); } } /** * Tato metoda je zavolána, stiskne-li uživatel nějakou * klávesu. Kódy kláves najdeme jako konstanty třídy * Canvas. V našem případě namapujeme nový hod * kostkou na klávesu s číslem 5. */ protected void keyPressed(int key) { if (key == Canvas.KEY_NUM5) { commandAction(cmdThrow, this); } } }

Midlet

import javax.microedition.lcdui.Display; import javax.microedition.midlet.MIDlet; import javax.microedition.midlet.MIDletStateChangeException; public class CoinMIDlet extends MIDlet { private Display display = null; protected void startApp() throws MIDletStateChangeException { /* * Podle toho, že je display==null, poznáme, * zda se aplikace spouští, nebo vrací * z pasivního stavu. */ if (display == null) { display = Display.getDisplay(this); display.setCurrent(new CoinCanvas(this)); } } protected void pauseApp() { } protected void destroyApp(boolean arg0) throws MIDletStateChangeException { } public final Display getDisplay() { return display; } }

A jak to vypadá v emulátoru?

Na následujících obrázcích můžete porovnat odlišnost třídy Canvas u různých výrobců. Oba uvedené telefony mají stejnou výšku displeje, ale u Nokie ji nemůže programátor použít celou, protože spodních 10 pixelů zabírají příkazy. Ve speciálním API pro telefony firmy Nokia je sice třída FullCanvas, která má k dispozici celý displej, ale jejím použitím je aplikace omezená pouze na telefony jedné firmy.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Štítky: Články

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *