callSerially(Runnable r)
Вызов этого метода заставляет ВМ вызывать метод run() объекта r сразу после окончания перерисовки экрана. Вызов осуществляется только один раз.
Объекты класса Canvas могут реагировать на нажатие кнопок при помощи методов:
keyPressed(int key)
keyReleased(int key)
keyRepeated(int key)
Использовать коды кнопок напрямую не рекомендуется. Для получения действия, связанного с тем или иным кодом кнопки, используется метод:
int getGameAction(int key)
Он возвращает код действия (коды определены как константы в классе Canvas на пример FIRE). Есть и обратный ему метод:
int getKeyCode(int gameAction)
Для каждого объекта класса Displayable может быть задан набор команд, определенных пользователем. Каждая команда является объектом класса Command и создается при помощи конструктора:
Command(String command, int type, int priority)
Для добавления и удаления команд в классе Displayable предусмотрены методы:
void addCommand(Command c)
void removeCommand(Command c)
Команды, в зависимости от их типа, могут закрепляться за кнопками под экраном телефона или заноситься в экранное меню (это делается автоматически). При этом над соответствующей кнопкой отображается имя команды.
Для того, чтобы мидлет мог обрабатывать команды, он должен объявлять (implements) интерфейс CommandListener. У этого интерфейса есть единственный метод: void commandAction(Command c, Displayable d), который вызывается после того, как пользователь выберет команду c.
Для того, чтобы объявить в объекте класса Displayable обработчик команд listener, используется метод этого класса:
void addListener(CommandListener listener)
Давайте создадим простейшее MIDP приложение-заготовку для нашей игры, на основе игры «червяк».
package example.wormgame;
import java.lang.Thread;
// подключаем требуемые нам компоненты
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.Item;
import javax.microedition.lcdui.Gauge;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
/**
* Основной класс нашего мидлета
*/
public class WormMain extends MIDlet implements CommandListener {
/** Класс описывающий "червяка" */
private WormPit theGame;
/** Кнопка выхода из игры. */
private Command exitCmd = new Command("Exit", Command.EXIT, 3);
/** Элемент меню, поменять уровень сложности. */
private Command levelCmd = new Command("Change Level", Command.SCREEN, 2);
/** Элемент меню, начать новую игру. */
private Command startCmd = new Command("Start", Command.SCREEN, 1);
/** Элемент меню, перезапустить игру. */
private Command restartCmd = new Command("Restart", Command.SCREEN, 1);
/** Элемент меню, вернутся в игру без извенений. */
private Command cancelCmd = new Command("Cancel", Command.ITEM, 1);
/** Элемент меню, для подтвержждения выбранных установок. */
private Command OKCmd = new Command("OK", Command.OK, 1);
/**
* Конструктор по умолчанию, в котором создаются графические вомпоненты и
* устанавливается command listener.
*/
public WormMain() {
theGame = new WormPit();
theGame.addCommand(exitCmd);
theGame.addCommand(levelCmd);
theGame.addCommand(startCmd);
theGame.addCommand(audioOnCmd);
theGame.setCommandListener(this);
}
/**
* Деструктор для очистки памяти занятой приложением.
*/
protected void destroyApp(boolean unconditional) {
theGame.destroyGame();
Display.getDisplay(this).setCurrent((Displayable)null);
}
/**
* Приостановка работы приложения
*/
protected void pauseApp() {
}
/**
* Запуск приложения
*/
protected void startApp() {
Display.getDisplay(this).setCurrent(theGame);
try {
// Запуск игры в отдельном потоке
Thread myThread = new Thread(theGame); // создаём новый поток
myThread.start(); // запуск потока
} catch (Error e) {
destroyApp(false);
notifyDestroyed();
}
}
/**
* Выполнения функций приложения в ответ на действия пользователя.
*/
public void commandAction(Command c, Displayable d) {
if (c == restartCmd) {
theGame.restart();
} else if (c == levelCmd) {
Item[] levelItem = {
new Gauge("Level", true, 9, theGame.getLevel())};
Form f = new Form("Change Level", levelItem);
f.addCommand(OKCmd);
f.addCommand(cancelCmd);
f.setCommandListener(this);
Display.getDisplay(this).setCurrent(f);
} else if (c == exitCmd) {
destroyApp(false);
notifyDestroyed();
} else if (c == startCmd) {
theGame.removeCommand(startCmd);
theGame.addCommand(restartCmd);
theGame.restart();
} else if (c == OKCmd) {
Form f = (Form)d;
Gauge g = (Gauge)f.get(0);
theGame.setLevel(g.getValue());
Display.getDisplay(this).setCurrent(theGame);
} else if (c == cancelCmd) {
Display.getDisplay(this).setCurrent(theGame);
}
}
Теперь необходимо создать меню и прочие графические компоненты на экране мобильного устройства.
public class WormPit extends Canvas implements Runnable {
/** Очки в игре. */
private int score = 0;
/** Уровень сложности. */
private int level = 5;
/** Ширина экрана в пикселях. */
static int CellWidth;
/** Длина экрана в пикселях. */
static int CellHeight;
/** Высота шрифта для вывода на экран счёта. */
private static final int SCORE_CHAR_HEIGHT;
/** Ширина шрифта для вывода на экран счёта. */
private static final int SCORE_CHAR_WIDTH;
/** Время по умолчанию между перерисовкой червя (400 milliseconds) */
private static final int DEFAULT_WAIT = 400;
/** Цвет шрифта. (0xff0000) */
static final int TEXT_COLOUR = 0x00ff0000;
/** Размер клетки червя. */
public static final int CELL_SIZE = 5;
// Установка размера шрифта
static {
Font defaultFont = Font.getDefaultFont(); // взять шрифт по умолчанию
SCORE_CHAR_WIDTH = defaultFont.charWidth('S');
SCORE_CHAR_HEIGHT = defaultFont.getHeight();
SCORE_HEIGHT = SCORE_CHAR_HEIGHT * 2;
}
/**
* Конструктор. Задания ширины и высоты червя.
*/
public WormPit() {
width = round(getWidth());
height = round(getHeight()-SCORE_HEIGHT);
WormPit.CellWidth = (width-(START_POS*2)) / WormPit.CELL_SIZE;
WormPit.CellHeight = (height-(START_POS*2)) / WormPit.CELL_SIZE;
myWorm = new Worm(this);
/**
* Обработчик событий от нажатия клавишь на мобильном устройстве.
* Стрелки(джойстик) на мобильном устройстве (UP, DOWN, LEFT, RIGHT)
*/
public void keyPressed(int keyCode) {
switch (getGameAction(keyCode)) {
case Canvas.UP:
myWorm.setDirection(Worm.UP);
break;
case Canvas.DOWN:
myWorm.setDirection(Worm.DOWN);
break;
case Canvas.LEFT:
myWorm.setDirection(Worm.LEFT);
break;
case Canvas.RIGHT:
myWorm.setDirection(Worm.RIGHT);
break;
case 0:
// можно использовать клавиши с номерами 2,4,6,8
switch (keyCode) {
case Canvas.KEY_NUM2:
myWorm.setDirection(Worm.UP);
break;
case Canvas.KEY_NUM8:
myWorm.setDirection(Worm.DOWN);
break;
case Canvas.KEY_NUM4:
myWorm.setDirection(Worm.LEFT);
break;
case Canvas.KEY_NUM6:
myWorm.setDirection(Worm.RIGHT);
break;
}
break;
}
}
/**
* Перерисовка экрана и всех объектов.
*/
private void paintPitContents(Graphics g) {
try {
myWorm.update(g); // update worm position
/* логика проверки съел ли червь объект или нет и подсчсёт очков
для вывода на экран */
g.setColor(WormPit.ERASE_COLOUR);
g.fillRect((width - (SCORE_CHAR_WIDTH * 3))-START_POS,
height-START_POS,
(SCORE_CHAR_WIDTH * 3),
SCORE_CHAR_HEIGHT);
g.setColor(WormPit.DRAW_COLOUR);
// Отобразить новый счёт
g.drawString("" + score,
width - (SCORE_CHAR_WIDTH * 3) - START_POS,
height - START_POS, g.TOP|g.LEFT);
} catch (WormException se) {
gameOver = true;
}
}
/**
* Вывод на экран всех компонентов
*/
public void paint(Graphics g) {
if (forceRedraw) {
// Перерисовать весь экран
forceRedraw = false;
// Очистить задний план
g.setColor(WormPit.ERASE_COLOUR);
g.fillRect(0, 0, getWidth(),
getHeight());
// Нарисовать границы поля
g.setColor(WormPit.DRAW_COLOUR);
g.drawRect(1, 1, (width - START_POS), (height - START_POS));
// Отобразить текущий счёт
g.drawString("L: " + level, START_POS, height, g.TOP|g.LEFT);
g.drawString("" + score,
(width - (SCORE_CHAR_WIDTH * 3)),
height, g.TOP|g.LEFT);
// Отобразить наивысший счёт на этом уровне
g.drawString("H: ",
(width - (SCORE_CHAR_WIDTH * 4)),
(height + SCORE_CHAR_HEIGHT),
g.TOP|g.RIGHT);
g.drawString("" + WormScore.getHighScore(level),
(width - (SCORE_CHAR_WIDTH * 3)),
(height + SCORE_CHAR_HEIGHT),
g.TOP|g.LEFT);
// Нарисовать червя и еду
g.translate(START_POS, START_POS);
g.setClip(0, 0, CellWidth*CELL_SIZE, CellHeight*CELL_SIZE);
myWorm.paint(g);
myFood.paint(g);
} else {
// Нарисовать червя и еду
g.translate(START_POS, START_POS);
}
/**
* Вызывает перерисовку экрана и компонентов при съёме паузы
*/
protected void hideNotify() {
super.hideNotify();
forceRedraw = true;
if (!gameOver) {
gamePaused = true;
}
}
/**
* Основной цикл выполнения MIDP приложения
*/
public void run() {
while (!gameDestroyed) {
try {
synchronized (myWorm) {
/* логика вычислений очков двидения червя и действий пользователя*/
repaint();
}
}
} catch (java.lang.InterruptedException ie) {
}
}
}
/**
* Вызывает событие уничтожения приложения
*/
public void destroyGame() {
synchronized (myWorm) {
gameDestroyed = true;
//myWorm.notifyAll();
myWorm.notifyAll();
}
}
}
Приложение 2. Примеры создания MIDP приложений
Для выполнения задания номер 2 потребуется создать TCP соединение с помощью сокетов и форму вводу передаваемых значений.
Для начала создадим MIDP приложение.
/**
* Основной класс MIDP приложения
*/
public class SocketMIDlet extends MIDlet implements CommandListener {
private final static String SERVER = "Server";
private final static String CLIENT = "Client";
private static String[] names = {SERVER, CLIENT};
private static Display display; // дисплей
private Form f; // форма
private ChoiceGroup cg;
private boolean isPaused;
private Server server;
private Client client;
// левая функциональная кнопка на мобильном устройстве
private Command exitCommand = new Command("Exit", Command.EXIT, 1);
// правая функциональная кнопка на мобильном устройстве
private Command startCommand = new Command("Start", Command.ITEM, 1);
/**
* Конструктор. создаёт графические компоненты на экране.
* И устанавливает обработчики событий.
*/
public SocketMIDlet() {
display = Display.getDisplay(this);
f = new Form("Socket Demo");
cg = new ChoiceGroup("Please select peer",
Choice.EXCLUSIVE, names, null);
f.append(cg);
f.addCommand(exitCommand);
f.addCommand(startCommand);
f.setCommandListener(this);
display.setCurrent(f);
}
public boolean isPaused() {
return isPaused;
}
/**
* Запуск приложения
*/
public void startApp() {
isPaused = false;
}
/**
* Приостановка приложения
*/
public void pauseApp() {
isPaused = true;
}
/**
* Остановка приложения
*/
public void destroyApp(boolean unconditional) {
if (server != null) {
server.stop();
}
if (client != null) {
client.stop();
}
}
/**
* Обработчик событий.
*/
public void commandAction(Command c, Displayable s) {
if (c == exitCommand) {
destroyApp(true);
notifyDestroyed();
} else if (c == startCommand) {
String name = cg.getString(cg.getSelectedIndex());
if (name.equals(SERVER)) {
server = new Server(this);
server.start();
} else {
client = new Client(this);
client.start();
}
}
}
}
Для создания соединения с сервером с помощью сокетов потребуется следующая конструкция:
// Установить сокет соединение на 5000 порту с localhost
SocketConnection sc = (SocketConnection) Connector.open("socket://localhost:5000");
// Входной поток для записи онформации в сокет
InputStream is = sc.openInputStream();
// Выходной поток для чтения информации из сокета
OutputStream os = sc.openOutputStream();
Для открытия соединения для ожидания соединения потребуется следующая конструкция:
// Установить сокет на 5000 порту
ServerSocketConnection scn = (ServerSocketConnection) Connector.open("socket://:5000");
// Ожидать соединения от других машин
SocketConnection sc = (SocketConnection) scn.acceptAndOpen();
1. Кен Арнольд, Джеймс Гослинг, Дэвид Холмс. Язык программирования Java™.
2. Официальный сайт Java - http://java.sun.com/ (есть раздел на русском языке с учебником).
3. Java™ 2 SDK, Micro Edition Documentation – http://java.sun.com/products/midp/index.jsp