Pull to refresh

Java ME: Структура мидлета

Reading time4 min
Views7.2K
В данной статье мы подробно разберём один из вариантов построения структуры мидлета. Данный материал будет полезен для начинающих.

Представим себе следующую ситуацию


У нас есть 3 экрана:

  1. Экран «Сплэш» (который будет отображаться первым);
  2. Экран «Меню»;
  3. Экран «Игра».

Для примера я не буду писать 3D игры и меню с анимацией космических боёв, ибо это будет только отвлекать. Каждый из экранов будет выполнять следующие действия:

  • Экран «Сплэш» — выводит на 10 секунд текст «SPLASH»;
  • Экран «Меню» — выводит на 10 секунд текст «MENU»;
  • Экран «Игра» — выводит текст «GAME».

Итак, имея уже эти сведения, для наших экранов можно подобрать общий абстрактный класс, и имя ему — «Screen».

Что общего между всеми экранами?

  • У каждого экрана есть свои максимальные размеры;
  • У каждого экрана есть свой объект для рисования;
  • Так как мы рисуем на экране только после его установки на дисплей, то можно определить общее событие:
  • start — вызывается после установки экрана на Дисплей.


Абстрактный класс для всех экранов


public abstract class Screen extends GameCanvas {
    
    protected final int screenWidth; //ширина экрана
    protected final int screenHeight; //высота экрана
    protected final Graphics graphics; //объект для рисования
    
    public Screen() {
        
        super(false);
        setFullScreenMode(true);
        screenWidth = getWidth();
        screenHeight = getHeight();
        graphics = getGraphics();
    }
    
    //метод, вызывающийся первым, после установки экрана на дисплей
    public abstract void start();
}

Мы унаследовали наш общий интерфейс от GameCanvas и внесли в него некоторые изменения:
Добавили собственные поля с модификатором protected и определили общее событие.
protected поля могут видеть только классы-потомки.
abstract в определении метода означает, что метод является абстрактным. То есть мы не пишем тело метода, но точно знаем, что потомки его реализуют (если только они не будут абстрактными). Если метод содержит как минимум один абстрактный метод, класс должен быть помечен абстрактным, иначе компилятор выдаст ошибку.

Далее, создавая наши экраны, мы будет наследовать их уже от Screen, а не от GameCanvas. У нас получается следующая иерархия:

Object -> GameCanvas -> Screen -> MyScreen

В Java все классы неявно наследуются от Object. Screen унаследован от GameCanvas, чтобы оставаться «холстом» для рисования и обработки нажатий (хотя это далеко не всё). Все наши экраны му будем наследовать от Screen для того, чтобы определить общий интерфейс. Было бы не совсем правильно писать одно и то же в каждом классе, тем более что общий интерфейс нам понадобится ещё для одной конструкции, о которой вы узнаете позже.

Главный класс


public class MIDletStructure extends MIDlet {

    public static MIDletStructure midlet; //указывает на себя
    private final Display display;
    
    public MIDletStructure() {
        
        midlet = this;
        display = Display.getDisplay(this);
    }
    
    //установить новый экран
    public void setCanvas(Screen canvas) {
        
        display.setCurrent(canvas);
        canvas.start();
    }
    
    public void startApp() {}
    public void pauseApp() {}
    public void destroyApp(boolean unconditional) {}
}

В главном классе определяем поля midlet и display. Первый ссылается на самого себя, а второй представляет собой ссылку на Display. Ссылка midlet нужна для того, чтобы иметь связь с главным классом. Например, чтобы выключить приложение или установить другой экран, чем мы и займёмся позже.

Рассмотрим метод setCanvas подробнее: метод setCanvas принимает в параметре ссылку на любой класс, унаследованный от Screen, затем, так как Screen унаследован от GameCanvas (который в свою очередь унаследован от Displayable), то устанавливает его на дисплей и с помощью метода start запускает его. Вся загвоздка в Screen — мы можем передать в метод экземпляр любого класса, унаследованного от Screen, будь то экран меню, или игры, а благодаря общему интерфейсу мы точно знаем, что у любого экземпляра есть реализованный метод start. Всё это осуществляется благодаря позднему связыванию в Java, если бы не оно, то для каждого класса-потомка Screen пришлось бы писать отдельный метод.

Первый экран


public class Splash extends Screen {
    
    //метод, вызывающийся первым, после установки экрана на дисплей
    public void start() {
        
        graphics.setColor(0xFFFFFF);
        graphics.fillRect(0, 0, screenWidth, screenHeight);
        graphics.setColor(0x000000);
        graphics.drawString("SPLASH", 0, 0, 0);
        flushGraphics();
        try {
            
            Thread.sleep(10000);
        } catch (InterruptedException interruptedException) {
            
            System.out.println(interruptedException.getMessage());
            MIDletStructure.midlet.notifyDestroyed();
        }
    }
}

Легко заметить, что в методе start мы уже пользуемся классами, которые инициализировали в нашем классе-предке.

Теперь пишем подобный класс для экрана игры:

public class Game extends Screen {
    
    //метод, вызывающийся первым, после установки экрана на дисплей
    public void start() {
        
        graphics.setColor(0xFFFFFF);
        graphics.fillRect(0, 0, screenWidth, screenHeight);
        graphics.setColor(0x000000);
        graphics.drawString("GAME", 0, 0, 0);
        flushGraphics();
        while (true) {
            
            try {
                
                Thread.sleep(20);
            } catch (InterruptedException interruptedException) {
                
                System.out.println(interruptedException.getMessage());
                MIDletStructure.midlet.notifyDestroyed();
            }
        }
    }
}

И добавляем ссылки на наши экраны в главный класс:

public class MIDletStructure extends MIDlet {

    public static MIDletStructure midlet; //указывает на себя
    private final Display display;
    public final Screen splash; //экран “сплэш”
    public final Screen menu; //экран меню
    public final Screen game; //экран игры
    
    public MIDletStructure() {
        
        midlet = this;
        display = Display.getDisplay(this);
        splash = new Splash();
        menu = new Menu();
        game = new Game();
    }
    
    //установить новый экран
    public void setCanvas(Screen canvas) {
        
        display.setCurrent(canvas);
        canvas.start();
    }
    
    public void startApp() {
        
        //устанавливаем первый экран
        setCanvas(splash);
    }
    
    public void pauseApp() {}
    public void destroyApp(boolean unconditional) {}
}

Обратим внимание на ссылку midlet: она является статистической, публичной и указывает на экземпляр класса, в котором находится. Зачем это нужно? public обозначает, что к ссылке можно обратиться в любом месте программы, static обозначает, что к ссылке можно обратиться через класс (Main.midlet), а так как все поля экранов и метод setCanvas являются тоже публичными, следовательно, экран можно заменить в любом месте программы:

Main.midlet.setCanvas(Main.midlet.game);

Заключение


После первого прочтения новичок, наверное, не поймёт ни единого слова, однако при более вдумчивом чтении можно заметить всю простоту алгоритма и его логичное решение, используя технологию ООП. В статье я постарался описать детально всё, что необходимо, однако если у вас остались вопросы или неточности, можете посмотреть полные исходники ниже:

Полные исходники
Tags:
Hubs:
-1
Comments7

Articles