Платформа программирования J2ME для портативных устройств

         

Canvas может отображать изображение



Листинг 6.10. Чтобы отобразить изображение, Canvas просто «рисует» объект изображения с помощью процедуры рисования изображения объекта Graphics

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import Java.io.lOException;
/*
Демонстрирует двойную буферизацию изображений в Canvas.
Изображения автоматически дважды буферизируются.
Эта программа демонстрирует, что вы ничего не должны делать для получения
поведения двойной буферизации при отображении изображений.
Однако вам все равно придется провести двойную буферизацию
операции, которая рисует фон Canvas, до рисования изображения.
*/
public class DoubleSufferlmageDemo extends Canvas
implements CommandListener
{


// Константа, которая представляет белый цвет.
private static final int WHITE = OxFF « 16 I OxFF « 8 I OxFF;
private static Command back = new Command ("Back", Command.BACK, 1);
private GraphicsDemo gDemo = GraphicsDemo.getlnstance();
private Display display = Display .getDisplay (gDerno) ;
// Ссылка на Image, которое отображает этот объект. Image image;
// Переменная, используемая для определения того,' осуществляет
// ли реализация автоматическую двойную буферизацию.
// Принимает значение «true», если реализация осуществляет
// автоматическую двойную буферизацию,
«false» в ином случае, private boolean autoDoubleBuffered = true;
/**
Конструктор No-arg.
*/
public DoubleBufferlmageDemo()
{
super();
if (!isDoubleBuffered())
{
autoDoubleBuffered = false;
}
// Создайте изображение PNG. Изображение «нарисовано» в
// изменяемом объекте Image, который имеет свой собственный
// внеэкранный Graphics. Мы сейчас создаем изображение в
// конструкторе, вместо метода paint (),
//так что оно создается только один раз. try
}
image = Image.createlraage("/bottle80x80.png" );
}
catch (lOException ioe)
{
System.out.println(ioe.getMessage()); ioe.printStackTracef);
}
addCommand(back); setCommandListener(this); display.setCurrent (this);
}
protected void paintClipRect(Graphics g)
{
int clipX = g.getClipX{} ;
int clipY = g.getClipY ();
int clipH = g.getClipHeight();
int clipW = g.getClipWidth () ;
int color = g.getColor();
g.setColor(WHITE);
g.fillRecc(clipX, clipY, clipW, clipH);
g.setColor (color);
/**
Рисует изображение на видимом Canvas этого объекта.
*/ public void paint(Graphics g)
Graphics originalG = null; int width = getWidth () ;
int height = getHeight () ;
if (image == null)
{
return; 1
// Мы все равно нуждаемся в двойной буферизации операций
// рисования, которые очищают графику Canvas, if (!autoDoubleBuffered)
{
// Сохраняет первоначальный графический контекст и использует
// внеэкранный Graphics из Image для очистки отсекаемого
// прямоугольника. originalG = g; g = image.getGraphics ();
paintClipRect (g);
}
else 1
// Нарисуйте фон с первоначальным Graphics, переданным в него. paintClipRect(g);
{
// Нам не нужна двойная буферизация вызова отображения Image.
// Вызов этого метода рисует изображение во
// внеэкранном Graphics объекта Image, копируя затем его
// содержимое в контекст Graphics устройства неявно.
g.drawlmage(image, 0, 0, Graphics.TOP I Graphics.LEFT);
public void commandAction(Command c, Displayable d)
{
if (c == back)
GraphicsDemo.getInstance().display!);
}
}
}

Процедура довольно прямолинейна. Вы должны сначала создать объект изображения, что вы сделали, когда переслали изображение в компонент высокоуровневого пользовательского интерфейса MIDP. Программа вызывает Image.createlmage(String name) для создания объекта Image. Этот метод определяет местоположение файла изображения, чье имя пути указано относительно директории res/ проекта.

Затем вы пересылаете изображение в объект Graphics, указывая точку привязки и местоположение (х, у) точки привязки. После этого программа просто вызывает метод Graphics.drawlmage() для отображения изображения. Реализация MIDP пересылает объект Graphics в метод приложения paint (Graphics g). Он представляет физический графический контекст устройства. То есть выполнение Graphics.drawlmage() в контексте Graphics, пересланного в ваш метод Canvas, paint (Graphics g), выражается в результате в визуализации на дисплее устройства.

Класс Image имеет четыре версии перегрузки метода createlmage(). В таблице 6.7 показаны все четыре версии. Вы уже видели третью версию, эта версия единственная, которая производит изменяемый объект изображения. Это вам необходимо для записи во внеэкранном контексте Graphics объекта Image.



Canvas все еще может выполнять



Клавишные события

Класс Canvas 1 подменяет метод keyReleased() в Canvas. Поскольку объект регистрируется как блок прослушивания событий, он получает клавишные события в ответ на действия пользователя, связанные с клавиатурой.

Нажатие на любую клавишу клавишной панели приводит к формированию двух клавишных событий: событие нажатия клавиши и событие отпускания клавиши. Эта программа выводит информацию о событиях отпускания клавиши. Информация о клавишном событии включает название клавиши, код клавиши и, возможно, связанное с ним обозначение игрового действия.

Название клавиши является String, которая представляет собой удобное для чтения представление клавиши, обычно сходное (если не такое же) с текстом, физически напечатанным на клавише устройства. Код клавиши является целым числом, чье значение однозначно представляет каждую клавишу. Для стандартных клавиш ITU-T, а именно от 0 до 9, * и #, код клавиши является значением уникода символа.

Программы должны использовать предписанные константы класса Canvas вместо значений уникода нажатой клавиши при проверке того, какая клавиша была нажата. Такой подход делает вашу программу более транспортабельной. Класс Canvas определяет константы для каждого из кодов клавиш, показанные в таблице 6.2.



Чтобы нарисовать текст укажите



Листинг 6.6. Чтобы создать текст, укажите точку привязки и нагрузку точки привязки. Вы также можете указать шрифт текста, который будет отображен

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Command;
import javax.rnicroedition.lcdui.CornmandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;
/**
Отображает некоторый текст, «нарисованный» в Canvas.
Демонстрирует использование процедур рисования текста в Graphics.
Усмотри javax.microedition.lcdui.Graphics
*/
public class TextDemo extends Canvas
implements CommandListener
}
public void paint(Graphics g)
}
paintClipRect(g) ;
int width = getWidth (); int height = "getHeight () ;
g.setFont(Font.getDefault Font());
g.drawStriny("Default", 5, 30, Graphics.LEFT I Graphics.BOTTOM);
g. setFont (Font.get Font (Font.FACE_SYSTEM, Font.STYLE_PLAIN,
Font.SIZE_LARGE)) ; g.drawstring("Large", 5, 53, Graphics.LEFT | Graphics.BOTTOM);
g.set Font(Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_ITALIC,
Font.SIZE_MEDIUM));
g.drawString("Medium", 5, 71, Graphics.LEFT I Graphics.BOTTOM);
g.set Font(Font.get Font(Font.FACE_PROPORTIONAL, Font.STYLE_UNDERLINED,
Font.SIZE_SMALL));
g.drawString("Small", 5, 90, Graphics.LEFT I Graphics.BOTTOM);
g.setFont(Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_BOLD,
Font.SIZE_MEDIUM));
g.drawString ("V", width - 10, 20, Graphics.RIGHT I Graphics.BOTTOM)
g.drawStringC'E", width - 10, 32, Graphics.RIGHT I Graphics.BOTTOM)
g.drawString("R", width - 10, 44, Graphics.RIGHT I Graphics.BOTTOM)
g.drawStringC'T", width - 10, 56, Graphics.RIGHT I Graphics.BOTTOM)
g.drawString("I", width - 10, 68, Graphics.RIGHT I Graphics.BOTTOM)
g.drawString ("C", width - 10, 80, Graphics.RIGHT | Graphics.BOTTOM)
g.drawStringC'A", width - 10, 92, Graphics.RIGHT I Graphics.BOTTOM) g.drawString ("L", width - 10, 104, Graphics.RIGHT I Graphics.BOTTOM);
g.drawChar('B', width - 25, 20, Graphics.RIGHT | Graphics.BOTTOM);
g.drawChar('0', width - 25, 32, Graphics.RIGHT I Graphics.BOTTOM) ;:
g.drawChar('L', width - 25, 44, Graphics.RIGHT I Graphics.BOTTOM) ;:
g.drawChar ( 'D', width - 25, 56, Graphics.RIGHT I Graphics.BOTTOM);

}
. . .
}

Эта демонстрационная программа выбирает, где разместить текстовые строки «Default», «Large», «Medium» и «Small», располагая основные линии ограничивающих прямоугольников. Текст также выравнивается по левому краю. Обратите внимание, что логический OR горизонтальной и вертикальной политик привязки (LEFT | BOTTOM) указывают позицию привязки.

Две строки «BOLD» и «VERTICAL» нарисованы вертикально простым размещением отдельных символов с помощью метода drawChar(). Они определяются сдвигом от правого края дисплея. С помощью политики привязки RIGHT код вычисляет положение правого края ограничивающего прямоугольника, вычитая некоторое количество пикселей из координаты крайнего справа пикселя дисплея.

API Graphics также определяет другую константу, VCENTER, которая действительна только для указания вертикальной политики привязки при размещении изображений. Она недействительна для текста. VCENTER обуславливает то, что вертикальный центр изображения должен быть размещен в координатной точке (х, у). Вы узнаете о работе с изображениями далее в этой главе.

Шрифты. Вы можете выбрать шрифт для любого текста, который вы изобразили в Canvas, как демонстрируется в листинге 6.6. Вы выбираете шрифт, указывая три атрибута шрифта: гарнитура, стиль и размер. Класс javax.microedition.lcdui.Font определяет для каждой из трех категорий константы, показанные в таблице 6.6.



Дисплей после перемещения начала



Листинг 6.8. После перемещения координаты, указанные процедурам рисования Graphics, не изменяются, поскольку они всегда связаны с началом координат контекста Graphics, а не дисплея

