Rysujemy po ekranie

fatalbomb

Używając klasy Form możemy utworzyć obiekt, na który "wrzucamy" etykiety tekstowe, obrazki, pola tekstowe oraz (tylko w MIDP 2.0) przyciski. Do pisania gier jednak nam to się nie przyda. Aby swobodnie rysować po ekranie musimy sięgnąć po klasę Canvas.

Klasa Canvas

Klasa Canvas jest klasą dziedziczącą, podobnie jak Form, po klasie Displayable. Udostępnia ona powierzchnię ekranu do dowolnych operacji graficznych. Nie dodajemy do niej komponentów, a wykonujemy niskopoziomowe operacje graficzne. Sama klasa Canvas nie udostępnia żadnych możliwości graficznych. Zajmuje się tym klasa Graphics. Zadaniem klasy Canvas jest przenieść obraz z obiektu klasy Graphics na ekran.

Piszemy kod

Klasy Canvas oraz Graphics zawarte są w pakiecie javax.microedition.lcdui, a więc ich użycie nie wymaga dokładania żadnych dodatkowych paczek. Wystarczy sztandarowa konstrukcja: ```java import javax.microedition.lcdui.*; ```

Inaczej niż w przypadku formatek, nie tworzymy bezpośrednio obiektu klasy Canvas. Wyprowadzamy natomiast własną klasę dziedziczącą po niej. Wynika to z faktu, że Canvas deklaruje abstrakcyjną metodę paint, więc wypełnienie jej treścią należy do zadań programisty - co jest bardzo logicznym posunięciem.

W tym artykule będziemy operować na dwóch klasach. Dlatego należy zapamiętać:
Każdą klasę zapisujemy w osobnym pliku o nazwie zgodnej z nazwą klasy i rozszerzeniu .java. Przykładowo: klasę JakasKlasa zapisujemy w pliku o nazwie JakasKlasa.java w folderze src naszego projektu

Jeżeli piszesz ten kod równolegle z czytaniem tego artykułu, utwórz teraz nowy projekt. Główną klasę projektu nazwij TestCanvas. Na razie wystarczy konfiguracja MIDP 1.0.
Do dzieła - piszemy kod naszej klasy. Nie korzystamy tu z klasy MIDlet, dlatego dołączamy tylko jeden pakiet:

import javax.microedition.lcdui.*;

Teraz definiujemy klasę:

public class MyCanvas extends Canvas

i od razu dodajemy definicję wspomnianej metody paint

public void paint (Graphics g)
{
}

Zatem nasz kod wygląda teraz tak:

import javax.microedition.lcdui.*;

public class MyCanvas extends Canvas
{
 public void paint (Graphics g)
  {
  }
}

Teraz zapisujemy nasz kod w odpowiednim pliku. Oczywiście nazwiemy go MyCanvas.java i zapiszemy w folderze src.
Kompilacja czegokolwiek jednak nic nam na razie nie da. Musimy utworzyć główną klasę MIDletu:

// Zapisujemy to jako TestCanvas.java
import javax.microedition.lcdui.*;
import javax.microedition.midlet.MIDlet;

public class TestCanvas extends MIDlet
{
 public TestCanvas()
 {
   Display scrn = Display.getDisplay(this);
   MyCanvas mc = new MyCanvas();
   scrn.setCurrent (mc);
 }
 public void startApp() {}
 public void pauseApp() {}
 public void destroyApp(boolean unc) {}
}

Uruchamiamy nasz kod i... nic. Jeżeli przeanalizowałeś kod, to na pewno wskażesz powód. Oczywiście ewidentnym absurdem jest pozostawienie metody paint pustej. Czas do niej coś dorzucić.
Wpisujemy zatem:

 public void paint (Graphics g)
  {
   g.setColor (0,0,0);
   g.fillRect (0,0,getWidth(), getHeight());
   g.setColor (255,255,255);
   g.drawString ("To jest tekst",30,60,Graphics.LEFT|Graphics.TOP);
   g.setColor (255,0,0);
   g.drawRect (30,80,60,20);
  }

Kompilujemy nasz projekt, uruchamiamy, i powinniśmy zobaczyć coś podobnego do tego:
sek750i_canvas1.png

Zwróć uwagę, że korzystamy z metod obiektu g będącego instancją klasy Graphics. Oczywiście możemy dać dowolny kod w tej metodzie. Do dyspozycji mamy przykładowo:

