Mobiltelefonapplikationer med Java ME: Lektion 10

Idag: Att rita saker på en rityta ("canvas")

Klicka på startknappen i den lilla mediespelaren ovan för att lyssna på lektionen. (Man kan behöva vänta en stund på att ljudfilen laddas ner.) Om mediespelaren inte syns, eller om det inte fungerar av något annat skäl, kan man klicka här för att ladda ner mp3-filen (ca 20 minuter, ca 9 megabyte). Beroende på hur webbläsaren är konfigurerad kan det kräva ett separat mp3-spelarprogram av något slag.

Bild(er) 1: Att rita bilder

Man kan gära mycket med formulärklassen Form och de olika subklasserna till Item (Gauge, TextField och så vidare). Men för att göra snygga, lättanvända gränssnitt, behöver man rita upp sakerna själv. Och ännu mer om man ska göra spel.

Inställningsformuläret i QuadraPop Spelfönstret i QuadraPop

Bild 2: Klassen Canvas

Ordet "canvas" betyder segelduk, tältduk, målarduk, men även målning, tavla (för man målar ju tavlan på en duk).

Med klassen Canvas kan man rita bilder, som till exempel den här:

En canvas-rityta

Bild(er) 3: Koordinaterna

Koordinaterna på en Canvas

Det röda strecket är ritat med de här två anropen:

    setColor(0xff0000); // Sätt färgen till röd
    drawLine(2, 1, 6, 5); // Rita linjen mellan punkterna (2, 1) och (6, 5)

"Bild" 4: Klassen Ritcanvas

Man använder inte klassen Canvas direkt, utan den är en abstrakt klass som man ärver från (ungefär som klassen MIDlet).

Vi gör en egen canvas, Ritcanvas, för att rita upp kvadraten och cirkeln från exemplet ovan.

Klassen Ritcanvas, ur programmet CanvasTest1. Utdrag ur filen CanvasTest1.java:

    1   class Ritcanvas extends Canvas {
    2       public void paint(Graphics g) {
    3           int w = getWidth();
    4           int h = getHeight();
    5
    6           // Måla bakgrunden, och radera därmed allt som fanns där förut
    7           g.setColor(0xff0000);
    8           g.fillRect(0, 0, w-1, h-1);
    9
   10           // Rita en kvadrat (eller...?)
   11           g.setColor(0x000000);
   12           g.drawRect(1, 1, w/2 - 2, h/2 - 2);
   13
   14           // Rita en cirkel (eller...?)
   15           g.setColor(0x000000);
   16           g.fillArc(w/2, h/2, w/2-1, h/2-1, 0, 360);
   17       }
   18   }

Likadant som i skrivbords-Java: När man gör egna bilder, är det metoden paint som ritar upp bilden. Den körs inte bara en gång, utan varje gång något behöver ritas upp, till exempel om fönstret varit dolt. (Det räcker om en enda pixel av fönstret varit dold!)

"Bild" 5: Klassen CanvasTest1

Klassen Ritcanvas kan nu användas som andra sorters Displayable (Form, TextBox, List och Alert). Dvs, vi kan skriva en MIDlet som först skapar en Ritcanvas, lägger till kommandon med addCommand, och sen visar den med setCurrent.