import javax.microedition.Icdui.Canvas;
import javax.microedition.Icdui.Command;
import javax.microedition.Icdui.CommandListener;
import javax.microedition.Icdui.Display;
import javax.microedition.Icdui.Displayable;
import javax.microedition.Icdui.Graphics;
/**
Демонстрирует преобразование контекста Graphics в Canvas.
(Усмотри javax.microedition.lcdui. Graphics
*/ public class TranslationDemo extends Canvas
implements CommandListener
{
private final int WHITE = OxFF « 16 I OxFF « 8 | OxFF;
private GraphicsDemo gDemo = GraphicsDemo.getlnstance ();
private Display display = Display.getDisplay(gDemo);
private static Command back = new Command("Back", Command.BACK, 1);
private static Command go = new Command("Go", Command.SCREEN, 1);
private static final int ORIGINAL_STATE = 1;
private static final int TRANSLATED_STATE = -1;
// Координата х начального рисунка, private int x = 20;
// Координата у начального рисунка, private int у = 20;
// Величина переноса в направлении х. private int deltaX = 30;
// Величина переноса в направлении у. private int deltaY = 30;
// Задает переменную, которая сообщает программе, рисовать ли на экране
// в первоначальной позиции или в преобразованной позиции,
private int state = ORIGINAL_STATE;
/**
Конструктор.
*/
public TranslationDemo()
{
super () ;
addCommand(back);
addCommand(go);
setCommandListener (this);
display.setCurrent(this);
}

protected void paintClipRect(Graphics g)
{
int clipX = g.getClipX() ;
int clipY = g.getClipY() ;
int clipH = g.getClipHeight(); int clipW = g.getClipWidth() ;
int color = g . getColor();
g.setColor(WHITE);
g.fillRect(clipX, clipY, clipW, clipH);
g.setColor (color) ;
}
public void paint(Graphics g)
{
int w = 50;
int h = 50;
paintClipRect(g); g.fillRect(x, y, w, h);
}
// Переключает режим рисования. Этот метод вызывается во время
// обработки команды «Go», которая переключает перемещение.
private void toggleState()
{
state = -state;
}
// Переключает преобразование. Перерисовывает заново Canvas.
private void toggleTranslation()
}
if (state == ORIGINAL_STATE)
x = x + deltaX; у = у т deltaY;
}
else
{
x = x - deltaX;

у = у - deltaY; 1 toggleState();
// Запрашивает у реализации вызов метода paint() для восстановления
// Canvas. Это выражается в генерировании внутреннего события
// рисования, которое обрабатывается реализацией, repaint () ;
*/
public void commandAction(Command c, Displayable d)
{
if (с == back)
GraphicsDemo.getInstanced.display!);
}
else if (c == go)
{
toggleTranslation() ;
}
}
}

Как вы узнали в предыдущем разделе, вы можете рисовать за пределами границ объекта Graphics, однако такое рисование не будет формировать изображение на экране. Но после выполнения внеэкранного рисования вы можете преобразовать Graphics для того, чтобы отобразить предыдущий внеэкранный рисунок.



Двойная буферизация



Двойная буферизация

Термин двойная буферизация относится к технике буферизации графического контекста перед его отображением. Эта идиома требует, чтобы вы использовали два графических контекста - или буфера - отсюда ее название.

Вы сначала рисуете графические данные во вторичном графическом контексте, а затем копируете его содержимое в графический контекст, представленный дисплеем устройства. Этот вторичный графический контекст называется внеэкранным буфером. Внеэкранный буфер не отображает на дисплее.

Смысл этой технологии заключается в ограниченности производительности. Операции по рисованию могут в результате привести к быстрым обновлениям дисплея, заставляя пользователя воспринимать мерцание. Чтобы избежать мерцания, вы должны сначала выполнить ваше рисование во внеэкранной графической среде, а затем скопировать весь внеэкранный графический контекст в оригинальную графику устройства. Операция копирования обычно быстрее, чем множество операций по рисованию, требуемых даже относительно сложными Canvas, так что это будет сделано практически без заметного мерцания.

В листинге 6.9 демонстрируется использование двойной буферизации. Код выполняет несколько простых функций рисования во внеэкранном буфере, затем копирует содержимое этого буфера в саму графическую среду, которая представляет дисплей устройства. Хотя процедуры рисования в этом примере относительно просты, реальное приложение может выполнять намного более сложное рисование, действительно подтверждая необходимость двойной буферизации.



Графическая модель



Графическая модель

Класс Graphics определяет возможности низкоуровневого графического рисования. Если вы уже разрабатывали программы на AWT или Swing, этот класс покажется вам очень знакомым. В действительности его свойства и программный интерфейс практически идентичны, хотя и являются подмножеством свойств класса Graphics J2SE.

Класс Graphics определяет модель, которая позволяет приложениям рисовать- или раскрашивать на языке Java - базовые двухмерные фигуры на Canvas. Описанным методом public void paint(Graphics g) осуществляется рисование в вашем подклассе Canvas, подменяя объявленный метод protected abstract в Canvas. Класс Canvas имеет пустое paint(Graphics g) определение, что объясняет, почему он не создает какого-либо визуального представления.

Каждый конкретный подкласс Canvas имеет доступ к объекту Graphics. Этот объект Graphics является копией графического контекста устройства и извлекает зависящий от реализации графический контекст устройства, являющийся частью программного обеспечения операционной системы устройства.

Объект Graphics, с которым вы работаете, создан реализацией Canvas при инициализации объекта Canvas. Это одна из главных причин, почему вы должны убедиться, что ваш конструктор подкласса Canvas вызывает super()! Реализация пересылает графический объект в ваш Canvas, когда он вызывает метод вашего класса paint (Graphics g).



Графическое рисование



Графическое рисование

Вы, несомненно, обратили внимание, что canvas, показанный на рисунке 6.2, был чистым за исключением экранной клавиши Exit (Выход). Причина этого кроется в том, что класс Canvasl не описывает свое визуальное представление. Все конкретные подклассы Canvas должны определять свой внешний вид для того, чтобы визуализировать какие-либо визуальные атрибуты. Для этого они должны привлекать помощь класса javax.microedition.lcdui.Graphics. Основная цель класса Graphics заключается в поддержке рисования на Canvas.



Игровые действия



Игровые действия

В дополнение к константам, которые вы уже видели, класс Canvas определяет константы GAME_A, GAME_B, GAME_C, GAME_D и FIRE, которые представляют игровые действия, отражая влияние игровой индустрии в J2ME. Значения этих констант нестандартны и изменяются в зависимости от реализации.

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

public int getKeyCode (int gameAction)
public int getGameAction(int keyCode)

В листинге 6.2 эти методы используются для вывода диагностической информации о каждом полученном событии отпускания клавиши. Если вы запустите эту программу и исследуете результат, вы увидите, что не каждая клавиша имеет связанное с ней игровое действие. В данном случае метод getGameAction () возвращает значение 0. Более того, не все устройства реализуют GAME_A, GAME_B, GAME_C и GAME_D. Примером устройства, которое не использует эти игровые действия, является Motorola Accompli 008.



Как и другие геометрические фигуры



Листинг 6.5. Дуги могут быть нарисованы в виде очертания или заполненными, как и прямоугольники

import javax.microedition.lcdui.*;
/**
Демонстрирует рисование дуг с помощью класса Graphics.
Усмотри javax.microedition.lcdui.Graphics
*/
public class ArcDemo extends Canvas
implements ComraandListener
{
public void paint(Graphics g)
{
paintClipRect(g);
}
int width = getWidth();
int height = getHeight ();
g.drawArc(5, 5, 80, 40, 90, 300);
g.fillArc(5, 60, 80, 40, 0, 250);
}
. . . .
}

Обратите внимание, что вторая дуга заполнена и что она была создана с помощью метода f illArc () вместо метода drawArc ().

Текст. Класс Graphics также поддерживает «рисование» текстовых символов в Canvas. Три метода, перечисленные в таблице 6.4, являются методами класса Canvas, поддерживающими размещение текста в Canvas.



Kaк рисуются компоненты



Kaк рисуются компоненты

Вы, возможно, заметили, что метод toggleTranslation() в листинге 6.8 вызывает Canvas.repaint (). Этот вызов требует, чтобы реализация перерисовывала дисплей.

Вызов Canvas.repaint() выражается в событии внутренней реализации, представляя запрос обновления. Реализация обрабатывает событие внутренне. Она назначает вызов метода paint () Canvas, который выполняется реализацией, а не вашей программой.

Canvas должен быть закрашен для визуализации всех элементов, изображенных в его контексте, или для перерисовки поврежденных пикселей. Однако вы никогда не должны вызывать paint () прямо. Если вы желаете перерисовать ваш Canvas, вы должны создать вызов repaint (). Или вы можете вызвать следующую версию перегрузки, которая также определяется в классе Canvas:

void repaint(int x, int у, int width, int height)

Эта версия требует перерисовки прямоугольной области, определяемой параметрами, указанными в вызове.

Обратите внимание, что вы все равно должны перерисовать поврежденные пиксели, прежде чем создавать- вызов на перерисовку Canvas. Это требование отличается от требований приложений, написанных в AWT или Swing. В AWT и Swing вызов repaint() выполняет две операции: он сначала вызывает update(), а затем - paint (Graphics g). Вызов update () приводит к тому, что реализация стирает Panel, Canvas или JComponent. Такого вызова в МГОР нет, так что вы должны перерисовать поврежденные пиксели сами. Обратите внимание, что в листинге 6.6 метод paint (Graphics g) все равно вызывает метод paintClipRect(Graphics g).



Класс Graphics



Класс Graphics

Класс Graphics поддерживает следующие абстракции:

рисование и заливка двухмерных геометрических фигур; выбор цветов для графической ручки; выбор шрифтов для рисования текста; отсечение областей для рисования (clipping); перенос координатной системы Graphics.

Устройства различаются в своей поддержке атрибутов, таких, как цвет. Поэтому класс Display предоставляет методы:

public int isColorO
public int numColors()

так что вы можете получить информацию о поддержке данным устройством цвета и количестве предоставляемых цветов или поддержке какого-либо числа уровней шкалы серого цвета для устройств, не поддерживающих цвет.

Первостепенной абстракцией, определяемой классом Graphics, является представление о Canvas, как о двухмерной сетке точек или пикселей. На рисунке 6.3 представлено схематичное изображение этой области для рисования. Графический контекст определяет эту координатную плоскость (х, у), в которой координаты лежат между пикселями, практически так же, как и курсор вашего любимого текстового редактора всегда лежит между двумя символами.



Класс Graphics представляет дисплей



Базовое геометрическое рисование

Класс Graphics предоставляет операции по рисованию и заливке следующих типов геометрических фигур:

линии; прямоугольники; дуги; текстовые символы.

Для всех операций по рисованию геометрических фигур класс Graphics использует графическую ручку, которая рисует линии шириной в один пиксель. Графическая ручка рисует слева направо и сверху вниз со своего координатного местоположения, как показано на рисунке 6.3. Взглянув на несколько примеров, вы лучше поймете принципы ее действия.

Линии. На рисунке 6.4 показаны линии, нарисованные в Canvas.



Левая половина предаавляет состояние



Отображение изображения с помощью Canvas

Вы уже узнали в главе 5, что некоторые компоненты высокоуровневого пользовательского интерфейса MIDP умеют отображать изображения, например, как часть элемента в ChoiceGroup. Объекты Canvas также могут отображать изображения. Кроме рисования базовых геометрических фигур, объект Canvas может «рисовать» изображения с помощью того же контекста Graphics, который он использует для низкоуровневых функций рисования. MIDP поддерживает только формат изображений PNG.

На рисунке 6.12 показано изображение, отображаемое в Canvas. В листинге 6.10 показана исходная программа, создающая изображение, показанное на рисунке 6.12. Структура программы сходна с другими демонстрационными программами Canvas, приведенными в этой главе.



Демонстрационной программе



Листинг 6.1. Демонстрационной программе CanvasDemol требуется MID-лет, как и любое другое приложение МIDР