MetodaOpis
setColor (int r, int g, int b) Ustawia kolor podając składowe RGB
setColor (int kolor) Ustawia kolor podając kolor w formacie 0x00RRGGBB
drawRect (int lx, int ly, int w, int h) Rysuje prostokąt o współrzędnych lewego górnego rogu (lx, ly), szerokości w i wysokości h.
fillRect (int lx, int ly, int w, int h) Podobnie jak fillRect, z tym, że wypełnia prostokąt aktualnym kolorem
drawString (String s, int x, int y, int polozenie) Wstawia tekst s w określonym miejscu. Dla zmiennej polozenie równej Graphics.TOP|Graphics.LEFT podajemy lewy górny róg.
drawImage (Image img, int x, int y, int polozenieJak wyżej, z tym, że wstawia rysunek przechowywany w obiekcie img
Metody getWidth() i getHeight() zwracają wysokość i szerokość powierzchni dostępnej dla programu. Są one zdefiniowane w klasie Displayable, dlatego możemy użyć ich w obrębie całej klasy.

Czytamy kody klawiszy

Klasa Canvas może służyć nie tylko do rysowania. Za jej pomocą możemy również niskopoziomowo odczytać kody naciskanych klawiszy. Do dyspozycji mamy trzy metody:
  • keyPressed(int key) - w momencie naciśnięcia klawisza
  • keyReleased(int key) - w momencie puszczenia klawisza
  • keyRepeated(int key) - przy trzymaniu naciśniętego klawisza
Metody te definiujemy podobnie jak paint, z tym, że nie jest to obligatoryjne. Metody te nie są abstrakcyjne - my je tylko przesłaniamy. ```java public void keyPressed (int key) { } public void keyReleased (int key) { } public void keyRepeated (int key) { } ```

Zastosowanie w praktyce

Napiszemy teraz przydatny program, pozwalający przeczytać kody klawiszy, przy okazji demonstrujący możliwości powyższych trzech metod.
import javax.microedition.lcdui.*;

public class MyCanvas extends Canvas
{
 public MyCanvas()
 {
  s = "";
 }
 public void paint (Graphics g)
  {
   g.setColor (255,255,255);
   g.fillRect (0,0,getWidth(), getHeight());
   g.setColor (0,0,128);
   g.drawString (s, 10, 10, Graphics.TOP|Graphics.LEFT);
  }
  
 public void keyPressed (int key)
 {
  s = "Naciśnięty klawisz: "+key;
  repaint();
 }

 public void keyReleased (int key)
 {
  s = "Puszczony klawisz: "+key;
  repaint();
 }

 public void keyRepeated (int key)
 {
  s = "Przytrzymany klawisz: "+key;
  repaint();
 }
 
 private String s;
}

Na początku przesłaniamy domyślny konstruktor klasy (który jest pusty). Wykonujemy w nim tylko jedną operację - przypisujemy pusty ciąg do zmiennej s. Jest to konieczne, ponieważ String jako typ obiektowy początkowo jest równy null, czyli zmienna s jako referencja nie wskazuje na żaden obiekt. Próba wyświetlenia jej zawartości w metodzie paint niechybnie spowoduje wyrzucenie wyjątku NullPointerException.
Dalszy kod nie zaskakuje niczym szczególnym, poza wywołaniami metody repaint(). Powoduje ona wywołanie metody paint w osobnym wątku, a więc nie wstrzyma to działania MIDletu (ponadto metody paint nie wywołamy ręcznie, ponieważ posiada ona modyfikator dostępu protected).
Dlaczego wcześniej nie używaliśmy metody repaint()? Metoda ta jest wywoływana automatycznie przy starcie MIDletu. Natomiast odświeżenie ekranu w czasie działania aplikacji musi być wywołane jawnie.

Na koniec jeszcze jeden drobiazg, czyli...

Dostęp do całego ekranu

Jak pewnie zdążyłeś zauważyć, nie mamy dostępu do całej powierzchni wyświetlacza. Testowany K750i posiada ekran o wymiarach 176x220 pikseli. Wywołanie takiego prostego kodu: ```java g.drawString (""+getWidth()+"x"+getHeight(), 10, 30, Graphics.TOP|Graphics.LEFT); ``` ujawni, że mamy do dyspozycji tylko 176x176. Pozostałą część ekranu zajmuje pasek powiadomień i podpisy softkeyów. Istnieje jednak metoda, aby zmusić MIDlet do udostępnienia nam wszystkiego. Tym sposobem jest metoda setFullScreenMode klasy Canvas. Jako parametr ona przyjmuje zmienną typu boolean. Gdy jest ona równa true, dostajemy dostęp do całego ekranu. Metodę tą najwygodniej wywołać w konstruktorze naszego MyCanvas: ```java super.setFullScreenMode (true); ``` Po takim zabiegu mamy dostęp do pełnej powierzchni ekranu. Niestety, metoda ta jest dostępna tylko w MIDP 2.0.

Zwróć uwagę, że nasz program nie da się tak łatwo zamknąć. W ramach ćwiczenia spróbuj dopisać do niego odpowiedni kod, według wskazówek w artykule
J2ME - Absolutne początki - część 2 (oczywiście dodajemy polecenia do obiektu klasy MyCanvas).

3 komentarzy

Sprawdź, czy nie wykorzystujesz MIDP 2.0, gdy telefon obsługuje tylko 1.0. Poza tym z telefonami innych firm niż SE może być problem (Samsungi w ogóle nie przyjmują instalek, przynajmniej wysyłanych z SE). Z SonyEricssonami nigdy z tym nie było kłopotów, ale Nokii nie testowałem.

Fajnie tyle że jak chcę wrzucić to na mój telefon (Nokia 6151) to wyskakuje błąd i jedyne co mogę zrobić to zamknąć aplikacje :/

Świetne :) Czekam z niecierpliwością na kolejną część :) Przydało by się coś o rysowaniu grafik i może jakieś timerki żeby można było już jakąś prowizoryczną gierkę zrobić :)