Kdo si J2ME, nezlobí – menu

13. června 2005

Aplikace se nám začíná komplikovat, takže je načase přidat menu, které bude tvořit výchozí obrazovku a pomáhat uživateli s orientací v možnostech programu. A co takhle jich rovnou udělat víc, abychom měli z čeho vybírat?

Jednoduché menu

Nejsnazší je použít k tvorbě menu třídu javax.microedition.lcdui.List. K tomu potřebujeme nejprve vytvořit ikony k jednotlivým položkám seznamu a vymyslet, jak se budou tyto položky jmenovat. Je dobré dodržovat nějaké konvence, aby uživatel našel pod každým názvem vždy to, co očekává. Při vývoji aplikace na různá zařízení a pro různé operátory může trochu komplikovat život, že někteří výrobci a operátoři mají celkem podrobný popis svých konvencí a doporučení a jednotlivé popisy mohou mít protichůdné požadavky.

Aby to bylo ještě trochu složitější, mají také různé telefony odlišnou maximální velikost ikon seznamu. Velikost těchto ikon se někdy dá najít v dokumentaci, ale občas je nutné se prostě pokusit spočítat počet pixelů ikony v obrázku přímo na telefonu a takto získaný výsledek doladit metodou „pokus – omyl“. V našem případě (Nokia řady 40) je odpovídající velikost 16×16 pixelů.

Pro texty položek zatím kvůli jednoduchosti zavedeme řetězcové konstanty. Je dobrým zvykem, že jména konstant obsahují pouze velká písmena. Jsou-li složena z více slov, oddělíme tato slova podtržítkem.

Seznam bude typu javax.microedition.lcdui.List.IMPLICIT. To znamená, že když uživatel vybere nějakou položku seznamu, zavolá aplikační manažer metodu commandAction(Command prikaz, Displayable zobrazitelnaKomponenta) příslušného posluchače událostí. Parametr prikaz této metody bude obsahovat abstraktní příkaz javax.microedition.lcdui.List.SELECT_COMMAND, což nás ale zrovna vůbec nezajímá. Kterou položku seznamu uživatel vybral, zjistíme porovnáním textu vybrané položky s našimi textovými konstantami.

public class MenuList extends List implements CommandListener {
  // texty položek menu
  public static final String PLAY = „Hrát“;
  public static final String HELP = „Nápověda“;
  public static final String EXIT = „Konec“;
  public MenuList(){
    super(„“, List.IMPLICIT);
    try{
      // inicializace obrázků
      Image playImg = Image.createImage(„/playIco.png“);
      Image helpImg = Image.createImage(„/helpIco.png“);
      Image exitImg = Image.createImage(„/exitIco.png“);
      // přidání obrázků do seznamu
      this.append(PLAY, playImg);
      this.append(HELP, helpImg);
      this.append(EXIT, exitImg);
      } catch (Exception e){
      }
      // nastavení posluchače událostí
      this.setCommandListener(this);
  }
  /* metoda rozhraní CommandListener */
  public void commandAction(Command c, Displayable d) {
    // zjištění indexu vybrané položky seznamu
    int i = this.getSelectedIndex();
    // zjištění jména vybrané položky seznamu
    String s = this.getString(i);
    GameMIDlet midlet = GameMIDlet.getInstance();
    // vykonání akce odpovídající vybrané položce
    if(s == PLAY){
      midlet.playAction();
    }else if(s == HELP){
      midlet.helpAction();
    }else if(s == EXIT){
      midlet.destroyApp(true);
    }
  }
}

Výsledek bude na emulátoru Nokia 6230 na Linuxu vypadat takto:

Menu - emulátor Nokia 6230
Menu – emulátor Nokia 6230

Myslíte-li si, že je na obrázku ve slově Nápověda překlep, musím vás zklamat. Takto prostě vypadá čeština na některých emulátorech. Skutečné telefony mají občas s některými českými písmeny sice také problémy, ale ne při tomto způsobu práce s texty. Problémy dělá například načítání diakritiky z deskriptoru aplikace.

Je však stále lepší používat emulátor cílového telefonu se špatnou češtinou, než vlastní skin tohoto telefonu pro J2MEWTK. V J2MEWTK u skinu nelze nastavit velikost ikon v seznamu a výsledek se správnou velikostí pro Nokii řady 40 pak vypadá následovně:

Menu - J2MEWTK
Menu – J2MEWTK

Nízkoúrovňové menu

Při použití třídy javax.microedition.lcdui.Canvas k tvorbě menu máme široké možnosti, co do určení výsledného vzhledu tohoto menu. Jediné, co nás bude omezovat, je naše představivost, velikost aplikace a velikost a počet barev displeje. V tomto článku si ukážeme jednu z mnoha možností, ale přístup je vždy podobný, nezávisle na konkrétním vzhledu. Naše grafické menu bude vypadat takto:

Grafické menu
Grafické menu

K tomuto menu potřebujeme dva obrázky – obrázek pozadí a obrázek položek menu s průhledným pozadím. Při vykreslování menu nakreslíme nejprve pozadí, pak žlutý pruh zvýrazňující vybranou položku a nakonec seznam položek. Budeme odchytávat stisky kláves. Při stisku šipky nahoru nebo dolu změníme zvýrazněnou položku, při vybrání položky zavoláme odpovídající akci.