import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.Display;
/"
Определяет MID-лет, отображающий пустой Canvas на дисплее устройства.
Отображаемый Canvas является Экземпляром класса Canvasl.
@смотри Canvasl
*/
public class CanvasDemol extends MIDlet
{
// Поддерживает ссылку на экземпляр данного класса.
private static CanvasDemol midlet;
// Поддерживает ссылку на Canvas, который пользователь
// видит на дисплее.
private static Canvasl instance;
private Display display; private Canvasl canvas;
/**
Конструктор No-arg. Вызывает конструктор no-arg класса MID-лета.
*/
public CanvasDemol()
super();

display = Display.getDisplay(this);
instance = canvas; midlet = this;
{
/**
Возвращает ссылку на MID-лет, связанный с данным отображаемым объектом.
@возвращает MID-лет, который отображает этот объект.
**/
public static CanvasDemol getMIDlet()
{
return midlet;
{
public void startApp()
{
canvas = new Canvasl ();

display.setCurrent(canvas);

(
public void pauseApp()
{
}
public void destroyApp(boolean destroy)
{
instance = null;
canvas = null;
void quit ()
{
destroyApp(true);

notifyDestroyed();

}
}



Чтобы использовать



Листинг 6.2. Чтобы использовать Canvas, вы должны создать подкласс Canvas

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Graphics;
/**
Определяет подкласс Canvas, который отображается с помощью MID-лета
CanvasDemol. Этот Canvas имеет единственную команду «Exit», так что
пользователь может завершить работу демонстрационной программы.
@Осмотри CanvasDemol
*/
public class Canvasl extends дополняет Canvas
implements CommandListener
{
private Command exit = new Command("Exit", Command.EXIT, 1);

/**
Конструктор No-arg.
*/
public Canvasl ()
{
// Обратите внимание на вызов super (), который является конструктором
// r.o-arg Canvas! Это очень важно. Без вызова superf) ваши экземпляры
// Canvas не смогут действовать как настоящие Canvas. Они не будут
// отображать правильно, они не будут рисовать должным образом и они
// не смогут обрабатывать события, super () ;
addCommand(exit);
setCommandListener (this);

printCanvasInfo() ;
}
/**
Рисует внешний вид Canvas, видимый пользователю.
В данном случае он не рисует ничего.
Поэтому этот Canvas не имеет визуального представления.
*/
public void paint(Graphics g)
{
}
public void commandAction(Command c, Displayable d)
{
if (c == exit)
CanvasDemol.getMIDlet().quit();

}
/**
Определяет, что обработка должна быть сделана в ответ на
событие отпускания клавиши, произошедшее в данном Canvas.
Этот метод подменяет тот же самый метод в Canvas.
*/
public void keyReleased(int keyCode)
{
printKeyEventlnfo(keyCode);

}
/**
Определяет некоторую обработку событий, связанных с клавишами.
Этот метод просто печатает в стандартном результате некоторую
диагностическую информацию о событиях, связанных с клавишами, на Canvas.
*/
protected void printKeyEventlnfо(int keyCode)
{
System.out.println("Key code = " + keyCode);

System.out.println("Key name = " + getKeyName(keyCode));

System.out.println("Game action = " + getGameAction(keyCode));

}
/*'*
Печатает диагностическую информацию об атрибутах
и возможностях объекта Canvas.
"/
protected void printCanvasInfо ()
{

System.out.println("Device height = " + getHeight ());

System.out.println("Device width = " + getWidth());

System.out.println("Pointer events = " + hasPointerEvents());

System, out. printl'n ("Pointer motion events = " +
hasPointerMotionEvents());

System.cue.println("Repeat events = " + hasRepeatEvents());

}
}

Чтобы убедиться, что в Canvas все еще осуществима обработка высокоуровневых команд, запустите MID-лет, показанный в листинге 6.2. Вы увидите, что дисплей, показанный на рисунке 6.2, имеет экранную клавишу Exit (Выход), которая при активации завершает работу МID-лета.



Вы должны стереть



Листинг 6.7. Вы должны стереть все недействительные пиксели, прежде чем рисовать свой компонент. Используйте отсекаемый прямоугольник графического объекта вашего компонента, чтобы определить прямоугольную область, которая содержит все поврежденные пиксели

protected void paintClipRect(Graphics g)
int clipX = g.getClipX ();
int clipY = g.getClipY() ;
int clipH = g.getClipHeight();
int clipW = g.getClipWidth();

int color = g.getColor();

g.setColor(WHITE);

g.fillRect(clipX, clipY, clipW, clipH);

g.setColor(color);

}

Проблема этого метода заключается в том, что он использует отсекаемый прямоугольник объекта Graphics. Отсекаемый прямоугольник является прямоугольной областью, которая содержит все недействительные пиксели экрана. Отсекаемый прямоугольник определяется его отклонением (х, у) от начала координат объекта Graphics, а также его шириной и высотой.

Вы можете получить отсекаемый прямоугольник, вызвав следующие методы Graphics:

int getClipHeight ()
int getClipWidth ()
int getClipX()
int getClipY()

При вызове метода paint (Graphics g) отсекаемый прямоугольник всегда представляет область, которая содержит все поврежденные пиксели дисплея. В случаях, подобных примерам, приведенным в этой главе, где вы заменяете отображение экрана новым, отсекаемый прямоугольник представляет всю область дисплея устройства.

Самый легкий способ «стереть» недействительные пиксели - это перерисовать каждый пиксель в отсекаемом прямоугольнике с помощью цвета фона экрана, таким образом гарантировав, что вы стерли все поврежденные пиксели. Затем вы выполняете операции по рисованию, которые определяются вашим Canvas, с помощью другого цвета.

Обратите внимание, что в листинге 6.7 метод получает и сохраняет текущий цвет дисплея, который представляет цвет ручки, используемый для всех операций рисования. По умолчанию цвет обычно черный в большинстве реализаций. Код затем устанавливает текущий цвет на белый (который обычно является цветом фона) и заполняет отсекаемый прямоугольник белыми пикселями, эффективно «стирая» все поврежденные пиксели. В конце код восстанавливает первоначальный цвет объекта Graphics. Последующие операции по рисованию визуализируют пиксели некоторого цвета, отличающегося от белого, на белом фоне.

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

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

void clipRect(int x, int у, int width, int height)

устанавливает отсекаемый прямоугольник как пересечение текущего отсекаемого прямоугольника и прямоугольника, определенного аргументами, - области, используемой вашим Canvas. Ваше приложение может затем вычислить, какие из пикселей относятся к этому новому отсекаемому прямоугольнику, и перерисовать их.

Вызов clipRect () всегда создает отсекаемый прямоугольник меньшего размера. Вы можете также установить любой размер отсекаемого прямоугольника с помощью следующего вызова:

setClipfint x, int у, int width, int height)

Ваш Canvas нуждается в перерисовке только тех пикселей, которые подпадают под область пересечения, поскольку отсекаемый прямоугольник гарантирует включение всех поврежденных пикселей. Конечно, вычисление этой области может быть более сложным, чем простая перерисовка всего Canvas. Но закрашивание только отсекаемого прямоугольника полезно для приложений, которые используют сложную или отнимающую много времени обработку при вычислении того, какие пиксели нужно закрашивать.

Другим стандартным применением отсечения является разработка игр. Стандартное приложение является каркасом, в котором вы хотите переносить фантом, являющийся небольшим изображением или значком. Используя область отсечения, как показано в листинге 6.7, вы рисуете фон области, где фантом расположен в настоящее время, а затем вы рисуете фантом в его новой позиции.

На самом деле на реальном устройстве, которое не поддерживает двойной буферизации, реализация, показанная в листинге 6.7, может производить довольно заметное и разрушительное мерцание экрана при его обновлении. Вы, вероятно, не заметите никакой вспышки лри использовании эмулятора из-за скорости вашего компьютера. В разделе «Двойная буферизация» далее в этой главе вам будет показано, как справиться с этой проблемой.

Рисование - это процесс изменения состояния объекта Graphics. Визуализация - это процесс отображения закрашенных пикселей на экране. Вы никогда не сможете формировать изображение за пределами отсекаемого прямоугольника. Координаты, переданные процедурам рисования, всегда являются интерпретированными по отношению к первоначальному отсекаемому прямоугольнику. Операции по рисованию, которые лежат вне границ отсекаемого прямоугольника, не влияют на визуализацию, они не появляются на экране. Отрицательные значения координат х и у относятся к пикселям, лежащим за пределами отсекаемого прямоугольника.

Хотя вы никогда не сможете формировать изображение за пределами отсекаемого прямоугольника, вы можете рисовать где угодно, даже за пределами отсекаемого прямоугольника. Вы можете даже рисовать за границами обьекта Graphics. Вы можете реализовать панорамирование или перемещение изображения, изменяя координаты х и у начала координат при рисовании.



Двойная буферизация



Листинг 6.9. Двойная буферизация использует два графических контекста. Единственный способ получить второй графический контекст в МЮР - через класс Image

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import Java.io.lOException;
Демонстрирует двойную буферизацию графического контекста
для отображения в Canvas.
public class DoubleBufferDerao extends Canvas
implements CommandListener
{

// Константа, которая представляет белый цвет.
private static final int WHITE = OxFF « 16 I OxFF « 8 | OxFF;
private static Command back = new Command("Back", Command.BACK, 1);

GraphicsDemo gDemo = GraphicsDemo.getlnstance();

private Display display = Display.getDisplay(gDemo);

// Объект изображения, используемый для получения
// внеэкранного объекта Graphics, private Iraage offscreen;
// Переменная, используемая для определения того, осуществляет
// ли реализация автоматическую двойную буферизацию.
// Сохраняет значение true, если реализация автоматически
// осуществляет двойную буферизацию, иначе становится
false. private boolean autoDoubleBuffered = true;
/**
Конструктор No-arg.
* /
public DoubleBufferDemo()
super();

addCoramand(back);
setCommandListener(this);
display.setCurrent(this);

if ( ! isDoubleBufferedO )
{
// Если реализация не использует двойную буферизацию
// автоматически, извлеките Image для того, чтобы мы могли
// получить из него внеэкранный Graphics. Этот Image изменяемый!
// Его размерами являются высота и ширина данного Canvas.
offscreen = Image.createlmage(getWidth (),
getHeight () ) ;
autoDoubleBuffered = false;

}
)
protected void paintdipRect (Graphics g)
int clipX = g.getClipX() ;
ir.t clipY = g.getClipY() ;
int clipH = g.getClipHeight();

int clipW = g.getClipWidth();

int color = g.getColor ();

g.setColor (WHITE);

g.fillRect(clipX, clipY, clipW, clipH);

g,setColor(color);

}
public void paint(Graphics g)
}
Graphics originalG = null;
int width = getWidthf);
int height = getHeight ();

if (!autoDoubleBuffered)
}
// Сохраняем первоначальный графический контекст и получаем
// новый внеэкранный Graphics из утилиты Image.
originalG = g;
g = offscreen.getGraphics ();

// Очищаем отсекаемый прямоугольник с помощью нового объекта
// Graphics. Таким образом, мы используем двойную буферизацию
// для очистки Canvas, следовательно, избегая мерцания.
// Очистка Canvas является рисованием, как и все другие
// операции по рисованию. paintdipRect (g) ;
}
else
{
// Очищаем Canvas с первоначальной графикой, поскольку
// реализация не выполняет двойной буферизации автоматически.
paintdipRect (g) ;
}
for (int x = 0, у = 0; (x < width /2);
x = x + 2)
{
g.drawRect(x, y, (width - x) - x, (height - y) - y) ;
у +1; у +1 ;
}
// При рисовании изображения содержимое внеэкранного
// контекста Graphics изображения на самом деле копируется
// в контекст Graphics устройства. if (!autoDoubleBuffered)
{
originalG.drawlmage(offscreen, 0, 0,
Graphics.TOP | Graphics.LEFT);

{{
public void commandAction(Command c, Displayable d)
}
if (c == back)
GraphicsDemo.getInstance().display!);

}
}
}

Конструктор содержит первый код, связанный с двойной буферизацией. Нижеприведенный оператор, взятый из безаргументного конструктора DoubleBufferDemo, определяет, поддерживает ли реализация автоматическую двойную буферизацию.

if (!isDoubleEuffered())
{
offscreen = Image.createlmage(getWidth(), getHeight());

autoDoubleBuffered = false;
}

Если реализация не поддерживает двойную буферизацию, приложению не нужно выполнять ее. Метод Canvas.IsDoubleBuffered() сообщает вам, не выполняет ли реализация двойную буферизацию неявно. Обратите внимание на конструкцию объекта Image. Этот вызов Image, create Image () создает изменяемый объект Image. Приложение нуждается в изменяемом Image, потому что оно выполняет рисование в контексте Graphics объекта Image, являющемся нужным вам внеэкранным буфером. Это единственный способ получения дополнительного Graphics в MIDP.

Метод paint () содержит остальной код двойной буферизации. Если автоматическая двойная буферизация не осуществляется, приложение должно выполнить ее. Это требует второго графического контекста. Следующий фрагмент метода paint () демонстрирует эту идиому

public void paint(Graphics g)
if (!autoDoubleBuffered)
originalG = g;
g = offscreen.getGraphics();

else
{
paintClipRect(g);

}
. . .
}

Временная переменная хранит ссылку на первоначальный объект Graphics, который представляет графический контекст устройства. Новый графический контекст получается через объект Image, созданный ранее. Этот Graphics связан с Image. Последовательность событий представлена схематично на рисунке 6.11.

Теперь метод paint (Graphics g) выполняет свои операции по рисованию во внеэкранном контексте Graphics. При выполнении он копирует содержимое внеэкранного Graphics в первоначальный контекст Graphics, что в результате формирует изображение на дисплее. Операция копирования совершается с помощью вызова метода Graphics.drawlmage(). Этот метод говорит по сути: «Копируй содержимое графического контекста этого аргумента изображения в меня».

Механизм двойной буферизации в MIDP отличается от двойной буферизации Swing. В Swing вы можете выполнять двойную буферизацию операций по рисованию в любом Component, не только в объектах Canvas. Приложения Swing вызывают Java. awt. Component .getGraphics () для получения внеэкранного графического контекста. Приложение может рисовать в этом контексте. Оно затем связывает этот внеэкранный графический контекст с самим устройством.



Объекты Canvas отображаются на



Oбработка команд и событий

В компоненте Canvas вы можете добавлять и удалять высокоуровневые команды и устанавливать один блок прослушивания команд на Canvas, как и в других отображаемых компонентах. Canvas также может внедрять CommandListener и регистрироваться как свой собственный блок прослушивания.

Однако, в дополнение к обработке высокоуровневых команд, класс Canvas также обрабатывает низкоуровневые команды. Компоненты Canvas сами являются источниками низкоуровневых событий клавиш и указателя, которые генерируются действиями пользователя по вводу с клавиатуры и перемещением указателя на устройстве. Они также являются своими собственными блоками прослушивания низкоуровневых событий. Класс Canvas определяет интерфейс для обработки низкоуровневых событий как часть своего собственного API, другого интерфейса блока прослушивания не реализуется.

Реализация MIDP передает информацию о событии низкого уровня объекту Canvas, вызывая соответствующий метод в объекте Canvas. В таблице 6.1 перечислены возможные методы.



Отсечение областей для рисования



Отсечение областей для рисования

Когда ваше приложение вызывает метод Display.setCurrent(), он запрашивает реализацию об отображении вашего Displayable. Для объектов Canvas реализация делает ваш компонент текущим отображаемым и вызывает метод вашего класса paint (Graphics g). Реализация генерирует внутреннее событие рисования, которое пересылается в текущий отображаемый элемент. В этом кроется причина того, что метод paint() указан в таблице 6.1 как часть обработки событий API, определяемой Canvas.

Во время отображения некоторая группа пикселей дисплея может быть недействительной или поврежденной. Недействительный или поврежденный пиксель - это тот, который видим в результате предыдущей операции рисования, но не должен быть визуализирован в качестве части текущей операции рисования. Дисплей может быть поврежден другим MID-летом или даже «внешним» приложением - например, приложением по передаче сообщений, которое обновляет дисплей для отображения получения сообщения SMS вашим мобильным телефоном.

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

Вы, несомненно, уже обратили внимание на наличие метода paintClipRect (Graphics g) в листинге 6.3. В листинге 6.7 повторяется этот метод. Это первый код, вызываемый методом paint (Graphics g) каждого приложения. Его цель состоит в удалении всех пикселей, которые были нарисованы предыдущей операцией рисования.



Преобразование



Преобразование

Как вы уже знаете, точка (х, у) указывает функции рисования место, расположенное относительно точки (0, 0). Точка (0, 0) является началом координат Graphics. Когда вы впервые получите ссылку на Graphics вашего Canvas, его начало координат, точка (О, О), всегда представляет верхний левый угол дисплея устройства.

Преобразование Graphics означает перенос его начала координат. После перемещения начало координат Graphics представляет некоторую точку, отличную от левого верхнего пикселя. Вы переводите начало координат Graphics с помощью метода

void translate(int x, int у)

Аргументы являются координатами точки, которая станет новым началом координат объекта Graphics. Точка (0, 0) теперь является этим новым началом координат. Все операции по рисованию теперь относятся к этому новому началу координат. На рисунке 6.9 показан экран, созданный кодом, описанным в листинге 6.8. Он просто рисует заполненный квадрат в Canvas.

При нажатии на кнопку Go начало координат Graphics переносится, а затем заполненный квадрат перерисовывается. На рисунке 6.10 показан обновленный дисплей после того, как кнопка Go была нажата в первый раз. Обратите внимание, что координаты, переданные вызовам методов рисования в методе paint (Graphics g) не изменились. Причина этого кроется в том, что эти координаты всегда связаны с началом координат Graphics, а не с верхним левым углом области дисплея устройства. Операции по рисованию всегда указываются относительно начала координат Graphics, безотносительно к точке места назначения, которое она представляет.

Нажатием на кнопку Go вы на самом деле переключаете перемещение. Нажатие на кнопку во второй раз перемещает начало координат назад к верхнему левому углу дисплея.



Прямоугольники как и все геометрические



Листинг 6.4. Демонстрационная программа RectangleDemo демонстрирует графические вызовы рисования прямоугольников. Обратите внимание, что это вызов на заполнение прямоугольников

import javax.microedition.lcdui.Canvas;
import javax.microedition.Icdui.Command;
import javax.microedition.Icdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.Icdui.Displayable;
import javax.microedition.Icdui.Graphics;
import javax.microedition.Icdui.Command;
/**
Рисует прямоугольники на Canvas с помощью графических методов
в классе javax.microedition.Icdui.Graphics.
@смотри javax.microedition.Icdui.Graphics
*/
public class RectangleDemo extends Canvas
implements CommandListener
{
// Константа, представляющая белый цвет.
private static final int WHITE = OxFF « 16 | OxFF « 8 I OxFF;
private Command back = new Command("Back", Command.BACK, 1);
private Display display =
Display.getDisplay(GraphicsDemo.get!nstance()) ;
/**
Конструктор No-arg. Вызывает конструктор no-arg Canvas.
*/
public RectangleDemo()
}
super () ;
addCommand(back); setCommandListener(this);
display.setCurrent (this) ;
}
/**
Рисует белый отсекаемый прямоугольник, эффективно стирающий
все, что было отображено на Canvas перед этим.
*/
protected void paintClipRect(Graphics g)
{
int clipX = g.getClipX () ;
int clipY = g.getClipY();
int clipH = g.getClipHeight();
int clipW = g.getClipWidth ();
int color = g.getColor();
g.setColor (WHITE);
g.fillRect(clipX, clipY, clipW, clipH);
g.setColor (color);
}
/**
Отображает внешний вид этого подкласса Canvas.
*/
public void paint(Graphics g)
{
paintClipRect(g);
int width = getWidthO; int height = getHeightf);
int xO = 5;
int yO = 5;
int barW = 10;
int initHeight = height - 10;
int deltaH = 10;
g.drawRect(xO, yO, barW, initHeight);
g.fillRect(xO + barW, yO + deltaH, barW, initHeight - deltaH + 1);
g.drawRect(xO + barW " 2, yO + deltaH * 2,
barW, initHeight - deltaH * 2);
g.setColor (255, 00, 00); g.fillRect(xO + bar» * 3, yO + deltaH * 3,
barW, initHeight - deltaH * 3 + 1) ; g. setColor (0," 0, 0);
g.drawRect(xO + barW * 4, yO + deltaH * 4,
barW, initHeight - deltaH * 4);
g.fillRect(xO + barW * 5, yO + deltaH * 5,
barW, initHeight - deltaH * 5 + 1);
g.drawRect(xO + barW * 6, yO + deltaH * 6,
barW, initHeight - deltaH * 6); g.fillRect(xO + barW * 1, yO + deltaH * 1,
barW, initHeight - deltaH * 7 + 1);
}
public void commandAction(Command c, Displayable d)
{
if (c == back)
{
GraphicsDemo.getlnstanceO.display!) ;
}
}
}

Дуги. Класс Graphics также поддерживает рисование дуг. Чтобы нарисовать дугу, вы должны указать шесть параметров. Эти параметры включают четыре размера, которые определяют ограничивающий дугу прямоугольник, ее начальный угол и ее конечный угол. Ограничивающий прямоугольник определяется теми же четырьмя параметрами, которые требуются для прямоугольников.

Процедура рисования отслеживает дугу вдоль ее пути от начального угла к конечному углу в направлении против часовой стрелки. Угол в 0 градусов располагается вдоль положительной оси X координатной плоскости. На рисунке 6.6 показаны две дуги, нарисованные методом paint (Graphics g) в листинге 6.5.



Методы уведомления


Название метода

Описание

protected void keyPressedfint KeyCode)

Клавиша была нажата и отпущена

protected void keyReleased.(int KeyCode)

Клавиша была отпущена

protected void keyRepeated(int KeyCode)

Клавиша была нажата несколько раз

protected void pointerPressed (int x, int y)

Указатель был нажат

protected void pointerDragged (int x, int y)

Указатель был перемещен

protected void pointerReleased(int x, int y)

Указатель был отпущен

protected abstract void paint (Graphics g)

Произошел запрос Canvas на перерисовку

Для выполнения обработки событий низкого уровня ваш конкретный подкласс Canvas должен подменять один или больше методов, перечисленных в таблице 6.1. Не подменяя пустые описания класса Canvas, вы пропускаете события и вышеупомянутую возможность их обработки. Кроме того, ваш подкласс Canvas должен описывать метод paint (), который объявляется абстрактным в Canvas.

В листингах 6.1 и 6.2 представлена простая схема обработки команд и событий в Canvas. Код в листинге 6.1 является кодом MID-лета для демонстрационной программы, большая часть которой выглядит знакомо. Код в листинге 6.2, однако, создает подкласс Canvas - Displayable, который согласно коду, показанному в листинге 6.1, размещается на экране.



Константы класса Canvas


Константа класса Canvas

Описание

public static final int KEY NUMO

Представляет клавишу 0 клавишной панели

public static final int KEY NUM1

Представляет клавишу 1 клавишной панели

public static final int KEY NUM2

Представляет клавишу 2 клавишной панели

public static final int KEY_NUM3

Представляет клавишу 3 клавишной панели

public static final int KEY NUM4

Представляет клавишу 4 клавишной панели

public static final int KEY NUM5

Представляет клавишу 5 клавишной панели

public static final int KEY_NUM6

Представляет клавишу 6 клавишной панели

public static final int KEY NUM7

Представляет клавишу 7 клавишной панели

public static final int KEY_NUM8

Представляет клавишу В клавишной панели

public static final int KEY NUM9

Представляет клавишу В клавишной панели

public static final int KEY POUND

Представляет клавишу * клавишной панели

public static final int KEY STAR

Представляет клавишу # клавишной панели

Для нестандартных (зависящих от устройства) клавишей, таких, как кнопки Up (Вверх), Down (Вниз), Left (Влево), Right (Вправо) и Select (Выбор) на мобильных устройствах, код клавиши является значением, зависящим от реализации, и должен быть отрицательным в соответствии со спецификацией MIDP. Опять же, однако, вы должны использовать предопределенные константы, показанные в таблице 6.3, и не думать о настоящем целом значении.



Константы класса Canvas


Константа класса Canvas

Описание

public static final int UP

Представляет клавишу панели со стрелкой вверх

public static final int DOWN

Представляет клавишу панели со стрелкой вниз

public static final int LEFT public static final int RIGHT

Представляет клавишу панели со стрелкой влево Представляет клавишу панели со стрелкой вправо

public static final int FIRE

Представляет клавишу панели со стрелкой запуска (выбора] на мобильных устройствах



Методы класса Canvas


Название метода отображения текста в Canvas

Описание

public void drawString(String str, int x, int y, int anchor)

Рисует символы, которые формируют строковую переменную с указанной точкой привязки в позиции, определяемой координатами (х, у]

public void drawSubstring(String str, int offset, int len, int x, int y, int anchor)

Рисует символы, которые формируют переменную подстроки, определяемую начальной точкой и сдвигом, с указанной точкой привязки в позиции, определяемой координатами (х, у)

public void drawChar (Char char, int x, int y, int anchor)

Рисует символ с указанной точкой привязки в позиции, определяемой координатами (х, у)

Эти методы вычисляют воображаемый ограничивающий прямоугольник, который описывает границы области, занимаемой текстом, вокруг текста, который необходимо изобразить, как показано на рисунке 6.7. Размеры этого прямоугольника зависят от длины строки и шрифта, используемого для отображения.

Параметры (х, у) в только что показанных методах представляют расположение ограничивающего прямоугольника. Параметр привязки определяет точку привязки ограничивающего прямоугольника. Точка привязки определяет, которая из шести возможных точек по периметру текста ограничивающего прямоугольника должна быть размещена в позицию (х, у).

На рисунке 6.7 показаны шесть точек привязки для регулирования расположения прямоугольника, ограничивающего текстовую строку. Значение точки привязки на самом деле является выбором нагрузки на точку ограничивающего прямоугольника. Два атрибута составляют нагрузку точки привязки: горизонтальная и вертикальная политики нагрузки. В таблице 6.5 описаны представляющие их константы класса Graphics. Они описывают public static final int.



Графические константы


Константа привязки

Описание

static int LEFT

Размещает левый край у координаты х

static int HCENTER

Размещает горизонтальный центр у координаты х

static int RIGHT

Размещает правый край у координаты х

static int TOP

Размещает верх у координаты у

static int BASELINE

Размещает нижнюю строку текста у координаты у

static int BOTTOM

Размещает низ ограничивающего прямоугольника у координаты у

static int VCENTER

Только для изображений, размещает вертикальный центр изображения у координаты у

Класс Graphics описывает эти константы для текущих значений горизонтальной нагрузки, а также определяет значения для текущих значений вертикальной нагрузки.

На рисунке 6.8 показан некоторый текст, отображаемый на Canvas, а в листинге 6.6 показан метод paint (Graphics g) исходного кода, который его отображает.



Графические константы


Константа атрибута

Описание

static int FACE MONOSPACE

Значение атрибута гарнитуры

static int FACE_PROPORTIONAL

Значение атрибута гарнитуры

static int FACE SYSTEM

Значение атрибута гарнитуры

static int STYLE BOLD

Значение атрибута стиля

static int STYLE ITALIC

Значение атрибута стиля

static int STYLE PLAIN

Значение атрибута стиля

static int STYLE UNDERLINED

Значение атрибута стиля

static int SIZE SMALL

Значение атрибута размера

static int SIZE MEDIUM

Значение атрибута размера

static int SIZE LARGE

Значение атрибута размера

Вы должны были заметить, что спецификация MIDP не требует от реализаций предоставления всех этих размеров, стилей и гарнитур. Выводимый шрифт, которым реализация может управлять, будет как можно ближе соответствовать требуемому шрифту.

В отличие от AWT и Swing, вам не придется иметь огромный набор шрифтов и несметное число размеров шрифтов. Более того, поскольку класс Font объявлен final и не имеет конструкторов public, вы не можете организовать его подклассы для определения новых шрифтов. Создатели MIDP решили ограничить число доступных шрифтов с учетом ограничений устройства.

Вам необходимо получить ссылку на текущий объект Font для того, чтобы переслать его в метод Graphics.setFontf). Вы можете получить объект Font, только вызвав один из двух методов static:

Font.getFont(int face, int style, int size)
Font.get Default Font ()

Указанный шрифт будет использоваться во всех последующих операциях по рисованию до тех пор, пока вы вновь его не измените. В листинге 6.6 шрифт был изменен до создания различных текстовых строк или символов для достижения желаемого эффекта.



Методы класса Image


Название метода изображения

Описание

static Image createlmage (byte [] imageData, int imageOffset, int imageLength)

Создает изменяемое изображение из указанных данных изображения, беря изображения начиная с указанных смещения и длины

static Image createlmage (Image source)

Создает изменяемую копию указанного изображения

static Image createlmage (int width, int height)

Создает новое изменяемое изображение с указанной шириной и длиной

static Image createlmage (String name)

Создает изменяемый объект изображения из изображения с путем к ресурсам, указанным в файле JAR набора МЮ-летов

Другие версии создают изменяемые объекты Image. Каждая версия дает вам возможность создавать изображение из различных источников. Первая версия создает изображение из необработанных двоичных данных. Вторая создает изображение из другого объекта изображения. Четвертая версия загружает изображение из файла JAR набора MID-летов. Строковый аргумент указывает имя файла ресурса в файле JAR.

В листинге 6.10 демонстрируется отображение реального изображения PNG. Вместо рисования изображений - рисунков, хранящихся как изображения в формате PNG, - вы можете нарисовать любую «картинку», которую вы сможете создать с помощью низкоуровневых процедур графического рисования, предоставляемых в классе Graphics. Вы можете рисовать геометрические фигуры или отдельные пиксели, заполнять части дисплея и так далее, чтобы создать изображение - рисунок - по, своему желанию.

Двойная буферизация изображений. Изображения подвергаются двойной буферизации неявно. Поэтому вам никогда не придется самостоятельно выполнять двойную буферизацию. Пример, описанный в листинге 6.10, раскрывает причину этого.

Метод paint () создает объект Image из файла ресурса, который представляет изображение PNG для отображения. Но этот объект Image уже имеет связанный с ним контекст Graphics, являющийся внеэкранным Graphics. Поэтому, когда метод paint () выполняет следующий оператор, он копирует содержимое контекста Graphics объекта Image - фактические биты, которые составляют изображение, - в графический контекст дисплея:

g.drawlmage (image, О, О, Graphics.TOP I Graphics.LEFT);

Таким образом, двойная буферизация изображений осуществляется автоматически.

Хотя при рисовании изображения двойная буферизация осуществляется автоматически, очистка отсекаемого прямоугольника, то есть рисование фона Canvas, - нет. Посмотрите внимательнее на метод paint (Graphics д)в листинге 6.10, и вы увидите, что он все еще проверяет, не осуществляет ли реализация автоматическую двойную буферизацию. Если нет, метод paint (Graphics g) использует внеэкранный графический контекст для очистки отсекаемого прямоугольника.

Этот код немного отличается от кода, описанного в листинге 6.9, в этом коде нет явной ссылки на внеэкранный Graphics. Причина этого заключается в том, что объект Image уже предоставил внеэкранную графику. Метод paint (Graphics g) может просто использовать ее как внеэкранный Graphics, необходимый для очистки отсекаемого прямоугольника.



Вы можете рисовать линии в Canvas



Листинг 6.3. Демонстрационная программа описывает метод paint (), который гарантирует, что некоторые визуальные представления появляются на дисплее устройства

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Graphics;
import javax.raicroedition.lcdui.Command;
/*'
Рисует серию линий для демонстрации различных типов и стилей линий,
которые могут быть нарисованы с помощью класса Graphics.
@смотри javax.microedition.Icdui.Graphics
*/
public class LineDemo extends Canvas .
implements CommandListener
}
// Константа, которая представляет белый цвет.
private static final int WHITE = OxFF « 16 | OxFF « 8 I OxFF;
private Command back = new Command("Back", Command.BACK, 1);
private GraphicsDemo gDemo = GraphicsDemo.getlnstance(};
private Display display = Display.getDisplay(gDemo);
/**
Конструктор No-arg.
*/
public LineDemo()
{
super ();
addCommand(back);
setCommandListener(this) ;
display.setCurrent(this);
}
/*'*
Рисует отсекаемый белый прямоугольник, эффективно стирающий все,
что было изображено в Canvas перед этим. "/
protected void paintdipRect (Graphics g)
}
int clipX = g.getClipX ();
int clipY = g.getClipY() ;
int clipH = g.getdipHeight () ;
int clipW = g.getClipWidth();
int color = g.getColor ();
g.setColor(WHITE);
g.fillRect(clipX, clipY, clipW, clipH);
g.setColor (color);
}
/ **
Отображает внешний вид этого подкласса Canvas.
* /
public void paint (Graphics g)
{
paintdipRect (g) ;
int width = getWidth();
int height = getHeight ();
g.drawLine (20, 10, width - 20, height - 34);
g.drawLine(20, 11, width - 20, height - 33);
g.drawLine(20, 12, width - 20, height - 32);
g.drawLine(20, 13, width - 20, height - 31);
g.drawLine(20, 14, width - 20, height - 30);
g.setStrokeStyle(Graphics.DOTTED);
g.drawLine(20, 24, width - 20, height - 20);
g.drawLine(20, 25, width - 20, height - 19);
g.drawLine(20, 26, width - 20, height - 18);
g. setStrokeStyle (Graphics.SOLID);
g.drawLine(20, 36, width - 20, height - 8);
}
public void commandAction(Command c, Displayable d)
{
if (c == back)
{
GraphicsDemo.getlnstanceO.display() ;
}
}
}

Метод paint (Graphics g) является основным в этом примере. Поскольку Canvas описывает этот метод как абстрактный, подклассы должны предоставлять конкретное описание. На экране, созданном программой в листинге 6.2, ничего не появляется, поскольку ее метод paint (Graphics g) не описывает никаких операций по рисованию.

Ваша программа должна выполнять все свои операции по рисованию в методе paint (Graphics g) на объекте Graphics, переданном ей. Вы запускаете стандартные операции по рисованию, предназначенные для класса Graphics, в этом экземпляре, который передан вашему Canvas.

Чтобы нарисовать линию, вы должны указать координаты (х, у) начальной и конечной точек. Координаты (х, у) определяются относительно точки (0, 0), которая, во время создания графического контекста, представляет пиксель, лежащий в верхнем левом углу дисплея, как показано на рисунке 6.3. Координата х определяет горизонтальное расстояние направо от колонки 0 (левый край дисплея), а координата у определяет вертикальное расстояние от строки 0, которая является верхним краем дисплея.

Ширина линий составляет один пиксель. Чтобы нарисовать более толстую линию, вы должны рисовать прилегающие линии, как демонстрируется в листинге 6.3. Три линии, показанные на рисунке 6.4, созданные с помощью листинга 6.3, имеют ширину в пять, три и один пиксель соответственно.

Кроме того, средняя линия отображена штрихпунктиром. Вы можете установить стиль штриховки для любого рисунка с помощью метода setStrokeStyle (), как демонстрируется в программе. Конкретное отображение линий, которые используют стиль штрихования Graphics.DOTTED, зависит от реализации.

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



lcdui формируют определение низкоуровневого программного



Выводы по главе

Два класса в пакете javax.microedition. lcdui формируют определение низкоуровневого программного интерфейса приложения в MIDP: класс Graphics и класс Canvas. Низкоуровневый API MIDP дает вашему приложению возможность получать информацию о событиях низкого уровня, которые недоступны для компонентов высокоуровневого программного интерфейса приложения. Объекты Canvas могут получать информацию о событиях нажатия кнопки или движения указателя. Объекты Canvas являются объектами Displayable. По этой причине они все еще могут выполнять обработку команд, как и другие компоненты Displayable.
Чтобы использовать низкоуровневый API, вы должны создать подкласс Canvas. Затем вы должны описать метод paint (Graphics g) в вашем подклассе, для того чтобы создать видимый внешний вид его экземпляров. Метод подкласса paint (Graphics g) определяет этот видимый внешний вид.
Метод paint (Graphics g) рисует внешний вид компонента Canvas с помощью графического контекста, определенного классом Graphics. Класс Graphics поддерживает рисование и заполнение базовых геометрических фигур, таких, как линии, дуги, прямоугольники, текст и так далее. Он также поддерживает рисование в цвете. Другими поддерживаемыми свойствами являются выбор шрифта для рисования текста, отсечение и перенос начала координат Graphics.
Объекты Canvas могут также отображать изображения с помощью функциональных возможностей класса Graphics. Приложения загружают изображения из файлов, которые должны храниться в формате PNG.
Двойная буферизация - это технология, которая повышает эффективность рисования на ресурсно ограниченных устройствах. Приложения используют два графических контекста. Приложение сначала рисует во внеэкранном буфере, а затем копирует содержимое этого буфера в графическую среду, связанную с дисплеем устройства, формируя изображение внешнего вида компонента Canvas. При рисовании изображений двойная буферизация осуществляется автоматически.

Блоки прослушивания записей



Блоки прослушивания записей

Приложения имеют способность получать уведомления при добавлении записи, ее удалении или изменении в хранилище записей. Класс RecordStore позволяет вам добавлять и удалять блоки прослушивания записей из определенного хранилища данных с помощью методов, перечисленных в таблице 7.2. Блок прослушивания записей является любым классом, реализующим интерфейс RecordListener, определенный в пакете javax.microedition.rms. Он объявляет три метода, показанных в таблице 7.3.



Cпиcки



Cпиcки

Существует на самом деле два способа извлечения записей из хранилища данных:

Извлечение отдельной записи с помощью ее уникального ID; Извлечение списка записей и выбор из них одной или нескольких нужных вам записей.

Чтобы извлечь определенную запись, вы можете использовать следующий метод класса RecordStore:

byte [] getRecord(int recordld)

Этот метод, очевидно, требует, чтобы вы знали уникальный ID записи, которую вы хотите извлечь. К сожалению, это означает, что вам, возможно, придется хранить ID где-нибудь в легкодоступном месте после того, как он будет выдан вам методом addRecord (). Это не всегда удобно или практично при большом количестве записей.

Самый легкий способ найти записи, которые вам нужны, - это использовать списки, которые поддерживаются классом RecordStore. Список весьма удобен при извлечении записей, если вы не знаете ID записей, которые вам нужны. Вы можете создать список записей, хранящихся в хранилище записей, а затем исследовать его, выбрав одну или несколько записей, которые вам нужны.

Класс RecordStore определяет метод

RecordEnumeration
enumerateRecords(RecordFilter filter,
RecordComparator comparator,
boolean keepUpdated)

который выдает список записей в хранилище записей. В листинге 7.2 показан исходный код RecordList.Java. Этот класс создает и отображает список всех записей адресной книги. Обратите внимание, что для того, чтобы извлекать записи, ID записей указывать не нужно.



Фильтры записей



Фильтры записей

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

Первый аргумент в методе enuraerateRecords() указывает фильтр записей. Фильтр является объектом, определяющим семантику соответствия записи набору критериев, которые определяют, должна ли запись включаться в набор списка.

Фильтр записей является классом, реализующим интерфейс RecordFilter, который определяется в пакете javax.microedition.rms. Этот интерфейс определяет единственный метод boolean matches (byte [] candidate). Ваш подкласс RecordFilter задает этот метод и устанавливает критерии фильтрации записей, указанных в списке всех записей хранилища записей. Метод enumerateRecords() активизирует вашу реализацию на каждой записи, извлеченной из хранилища записей.

В листинге 7.3 показан код класса SearchScreen. Java. Он ищет записи, которые начинаются с подстроки, введенной пользователем, или эквивалентные указанной пользователем строке.



Компараторы записей



Компараторы записей

Вы, несомненно, заметили, что второй аргумент, пересланный в enumerateRecords () в предыдущих примерах, был равен нулю. Этот второй параметр является «заполнителем» для компаратора записей. Компаратор записей - это объект, который сравнивает две записи для определения их упорядочивания или сортировки. Компараторы предоставляют приложениям возможность выполнять различную сортировку.

Как и фильтры, компараторы определяют семантику функции сравнения. Компаратор записей является реализацией интерфейса RecordComparator, который определяет единственный метод

int ccmparefbyte [] recordl, byte [] record2)

Компаратор также определяет три константы, описанные в таблице 7.1, которые ваша реализация должна использовать как текущие выводимые значения данного метода.



Класс AddressBook



Листинг 7.1. Класс AddressBook позволяет приложению получать доступ к хранилищу записей

import javax.microedition.rms.RecordComparator;
import javax.microedition.rms.RecordEnumeration;
import javax.microedition.rms.RecordFilter;
import javax.microedition.rms.RecordStore;
import javax.microedition.rms.RecordStoreException;
import javax.microedition.rms.RecordStoreNotOpenException;
import Java.io.ByteArrayInputStream/
import java.io.ByteArrayOutputStream;
import Java.io.DatalnputStream;
import java.io.DataOutputStream;
import Java.io.lOException;
/**
Этот класс внедряет простую адресную книгу с целью демонстрации.
В нем хранятся записи, состоящие из полей имени String и номера телефона String.
Этот класс определяет два внутренних класса,
один является блоком сравнения записей, а второй фильтром записей,
используемым при извлечении записей.
*/
public class AddressBook
private static final String RECORD_STORE_NAME = "address-book";
private RecordStore recordStore;
public AddressBook () throws RecordStoreException
super!);