Klassen CanvasTest1, ur programmet CanvasTest1. Utdrag ur filen CanvasTest1.java:

    1   public class CanvasTest1 extends MIDlet implements CommandListener {
    2       private Display display;    
    3       private TextBox om_rutan =
    4           new TextBox("Om programmet",
    5                       "Ett program som visar hur man ritar figurer på en Canvas. " +
    6                       "Av Thomas Padron-McCarthy.", 100, TextField.ANY);
    7       private Ritcanvas canvasen = new Ritcanvas();
    8   
    9       private Command tillbaka_kommandot = new Command("Tillbaka", Command.BACK, 1);
   10       private Command avsluta_kommandot = new Command("Avsluta", Command.EXIT, 1);
   11       private Command om_kommandot = new Command("Om programmet", Command.SCREEN, 1);
   12   
   13       public CanvasTest1() {
   14           om_rutan.addCommand(tillbaka_kommandot);
   15           om_rutan.addCommand(avsluta_kommandot);
   16           om_rutan.setCommandListener(this);
   17           canvasen.addCommand(om_kommandot);
   18           canvasen.addCommand(avsluta_kommandot);
   19           canvasen.setCommandListener(this);
   20       }
   21   
   22       public void startApp() {
   23           display = Display.getDisplay(this);
   24           display.setCurrent(canvasen);
   25       }
   26   
   27       public void destroyApp(boolean unconditional) { 
   28   
   29       }
   30   
   31       public void pauseApp() {
   32   
   33       }
   34   
   35       public void commandAction(Command kommandot, Displayable s) {
   36           if (kommandot == avsluta_kommandot) {
   37               System.out.println("avsluta_kommandot");
   38               destroyApp(true);
   39               notifyDestroyed();
   40           }
   41           else if (kommandot == om_kommandot) {
   42               System.out.println("om_kommandot");
   43               display.setCurrent(om_rutan);
   44           }
   45           else if (kommandot == tillbaka_kommandot) {
   46               System.out.println("tillbaka_kommandot");
   47               display.setCurrent(canvasen);
   48           }
   49       }
   50   }

Bild 6: Den uppritade Ritcanvas

En canvas-rityta

Bild 7: Snett?

Kvadraten blir inte riktigt liksidig, och cirkeln inte riktigt rund. Det beror på höjd/bredd-förhållandet på skärmen. På en lite bredare telefon:

En canvas-rityta

Bild 8: Om-rutan

En canvas-rityta

Bild 9: En bild som går att ändra

En bild som går att ändra

"Bild" 10: Den nya Ritcanvas

Klassen Ritcanvas, ur programmet CanvasTest2. Filen Ritcanvas.java:

    1   import javax.microedition.midlet.*;
    2   import javax.microedition.lcdui.*;
    3   
    4   public class Ritcanvas extends Canvas {
    5       private boolean kvadrat = false;
    6       private boolean cirkel = false;
    7   
    8       public void paint(Graphics g) {
    9           int w = getWidth();
   10           int h = getHeight();
   11           g.setColor(0xff0000);
   12           g.fillRect(0, 0, w-1, h-1);
   13           if (kvadrat) {
   14               g.setColor(0x000000);
   15               // g.drawRect(1, 1, w/2 - 2, h/2 - 2); -- Fel!
   16               int min = Math.min(h, w);
   17               g.drawRect(1, 1, min/2 - 2, min/2 - 2);
   18           }
   19           if (cirkel) {
   20               g.setColor(0x000000);
   21               // g.fillArc(w/2, h/2, w/2-1, h/2-1, 0, 360); -- Fel!
   22               int min = Math.min(h, w);
   23               g.fillArc(w/2, h/2, min/2-1, min/2-1, 0, 360);
   24           }
   25       }
   26   
   27       public void togglaKvadraten() {
   28           kvadrat = ! kvadrat;
   29           repaint();
   30       }
   31   
   32       public void togglaCirkeln() {
   33           cirkel = ! cirkel;
   34           repaint();
   35       }
   36   }

"Bild" 11: Klassen CanvasTest2