public class MenuCanvas extends Canvas {
  // obrázek na pozadí
  private Image background;
  // obrázek s položkami menu
  private Image menu;
  // index vybrané položky
  private int selected = 0;
  // barva zvýrazňovacího pruhu
  private int selectedColor = 0xfff38a;
  // výška zvýrazňovacího pruhu
  private int itemHeight = 25;
  // umístění levého horního rohu obrázku
  // s položkami menu
  private int menuY;
  private int menuX;
  // umístění levého horního rohu obrázku
  // s pozadím
  private int bgX;
  private int bgY;
  // počet položek menu
  private int menuItems = 3;
  public MenuCanvas(){
    try{
      // inicializace obrázků
      background = Image.createImage(„/menubg.png“);
      menu = Image.createImage(„/menu.png“);
    } catch (Exception e){
    }
    /* nastavení umístění levého horního rohu
     * pozadí a obrázku s položkami menu tak,
     * aby tyto obrázky byly na displeji vycentrovány
     */
    menuX = (getWidth() – menu.getWidth())/2;
    menuY = (getHeight() – menu.getHeight())/2;
    bgX = (getWidth() – background.getWidth())/2;
    bgY = (getHeight() – background.getHeight())/2;
  }
  /* vykreslení menu */
  protected void paint(Graphics g) {
    // překreslení celého displeje bílou barvou
    g.setColor(0xffffff);
    g.fillRect(0,0,getWidth(), getHeight());
    // nakreslení pozadí
    g.drawImage(background, bgX, bgY,
      Graphics.TOP | Graphics.LEFT);
    // nakreslení žlutého pruhu
    g.setColor(selectedColor);
    g.fillRect(0, menuY + selected * itemHeight,
      getWidth(), itemHeight);
    // nakreslení položek menu
    g.drawImage(menu, menuX, menuY,
      Graphics.TOP | Graphics.LEFT);
  }
  protected void keyPressed(int key) {
    int action = getGameAction(key);
    switch(action){
      case Canvas.UP: // pohyb kurzorem nahoru
        selected = Math.abs((–selected)%menuItems);
        repaint();
        break;
      case Canvas.DOWN: // pohyb kurzorem dolů
        selected = (++selected)%menuItems;
        repaint();
        break;
      case Canvas.FIRE: // vybrání položky
        GameMIDlet midlet=GameMIDlet.getInstance();
        if(selected==0){
          midlet.playAction();
        } else if(selected==1){
          midlet.helpAction();
        } else if(selected==2){
          midlet.destroyApp(true);
        }
    }
  }
}

Jak jste si jistě všimli, obsahuje třída MenuCanvas některé parametry, které mohou být závislé na velikosti displeje nebo na konkrétní aplikaci a které by se hodilo nastavovat beze změny kódu. Jsou to barva zvýrazňovacího pruhu (selectedColor) a výška zvýrazňovacího pruhu (itemHeight). Takovéto parametry aplikace lze například načítat z deskriptoru aplikace pomocí metody getAppProperty(String key) třídy javax.microedition.midlet.MIDlet. Tento přístup však nelze použít vždy, protože některé portály sloužící ke stahování aplikací na mobilní telefony umožňují vložit do deskriptoru pouze několik předdefinovaných povolených atributů. (Načítání z datových souborů necháme na jiný článek.)

Které menu použijeme?

Máme teď vytvořeny dvě implementace menu aplikace a chtěli bychom s co nejmenší námahou vyměnit menu, jež je v aplikaci použito. Rozhodně nechceme kvůli výměně menu měnit zdrojový kód a aplikaci znovu překládat. Dáme si tedy jméno třídy, kterou pro menu použijeme, do deskriptoru aplikace jako atribut a vytvoříme instanci této třídy podle jejího jména.

// získání jména implementace menu z deskriptoru aplikace
String className = this.getAppProperty(„menu-class“);
if(className==null){
  className = „MenuList“;
}
try{
  // vytvoření instance třídy se jménem className
  menu = (Displayable)Class.forName(className).newInstance();
} catch (Exception e){
}

Teď nám k výměně menu stačí pouze změnit obsah atributu menu-class v deskriptoru aplikace a v JAR souboru vyměnit jednu třídu za jinou.

Jedináček neboli singleton

Aby byla aplikace přehledná, umístila jsem základní mechanismus, který řídí přechody mezi obrazovkami aplikace, do třídy GameMIDlet. Hra zatím obsahuje obrazovky menu, nápověda a vlastní hra. Třída GameMIDlet proto obsahuje pro přechod na každou z obrazovek odpovídající metodu (menuAction(), helpAction() a playAction()).

Třída GameMIDlet je typickým představitelem třídy, která v rámci aplikace existuje právě v jedné instanci. Tento návrhový vzor bývá nazýván jedináček nebo singleton (podle toho, zda je dotyčný zastáncem české nebo anglické terminologie). Jelikož má tedy třída GameMIDlet pouze jednu instanci, nemusíme ji ostatním třídám předávat jako parametr. Uložíme si odkaz na tuto instanci do statické proměnné instance a zpřístupníme ji statickou metodou getInstance().

Ke stažení

Veškeré zde uvedené zdrojové kódy, obrázky ke hře a hotovou aplikaci si můžete stáhnout a použít pro vlastní potřebu.

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

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

Předchozí článek bicycle-tours.cz
Š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 *