recordStore = RecordStore.openRecordStore(RECORD_STORE_NAME, true);

{
void close() throws RecordStoreException
{
try
{
recordStore.closeRecordStore();

}
catch (RecordStoreNotOpenException rsno)
{
}
}
/*
Получает хранилище записей, используемое этим объектом.
@возвращаем ссылку на RecordStore, используемый э.тим объектом.
public RecordStore getRecordStore()
}
return recordStore;
/**
Добавляет указанную запись в хранилище записей данной адресной книги.
@param name имя входа было добавлено.
@parara phone телефонный номер для входа был добавлен.
@сбрасывает RecordStoreException, если есть проблемы с добавлением записи.
public void addRecord(String name, String phone)
throws RecordStoreException
}
ByteArrayOutputStreara baos = new ByteArrayOutputStream();

DataOutputStream dos = new DataOutputStream(baos);

try
dos.writeUTF(name);
dos.writeUTF(phone);

}
catch (lOException ioe)
{
ioe.printStackTracef);

)
int id =
recordstore.addRecord(baos.toByteArray(), 0,
baos.toByteArrayO .lengthy-System, out. println ("Record id = " + id);

}
/**
RecordEnumerator, упорядочивающий записи в
лексикографическом порядке по полям
имен записей.
*/
RecordEnumeration getMatchesByNarae(String matchKey)
throws RecordStoreNotOpenException
(
MacchAllNaraesFilter filter =
new MatchAllNamesFilter(matchKey);

AlphabeticalOrdering comparator =
new AlphabeticalOrdering();

return recordStore.enuraerateRecords(filter,
comparator, false);

}
/**
RecordFilter, устанавливающий совпадение, если имя варианта
(первое поле в записи варианта)!) точно соответствует имени
элемента списка или 2) если строка имени элемента списка
начинается с имени варианта. Возвращает значение true, ее
установлено соответствие, false - в ином случае.
*/
class MatchAllNamesFilter implements RecordFilter
{
String requestString;
public MatchAllNamesFilter(String matchKey) ;
requestString = matchKey;
}
public boolean matches(byte [] candidate)
{
ByteArraylnputStream bais =
new ByteArraylnputStream(candidate);

DatalnputStream dis = new DatalnputStream(bais);

Siring name = null;
try
}
name = dis.readUTF();

if (name.indexOf(requestString) == 0)
return true;
else
return false;
}
catch (lOException ioe)
{
ioe.printStackTrace!);