Klassen CanvasTest2, ur programmet CanvasTest2. Filen CanvasTest2.java:

    1   import javax.microedition.midlet.*;
    2   import javax.microedition.lcdui.*;
    3   
    4   public class CanvasTest2 extends MIDlet implements CommandListener {
    5       private Display display;    
    6       private TextBox om_rutan =
    7           new TextBox("Om programmet",
    8                       "Ett program som visar hur man ritar figurer på en Canvas. " +
    9                       "Av Thomas Padron-McCarthy.", 100, TextField.ANY);
   10       private Ritcanvas canvasen = new Ritcanvas();
   11   
   12       private Command tillbaka_kommandot = new Command("Tillbaka", Command.BACK, 1);
   13       private Command avsluta_kommandot = new Command("Avsluta", Command.EXIT, 1);
   14       private Command om_kommandot = new Command("Om programmet", Command.SCREEN, 1);
   15       private Command kvadrat_kommandot = new Command("Toggla kvadraten", Command.SCREEN, 1);
   16       private Command cirkel_kommandot = new Command("Toggla cirkeln", Command.SCREEN, 1);
   17   
   18       public CanvasTest2() {
   19           om_rutan.addCommand(tillbaka_kommandot);
   20           om_rutan.addCommand(avsluta_kommandot);
   21           om_rutan.setCommandListener(this);
   22           canvasen.addCommand(om_kommandot);
   23           canvasen.addCommand(avsluta_kommandot);
   24           canvasen.addCommand(kvadrat_kommandot);
   25           canvasen.addCommand(cirkel_kommandot);
   26           canvasen.setCommandListener(this);
   27       }
   28   
   29       public void startApp() {
   30           display = Display.getDisplay(this);
   31           display.setCurrent(canvasen);
   32       }
   33   
   34       public void commandAction(Command kommandot, Displayable s) {
   35           if (kommandot == avsluta_kommandot) {
   36               System.out.println("avsluta_kommandot");
   37               destroyApp(true);
   38               notifyDestroyed();
   39           }
   40           else if (kommandot == om_kommandot) {
   41               System.out.println("om_kommandot");
   42               display.setCurrent(om_rutan);
   43           }
   44           else if (kommandot == tillbaka_kommandot) {
   45               System.out.println("tillbaka_kommandot");
   46               display.setCurrent(canvasen);
   47           }
   48           else if (kommandot == kvadrat_kommandot) {
   49               System.out.println("kvadrat_kommandot");
   50               canvasen.togglaKvadraten();
   51           }
   52           else if (kommandot == cirkel_kommandot) {
   53               System.out.println("cirkel_kommandot");
   54               canvasen.togglaCirkeln();
   55           }
   56       }
   57   
   58       public void destroyApp(boolean unconditional) { 
   59   
   60       }
   61   
   62       public void pauseApp() {
   63   
   64       }
   65   }

"Bild" 12: Mer saker som man kan göra med en Canvas

  • Visa text (med drawString)
  • Använda olika fonter (nåja...)
  • Visa riktiga bilder (jpeg, png)
  • Hantera händelser: keyPressed, keyReleased, keyRepeated
  • Saker som flyttas runt, eller rör sig själva

"Bild" 13: Klassen GameCanvas

  • En subklass till Canvas
  • Särskilt för att underlätta programmering med trådar, så saker kan röra sig av sig själva
  • Sprites (små flyttbara bildelement), och inbyggd kollisons-detektering.
  • Kommer i nästa lektion.

Läsanvisningar

Man kan läsa mer om klassen Canvas i kapitel 9, Creating Custom Screens, i den nya kursboken Kicking Butt with MIDP and MSA.

I den gamla kursboken Beginning J2ME kan man läsa kapitel 13, Programming a Custom User Interface.

Programmeringsövningar

  1. Gör en egen MIDlet som ritar upp några olika figurer: cirklar, kvadrater, ellipser, rektanglar, streck.
  2. Gör en egen MIDlet där man kan flytta runt ett kryss på skärmen. Nu kan metoden paint inte rita samma bild hela tiden, utan vi måste hålla reda på var krysset ska vara, och rita just där. Glöm inte att anropa repaint, så paint faktiskt körs.
  3. Lägg till en markera-knapp, som sätter en markering på skärmen där krysset är just nu. Notera att nu behöver vi hålla reda på alla markeringarna i en särskild lista, så metoden paint vet vilka markeringar som finns.
  4. Gör uppgiften i hemtentan från 2008-05-31.
  5. Komplettera det programmet med att spara objektens positioner i ett postlager, så att positionerna finns kvar om programmet startas om. (Den här uppgiften handlar kanske mer om postlager än om grafik, men tanken är att visa att sakerna som ritas upp egentligen också är en sorts data, och att de kan representeras med tal och strängar som man exempelvis sparar i ett postlager.)

Föregående lektion | Lektionslista | Nästa lektion


Thomas Padron-McCarthy (Thomas.Padron-McCarthy@oru.se), 22 mars 2009