return true;
}

}
/**
Этот внутренний класс реализует RecordCornparator, чья политика
Заключается в выполнении сортировки по алфавиту.
*/
class AlphabeticalOrdering implements RecordCoraparator
}
Конструктор.
public AlphabeticalOrdering ()
(
)
public int compare(byte [] reel, byte [] rec2)
{
ByteArraylnputStream baisl =
new ByteArraylnputStream(recl);

DatalnputStream disl = new DatalnputStream(baisl);

ByteArraylnputStream bais2 =
new ByteArraylnputStream(rec2);

DatalnputStream dis2 = new DatalnputStream(bais2);

String namel = null; String name2 = null; try
namel = disl.readUTF ();

name2 = dis2.readUTF () ;
}
catch (lOException ioe)
ioe.printStackTrace();

}
if (namel == null II name2 == null) return 0;
int result = namel.compareTo(name2);

if (result < 0)
return RecordCornparator. PRECEDES;
else if (result == 0)
return RecordCoraparator.EQUIVALENT;
else
return RecordComparator.FOLLOWS;
}
}
/**
Удаляет все записи из хранилища данных.
В текущих реализациях самый быстрый способ удаления всех записей
заключается в удалении хранилища данных и повторном его создании,
вместо удаления каждой записи по очереди!
void deleteAHRecords ()
}
try
RecordEnumeration re =
recordStore.enumerateRecords(null, null, false);

while (re.hasNextElement())
*/
int id = re.nextRecordld();

recordStore.deleteRecord(id);

}
}
catch (RecordStoreException rse)
{
rse.printStackTracel);

} }
/**
Получает статистику хранилища данных, используемого данной адресной книгой.

/**
возвращает String статистических данных.
*/
public String getStatistics ()
{
int numRecords = 0;
int space = 0;
StringBuffer stats = new StringBuffer("Records:
*/
try
{
numRecords = recordStore.getNumRecords ();

space = recordStore.getSizeAvailable();

)
catch (RecordStoreException rse)
(
rse.printStackTrace();

}
stats.append(String.valueOf(nuraRecords));

stats.append("\n\n") ;
stats.append("Available bytes: ");

stats.append(String.valueOf(space));

return stats . toString();

}
}

Обратите внимание, что класс AddressBook определяет член типа RecordStore. Это экземпляр действительного хранилища записей, используемого приложением. Класс RecordStore является единственным открыто объявляемым классом в пакете RMS. Он определяет абстракцию хранилища записей.

Конструктор AddressBook сбрасывает RecordStoreException, поскольку метод openRecordStore() может сбрасывать три исключения, которые происходят от него. Пакет javax.microedition.rras определяет пять исключений. На рисунке 7.2 показана иерархия наследования, которая содержит типы исключений RMS.



Списки дают вам возможность



Листинг 7.2. Списки дают вам возможность получать доступ к записям, не зная их идентификационных номеров (ID)

import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.List;
import javax.microedition.rms.RecordEnumeration;
import javax.microedition.rms.RecordStore;
import javax.microedition.rms.RecordStoreException;
import java.io.ByteArraylnputStream;
import Java.io.DatalnputStream; import Java.io.lOException;
/**
Этот класс является компонентом пользовательского интерфейса,
отображающим список записей, находящихся в хранилище записей.
Он использует объект AddressBook, определенный классом MID-лета
для данного приложения MID-лета.
@смотри AddressBook
@смотри AddressBookMain
*/
public class RecordList extends List
implements CommandListener
{
private static Command go =
new command("Go", Command.SCREEN, 1);

private static Command back =
new Command("Back", Command.BACK, 1);

private Display display;
private static RecordList instance;
/**
Конструктор.
@param title название экрана пользовательского интерфейса,
который является List.
*/
public RecordList (String title)
superltitle, List.IMPLICIT);

instance = this;
PersistenceDemo pDemo = PersistenceDemo.getlnstance ();

display = Display .get-Display (pDemo) ;
addCommand(back);
setCommandListener (this);

if (buildRecordList() <= 0) setTitle("No records found");

}
/""
Возвращает один экземпляр данного класса.
Вызов этого метода перед созданием объекта возвращает нулевой указатель.
@возвращает экземпляр данного класса.
*/
public static RecordList getlnstance()
}
return instance;
}
void display ()
{
display.setCurrent (this);

{

/**

Создает список записей, хранящихся в хранилище записей. Выдает число найденных записей. Этот метод извлекает все записи из хранилища записей, то есть он не использует фильтров для извлечения записей. Он также не использует компараторов записей, так что не упорядочивает выводимые записи.

<р>
Этот метод не сбрасывает исключений, но находит исключения,
которые происходят при доступе к хранилищу записей.
(@возвращает число записей, найденных в хранилище записей,
или 0, если записей не найдено.
*/
int buildRecordList ()
{
AddressBook addressBook =
AddressBookMain.get Instance!).getAddressBook();

RecordStore recordStore = addressBook.getRecordStore();

int numRecords = 0; try
RecordEnuraeration re;
re = recordStore.enumerateRecords(null,
null, false);

if (re.numRecords() >
0)
{
ByteArraylnputStream bais = null;
DatalnputStreara dis = null;
String name = null;
while (re.hasNextElement())
byte [] record = re.nextRecord();

bais = new ByteArraylnputStream(record);
dis = new DatalnputStrearn (bais ) ;
String strRec = new String(record);

name = dis . readUTFO ;
appendfname, null ;
numRecords++;
)
)
else
}
Alert a = new Alert("No records",
"No records found in record store", null,
AlertType.CONFIRMATION);
a.setTimeout(Alert.FOREVER);

display.setCurrent (a, AddressBookMain.get Instance ());

} )
catch (RecordStoreException re)
re.printStackTrace();
Alert a = new Alert("Error retrieving record",
"Error retrieving record.", AlertType.CONFIRMATION);

a.setTimeout(Alert.FOREVER);
display.setCurrent (a, this);

catch (lOException ioe)
}
ioe.printStackTrace();

}
finally
{
return numRecords;
{
public void coramandAction(Command c, Displayable d)
if (c == back)
AddressBookMain.getlnstancel).display ();

}
}
}

Метод buildRecordList() использует составление списка для получения всех записей, хранящихся в хранилище записей, а затем извлекает поле имени каждой из них, чтобы создать список всех имен. Вызов enumerateRecords () выдает RecordEnumeration, содержащий все записи. С помощью методов hasNextRecord() и nextRecord() цикл while просто извлекает имена из каждой записи и добавляет их в объект List для отображения.

Для каждой записи вы должны расшифровать байтовый массив обратно тому процессу, согласно которому вы создали запись ранее. Вы знаете, что первый элемент, имя, является string, так что вы можете преобразовать его из байтов в String. Обратите внимание, что та же самая идиома потока ввода-вывода Java используется здесь для создания DatalnputStream, который поддерживает API для легкого преобразования встроенных типов Java.



Поиск имен которые



Листинг 7.3. Поиск имен, которые начинаются с подстроки, введенной пользователем, использует API в классе AddressBook, определяющем семантику поиска

import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.TextField;
import javax.microedition.rms.RecordEnumeration;
import javax.microedition.rms.RecordStoreException;
import Java.util.Enumeration;
import Java.util.Vector;
/**
Этот класс внедряет экран, который дает возможность пользователю искать одну
или несколько определенных записей в адресной книге. Пользователь вводит имя
или префикс, который представляет имя одной или нескольких записей
в адресной книге.
*/
public class SearchScreen extends Form
implements CommandListener
{
private static Command go =
new Command("Go", Command.SCREEN, 1);

private static Command back = new Command("Back", Command.BACK, 1);

private static SearchScreen instance; private Display display;
private AddressBookMain addressBook; private TextField keyEntry;
/**
Конструктор.
*/
public SearchScreen(}
(
super("Search for entry");

instance = this;
PersistenceDerao pDemo = PersistenceDemo.getlnstance () ;
display = Display .getDisplay (pDerno) ;
addressBook = AddressBookMain.getlnstance ();

keyEntry = new TextField("Enter name",
null, 20, TextFieid.ANY);
append(keyEntry);

addCommand(go);
addCommand(back);

setCoramandListener(this);

}
/**
Возвращает один экземпляр данного класса.
Вызов данного метода до создания объекта возвращает нулевой указатель.
/**
возвращает экземпляр данного класса.
**/
public static SearchScreen getlnstance ()
return instance; ) void display!)
( display.setCurrentlthis) ;
}
/**
Отображает данные, переданные на экран.
На самом деле этот метод передает обязанности по отображению
данных экземпляру SearchResultScreen. Этот метод,
однако, устанавливает новый экземпляр данного класса на текущее отображение.
Затрата выражается в Vector записей из хранилища записей адресной книги.
*/
void displaySearchResults(Vector results)
SearchResultScreen screen =
new SearchResultScreen (results);

display. setCurrenJ: (screen) ;
)
Создает конечный набор записей, соответствующих указанному имени.
Критерии отбора заключаются в том, что запись должна
соответствовать имени, введенному
пользователем в TextField "keyEntry". Этот метод задействует метод
AddressBook.getMatchesByName() для применения специального фильтра,
определяющего соответствие этого имени.
*/
Vector buildSearchResults()
{
AddressBook addressBook =
AddressBookMain.getInstance().getAddressBookf);

String matchKey = keyEntry.getString();
Vector results = new Vectorf);

try
{
RecordEnuraeration re =
addressBook.getMatchesByName(matchKey);

byte [] record = null;
while (re.hasNextElement())
record = re.nextRecord () ; results.addElement(record);

}
}
catch (RecordStoreException rse)
}
rse.printStackTracet) ;
)
return results;
)
/**
Создает результаты поиска и отображает их на экране.
class BuildSearchResultsAction implements Runnable
{
public void run ()
Vector results = buildSearchResults ();

displaySearchResults(results) ;
}
}
public void commandAction(Command c, Displayable d) ;
if (c == go)
Runnable action = new BuildSearchResultsAction();

action.run () ;
)
else if (c == beck)
}
AddressBookMain.getInstanced.display!);

}
}
}

Метод buildSearchResults() в классе SearchScreen получает список записей, вызывая метод getMatchesByName (String matchKey) в классе AddressBook. Этот метод фильтрует записи для вывода лишь тех, в которых поле имени начинается с matchKey.

Метод getMatchesByName () выполняет эту фильтрацию, пересылая фильтр записей как первый аргумент в метод enumerateRecords (). Экземпляр MatchAllNamesFilter определяет семантику фильтра для нахождения всех записей, которые начинаются с подстроки matchKey.

Метод enumerateRecords () обращается к следующему методу объекта фильтра для каждой записи в хранилище:

boolean matches(byte [] candidate)

Если в результате выводится true, он включает эту запись в набор списка. Теоретически это сходно с определением запроса SQL в системе родственных баз данных. Объект RecordFilter определяет критерии поиска.

Обратите внимание, что в листинге 7.2 аргумент RecordFilter был равен нулю. Таким образом класс RecordList может вывести все записи в списке, фильтр не применяется.

Вы можете описать несколько фильтров для поддержки поиска по различным критериям. Следуя программе листинга 7.4, вы можете определить несколько внутренних классов, которые реализуют RecordFilter и используют внутренний класс, соответствующий осуществляемому поиску.



Этот компаратор записей



Листинг 7.4. Этот компаратор записей определяет семантику упорядочивания записей, базируясь на лексикографической сортировке значений их полей имени

/*'*
Этот внутренний класс реализует RecordComparator,
чья политика заключается в выполнении сортировки по алфавиту.
*/
class AlphabeticalOrdering implements RecordComparator
/**
Конструктор No-arg.
*/
public AlphabeticalOrdering()
}
super();
)
public int comparelbyte [] reel, byte [] rec2)
ByteArraylnputStream baisl =
new ByteArraylnputStream(reel);

DatalnputStream disl = new DatalnputStream (baisl);

ByteArraylnputStream bais2 -
new ByteArraylnputStream(rec2);

DatalnputStream dis2 = new DatalnputStream(bais2);

String namel = null;
String name2 = null; try
(
namel = disl.readUTF ();

name2 = dis2.readUTF () ;
catch (lOExceotion ioe)
ioe.pnntStackTrace () ;
}
if (namel == null I| name2 == null) return 0;
int result = namel.compareTo(narae2);

if (result < 0)
return RecordComparater.PRECEDES;
else if (result == 0)
return RecordComparator.EQUIVALENT;
else
return RecordComparator.FOLLOWS;
}
}

Ваша адресная книга может использовать этот новый компаратор для лексикографической сортировки списка имен, извлеченных из хранилища записей. Например, чтобы отсортировать имена, выведенные поиском, вы просто создаете экземпляр вашего нового компаратора и пересылаете его как второй аргумент в вызов enumerateRecords (). Следующий фрагмент кода, показанный в листинге 7.5, является новой версией вызова метода getMatchesByName(String matchKey) в классе AddressBook.



Чтобы осуществить



Листинг 7.5. Чтобы осуществить сортировку, просто перешлите экземпляр компаратора в вызов списка записей из хранилища записей. Различные списки могут определять различную политику сортировки

RecordEnumeration getMatchesByName(String matchKey)
throws RecordStoreNotOpenException
{
MatchAllNaraesFilter filter =
new MatchAHNamesFilter (matchKey) ;
AlphabeticalOrdering comparator =
new AlphabeticalOrdering();

return recordStore.enumerateRecords(filter,
comparator, false) ;
}

Вы можете запустить это приложение и определить для себя, какие из записей, выведенных в результате поиска, теперь будут отсортированы лексикографически. Вы также можете использовать этот компаратор для сортировки имен, выводимых в List функцией ввода адресной книги. Вместо пересылки null как для фильтра, так и для компаратора перешлите экземпляр компаратора AlphabeticalOrdering при извлечении списка всех записей.



Модель хранения данных RMS



Модель хранения данных RMS

RMS поддерживает создание множества хранилищ записей, показанных на рисунке 7.1, и управление ими. Хранилище записей - это база данных, основным понятием которой является запись. Каждое хранилище записей содержит ноль или больше записей. Название хранилища записей чувствительно к регистру и может состоять максимум из 32 знаков уникода. Хранилище записей создается МШ-летом.



Пакет RMS определяет несколько



Работа с данными byte [ ]

Как уже упоминалось выше, приложение в этом примере работает с записями, которые содержат имя и номер телефона. Пользователь вводит как имена, так и телефонные номера как объекты String, поскольку экран ввода данных использует экземпляры класса TextField, описанный ранее в главе 5. Соответственно, метод addRecord () получает эти значения String и преобразует их в байты.

Так или иначе, эти значения должны быть преобразованы в один массив байтов перед добавлением в хранилище данных. Причина того, что вы должны выполнить это преобразование, заключается в том, что API RecordStore хранит записи только в виде однобайтового массива.

Метод addRecord () использует стандартную идиому ввода-вывода Java при создании DatalnputStream, который поддерживает запись встроенных типов Java в выходном потоке. Получающийся в результате байтовый массив затем добавляется в объект RecordStore.

Метод RecordStore.addRecord() возвращает int, которая представляет значение ID только что созданной записи. Ваше приложение может сохранить данный ID и использовать его при последующем извлечении записи. Но существует более интересный способ извлечения записей.



Поддержка постоянного хранения устройством



Поддержка постоянного хранения устройством

Каждое соответствующее требованиям MIDP устройство поддерживает выделенную область памяти для постоянного хранения данных приложения. Данные MID-лета, хранящиеся там, постоянно существуют при множестве инициализаций приложения, которое их использует. Как физическое местоположение, так и размер хранилища данных зависят от устройства.

RMS API извлекает подробную информацию об области хранения устройства и доступе к этой информации, а также предоставляет единообразный механизм для создания, уничтожения и изменения данных. Это гарантирует переносимость MID-летов на различные устройства.



Пример приложения



Пример приложения

В остальной части этой главы описываются частные подробности RMS с помощью следующего примера, использующего базовые свойства RMS. Этот пример является простой адресной книгой, которая хранит имена и номера телефонов.

Многие из примеров имеют дело с созданием организации и структуры приложений MIDP. Большинство протекающих операций RMS ограничены одним классом. В этом примере вы можете видеть, как включать использование постоянного хранения в приложение, которое вы, вероятно, найдете на настоящем мобильном устройстве.

Конечно, вы можете создать и исполнить исходный код, приведенный в этой главе, для получения представления о том, как приложение продвигается вперед по различным экранам. Я оставляю это на ваше усмотрение вместо того, чтобы показывать вам здесь изображения всех этих экранов.

Следующие файлы включены в адресную книгу, описанную в данном примере:

AddScreen.java; AddressBook.java; AddressBookMain.java; DeleteAllConfirmationScreen.java; PersistenceDemo.java; RecordList.java; SearchResultScreen.java; SearchScreen.java.

Подробные листинги этих файлов можно найти на Web-сайте «Prentice-Hall» по адресу http://www.phptr.com. Файл PersistenceDemo.java определяет MID-лет, который представляет меню, содержащее приложение адресной книги. Файл AddressBookMain.java определяет точку входа в приложение адресной книги.

В листинге 7.1 показан полный исходный код класса AddressBook.java. Этот класс извлекает подробную информацию о вызовах RMS API из остальной части МID-лета. При инициализации MID-лета он создает экземпляр класса AddressBook, который, в свою очередь, открывает хранилище записей с именем addressbook.



Различные свойства хранилищ записей



Различные свойства хранилищ записей

Класс RecordStore определяет несколько других свойств, которые полезны для приложений. В таблице 7.4 перечислены некоторые из других методов класса RecordStore и кратко описано их использование.



RMS состоит из одного или нескольких



Записи

Запись является массивом байтов типа byte []. RMS не поддерживает описание или форматирование полей записи. Ваше приложение должно определять элементы данных записи и их формат.

Читатель записи поэтому должен знать формат, который использовался при ее создании. Поскольку запись является просто массивом байтов, приложения должны преобразовывать данные из произвольных типов в байты при создании записей, а затем преобразовывать их из байтов в типы при чтении данных.



Константы RecordComparator


Константа

Описание

public static int EQUIVALENT

Две записи эквивалентны в соответствии с семантикой сравнения

public static int FOLLOWS

Запись 1 «больше», чем запись 2, в соответствии с семантикой сравнения

public static int PRECEDES

Запись 1 «меньше», чем запись 2, в соответствии с семантикой сравнения

Идея использования компараторов сходна с понятием фильтрации записей. Вы определяете класс, который реализует интерфейс javax.microedition.rras.RecordComparator. Вы передаете его экземпляр в вызов enumerateRecords (). Записи, извлеченные из хранилища записей, сравниваются друг с другом, по две одновременно, а затем сортируются в соответствии с результатами сравнения. Вы можете таким образом извлекать записи из списка в порядке, определяемом компаратором.

В листинге 7.4 демонстрируется использование компаратора записей. Он определяет новый внутренний класс класса AddressBook, который вы видели в листинге 7.1. Новый внутренний класс AlphabeticalOrdering реализует RecordComparator. Его метод сравнения извлекает поле имени из каждого параметра байтового массива и сравнивает их лексикографически (по словам).



Методы поддержки блока


Название метода RecordStore

Описание

Void addRecordListener (RecordListener listener)

Делает указанный объект блоком прослушивания для данного хранилища записей

Void removeRecordListener (RecordListener listener)

Удаляет указанный блок прослушивания как блок прослушивания данного хранилища записей



Методы интерфейса RecordListener


Название метода RecordListener Описание

void recordAdded (RecordStore recordStore, int recordld)

Уведомляет блок прослушивания записей о том, что запись была добавлена в указанное хранилище записей с указанным ID

void recordChanged (RecordStore recordStore, int recordld)

Уведомляет блок прослушивания записей о том, что запись с указанным ID была изменена в хранилище записей

void recordDeleted(RecordStore recordStore, int recordld)

Уведомляет блок прослушивания записей о том, что запись с указанным ID была удалена из хранилища записей

Возможность связывать блоки прослушивания с хранилищами записей означает, что ваши блоки прослушивания могут быть уведомлены об изменении любой записи в хранилище записей, к которому данные блоки прослушивания относятся. Необходимо переслать обратно информацию о задействованном хранилище записей, потому что ваш блок прослушивания может без труда регистрироваться более чем с одним хранилищем записей. Идея регистрации блока прослушивания записей сходна с идиомой, используемой любым другим блоком прослушивания событий, так что я не буду описывать здесь примеры кодов.



Методы класса RecordStore


Название метода

Описание

void- closeRecordStore ( )

Закрывает хранилище записей

static void deleteRecordStore ( )

Удаляет хранилище записей

long getLastModif ied ( )

Выдает время последней модификации

String getNameO

Выдает название хранилища записей

int getNumRecords ()

Выдает число записей в хранилище

byte [] getRecordfint recordld)

Извлекает запись по Ю

byte [] getRecord(int recordld, byte [] buffer, int offset)

Получает запись и помещает ее в предоставленный буфер

byte [] getRecordSize (int recordld)

Получает размер указанной записи

int getSizef)

Выдает размер места (в байтах), которое занимает хранилище записей

int getSizeAvailable ( )

Выдает число оставшихся байтов, на которое хранилище записей может вырасти

int getVersionf)

Выдает номер версии хранилища записей

static String [] listRecordStores ()

Выдает список всех хранилищ записей, доступных набору MID-летов

static RecordStore openRecordStore (String name, boolean createlfNecessary)

Открывает указанное хранилище записей, создавая его, если оно не существует



MIDP поддерживает постоянное хранение записей



Выводы по главе

Система управления записями (RMS) MIDP поддерживает постоянное хранение записей данных в зависимости от устройства. Класс RecordStore предоставляет API для постоянного хранения данных и извлекает подробную информацию о доступе к определяемым устройством областям хранения.
Хранилища записей определяются по именам, которые состоят максимум из 32 знаков уникода. Хранилища записей могут совместно использоваться MID-летами, находящимися в одном наборе MID-летов.
RMS определяет простую абстракцию базы данных, связанную с записями. Записи хранятся как массив байтов. Хранилище записей не имеет понятий встроенных типов Java.
Вы можете извлекать записи, предоставляя уникальный ID записи. Либо вы можете извлекать записи, получая список записей из RecordStore.
Списки необходимы для поиска записей в хранилище записей. Теоретически фильтры записей предоставляют своего рода механизм запросов. В связи с возможностью составления списков в RecordStore, фильтры записей поддерживают поиск только тех записей, которые соответствуют одному или нескольким критериям. Фильтр записей, класс, который реализует интерфейс RecordFilter, определяет критерии поиска.
Компараторы записей предоставляют возможность сортировки записей, извлекаемых из списка. Компараторы определяют политику сортировки и используются с механизмом составления списка. Реализация RecordComparator определяет семантику сортировки.
Блоки прослушивания записей являются блоками прослушивания, регистрирующимися с определенным хранилищем записей. Они дают возможность уведомления вашей программы об изменениях, вносимых в любую запись, находящуюся в хранилище записей.
Производительность является важной проблемой при доступе к хранилищу записей. Производительность современных реализаций RMS довольно низка. Разработчики приложений должны с осторожностью подходить к использованию RMS, применяя ее только тогда, когда это необходимо. Они должны рассматривать другие альтернативы постоянного хранения данных и сравнивать различные варианты.
Разработчики должны также измерять производительность их реализации RMS при запуске приложений, чтобы убедиться, что производительность приемлема для конечных пользователей. Бывало, что действующие приложения начинали работать слишком медленно из-за использования обновлений хранилища записей. Подтверждено, что перезапись приложений таким образом, чтобы все содержимое хранилища записей было загружено и перемещено, быстрее, чем выполнение обновлений в измененных элементах!