Kitabı oku: «Графические интерфейсы пользователя Java», sayfa 3
Java 2D
Как уже было сказано, библиотека AWT была дополнена библиотекой Java 2D API, расширяющей возможности работы с двухмерной графикой и изображениями.
Java 2D API предоставляет единую модель рендеринга для разных типов устройств.
На уровне приложения процесс рендеринга одинаковый, является ли целевое устройство рендеринга экраном или принтером.
Когда компонент должен отображаться, автоматически вызывается его метод paint или update с соответствующим графическим контекстом Graphics.
Java 2D API предоставляет класс Graphics2D, который расширяет класс Graphics, чтобы обеспечить доступ к расширенной графике и функциям рендеринга Java 2D API.
Чтобы использовать функции Java 2D API в приложении, нужно привести объект Graphics, переданный в метод рендеринга компонента, к объекту Graphics2D.
И объект Graphics2D предоставит следующие функции:
Это отображение набора примитивов, реализующих интерфейс Shape.
При этом примитив может быть заполнен цветом и его контуры могут быть нарисованы с указанием определенной толщины и шаблоном штрихов.
Graphics2D позволяет создать композицию примитивов, переместить, масштабировать, повернуть и обрезать форму.
Также Graphics2D позволяет конвертировать текстовую строку в глифы, которые затем могут быть заполнены цветом.
Примитивы – это точки, линии, прямоугольники, эллипсы, дуги, кривые.
Также можно нарисовать произвольную форму с помощью пути рисования GeneralPath.
Контур примитива можно определить с помощью объекта Stroke.
А заполнить примитив цветом можно с помощью объекта Paint.
Java 2D API предоставляет различные возможности для отображения текста, включая установку атрибутов шрифта и выполнения компоновки текста.
Если вы просто хотите нарисовать статическую текстовую строку, проще всего это сделать с помощью метода drawString класса Graphics, указав шрифт методом setFont класса Graphics.
Если вы хотите контролировать качество отображения текста и его компоновку, вы можете использовать Java 2D API.
Java 2D API позволяет контролировать качество отображения с помощью подсказок класса RenderingHints, например, указав сглаживание текста.
Класс TextLayout предоставляет различные возможности для стилизации текста.
Перед тем, как текст может быть отображен, он должен быть правильно сформирован и расположен с использованием соответствующих символов и лигатур.
Этот процесс называется компоновкой текста.
Процесс компоновки текста включает в себя формирование текста с использованием соответствующих глифов и лигатур, упорядочивание текста, измерение и позиционирование текста.
Что касается изображений, Java 2D API позволяет загрузить внешний файл изображения формата GIF, PNG, JPEG во внутреннее представление изображения, используемое Java 2D.
Непосредственно создать 2D Java изображение и отобразить его.
Отрисовать содержимого 2D-изображения Java на поверхности.
Сохранить содержимое 2D Java изображения во внешний файл изображения GIF, PNG или JPEG.
Для работы с изображениями используются два класса – это класс Image – суперкласс, представляющий графические изображения в виде прямоугольных массивов пикселей.
И класс BufferedImage, который расширяет класс Image, чтобы приложение могло работать непосредственно с данными изображения, например, извлечение или настройка цвета пикселя.
И приложения могут напрямую создавать экземпляры этого класса.
Класс BufferedImage управляет изображением в памяти и предоставляет методы для хранения, интерпретации и получения данных пикселей.
Так как BufferedImage является подклассом Image, его контент может быть визуализирован с помощью методов Graphics и Graphics2D, которые принимают параметр Image.
Для загрузки изображения из внешнего источника используется Image I/O API, которое поддерживает форматы изображения GIF, PNG, JPEG, BMP.
Соответственно Image I/O API используется и для сохранения объекта BufferedImage во внешний формат изображения.
Для получения изображения из внешнего источника также можно использовать класс Toolkit, как мы видели уже раньше.
Для отрисовки полученного изображения нужен объект Graphics или Graphics2D.
Если мы не используем метод paint или update компонента, получить объект Graphics2D можно вызвав метод createGraphics, но только изображения, которое вы создали сами.
Поэтому, получив изображение из внешнего источника, мы создаем пустое изображение того же размера, получаем из него объект Graphics2D, и используем его для отрисовки нашего изображения.
Используя возможность получения графического контекста из изображения, можно, например, создать свое изображение, рисуя в нем с помощью объекта Graphics или Graphics2D.
Работа с изображениями
AWT предоставляет некоторые возможности для управления изображениями с помощью пакета java.awt.image.
Как мы уже говорили, получить изображение из внешнего источника можно с помощью объекта Toolkit.
Затем с помощью BufferedImage мы можем получить объект графического контекста и нарисовать изображение.
Далее вы можете масштабировать это изображение.
Если вы хотите что-то нарисовать на своем изображении, вы можете получить графический объект и делать все что захотите.
Java была разработана для загрузки изображения во время работы программы.
Таким образом, вы можете вызвать методы getWidth и getHeight до того, как Java узнает размер изображения.
В этом случае размер изображения будет установлен в -1.
Это основная проблема, когда вам нужно знать размер изображения.
Решение этой проблемы заключается в том, чтобы ваша программа дождалась загрузки изображения.
Сделать это можно с помощью класса MediaTracker, предназначенного для отслеживания состояния изображений.
Мы добавляем изображение для отслеживания и вызываем метод waitForID, который начинает загрузку изображения, отслеживаемого этим медиа-трекером, с указанным идентификатором.
Этот метод ожидает завершения загрузки изображения с указанным идентификатором.
Отслеживать загрузку изображения также можно с помощью интерфейса ImageObserver, который реализуется классом Component.
ImageObserver – это интерфейс, используемый для приема уведомлений о том, как генерируется изображение.
ImageObserver определяет только один метод: ImageUpdate ().
Использование наблюдателя изображения позволяет выполнять (параллельно с загрузкой изображения) другие действия, такие как показ индикатора хода работы или дополнительного экрана, который информирует о ходе загрузки изображения.
Многие из разработчиков Java находили интерфейс ImageObserver слишком сложным для понимания и управления загрузкой множественных изображений.
Поэтому был создан класс MediaTracker, который позволяет параллельно проверять состояние произвольного числа изображений.
Для многих реальных задач обработки изображений необходимо получить массив с пикселями изображения.
Здесь показан код, который позволяет сделать это.
В этом коде мы предполагаем, что изображение полностью загружено.
Сначала мы получаем размер изображения.
Затем создаем массив для пикселей изображения.
Далее создаем объект PixelGrabber, который с помощью метода grabPixels позволяет извлечь пиксели в массив, указанный при создании объекта PixelGrabber.
После получения массива пикселей изображения, его можно обработать и создать новое изображение.
Для создания изображения из массива пикселей используется вспомогательный класс MemoryImageSource и объект Toolkit.
И наконец полученное изображение можно нарисовать.
На слайде показано, как создаются данные изображения за сценой.
Производитель изображения – это объект, который реализует интерфейс ImageProducer, и создает необработанные данные для объекта изображения Image.
Производитель изображения предоставляет эти данные потребителю изображения – объекту, который реализует интерфейс ImageConsumer.
Если вам не нужно манипулировать или создавать пользовательские изображения, вам обычно не нужно знать о производителе изображения и потребителе изображения.
AWT автоматически использует производителя и потребителя изображения за сценой.
При создании экземпляра класса Image требуется наличие производителя изображений.
Код глубоко внутри AWT создает производителя изображений.
Как альтернатива программист может предоставить создателя изображения, при создании объекта изображения Image.
При рисовании экземпляра класса Image методом draw, глубоко внутри AWT создается потребитель изображения.
Здесь показано, как при создании пользовательского компонента, можно с помощью метода createImage класса Component создать пользовательское изображение, реализуя собственные ImageProducer и ImageConsumer, и нарисовать это изображение в методе paint компонента.
Java позволяет «фильтровать» изображения с помощью класса ImageFilter или его подклассов.
AWT поддерживает обработку изображений, позволяя вставлять фильтры изображений между производителем изображения и потребителем изображения.
Фильтр изображения представляет собой объект ImageFilter, который находится между производителем и потребителем изображения, изменяя данные изображения до того, как потребитель получит его.
ImageFilter реализует интерфейс ImageConsumer, поскольку фильтр изображения перехватывает сообщения, которые производитель отправляет потребителю.
Для использования фильтра, создается объект фильтра.
Далее нужно создать объект класса ImageProducer.
Этот объект создается при помощи конструктора класса FilteredImageSource.
В качестве первого параметра мы передаем конструктору ссылку на источник данных исходного изображения, полученный методом getSource, а через второй – ссылку на свой фильтр.
Затем мы можем создать новое изображение методом createImage.
Чтобы дождаться процесса завершения формирования изображения, используйте класс MediaTracker.
После этого изображение готово и доступно для рисования методом drawImage.
Все фильтры изображений должны быть подклассами класса ImageFilter.
Если ваш фильтр изображения изменяет цвета или прозрачность изображения, вы можете создать подкласс класса RGBImageFilter, который расширяет класс ImageFilter.
Здесь показан простой фильтр изображения, который отфильтровывает отдельные цветовые компоненты (красный, зеленый и синий) изображения.
Класс ColorFilter расширяет класс RGBImageFilter и содержит три логические переменные, которые определяют, какие цвета должны быть отфильтрованы из изображения.
Эти переменные задаются параметрами, переданными в конструктор.
Переменная canFilterIndexColorModel, унаследованная от RGBImageFilter, установлена в значение true, чтобы указать, что записи цветовой карты могут быть отфильтрованы, если входящее изображение использует модель индексированного цвета.
В Java цвета пикселей управляются через цветовые модели.
Цветовые модели Java обеспечивают важную абстракцию, которая позволяет Java работать с изображениями разных форматов единым образом.
Цветовая модель представляет собой объект Java, который предоставляет методы для перевода значений пикселей в соответствующие красные, зеленые и синие цветовые компоненты изображения.
Это может показаться тривиальной задачей, зная, что компоненты цвета пикселей аккуратно упакованы в 32-битное значение.
Тем не менее, существуют различные типы цветовых моделей, отражающие различные методы определения цветов пикселей.
Двумя типами цветовых моделей, поддерживаемых Java, являются прямая цветовая модель и модель индексированных цветов.
Прямая цветовая модель работает со значениями пикселей, которые представляют цвет RGB и альфа-информацию отдельно, которые упакованы для каждого пикселя в одно значение.
Модель индексированных цветов поддерживается 8-битными изображениями, содержащими не более 256 цветов.
Эта модель работает с картой цветов изображения, в которой хранятся и индексируются цвета, используемые в изображении.
Эта позволяет уменьшить размер файла изображения, при этом сохраняя качество изображения.
Вернемся к нашему примеру.
Помимо конструктора, класс ColorFilter реализует только один метод filterRGB, который является абстрактным методом, определенным в классе RGBImageFilter.
Метод filterRGB принимает три параметра: положение x и y пикселя внутри изображения и 32-битное (целочисленное) значение цвета.
Единственный параметр, которым вы занимаетесь, является значение цвета, rgb.
Стандартная цветовая модель RGB помещает красные, зеленые и синие компоненты в нижние 24 бита 32-битного значения цвета.
И каждый из них можно извлечь, сдвигая параметр rgb.
Эти отдельные компоненты хранятся в локальных переменных r, g и b.
Обратите внимание, что здесь каждый компонент цвета смещается только в том случае, если он не фильтруется.
Для фильтрованных цветов, компонент цвета установлен в 0.
Новые цветовые компоненты затем переносятся обратно в 32-битное значение цвета и возвращаются из метода filterRGB.
Обратите внимание, что альфа-компонент значения цвета не изменяется.
Для этого используется маска 0xff000000, потому что альфа-компонент находится в верхнем байте значения цвета.
Помимо обработки изображений, с помощью вставки фильтров изображений между производителем изображения и потребителем изображения,
Java поддерживает фильтрацию изображений с помощью интерфейса BufferedImageOp.
Метод filter интерфейса BufferedImageOp принимает объект BufferedImage как вход (исходное изображение) и выполняет обработку данных изображения, создавая другой объект BufferedImage (конечное изображение).
Напомним, что класс BufferedImage расширяет класс Image, обеспечивая доступ к буферу данных изображения.
Java 2D API предоставляет набор реализаций интерфейса BufferedImageOp.
AffineTransformOp – преобразует изображение геометрически.
ColorConvertOp – выполняет по-пиксельное преобразование цвета в исходном изображении.
ConvolveOp – выполняет свертку, математическую операцию, которая может использоваться для размытия, изменения резкости или другой обработки изображения.
LookupOp – изменяет отдельные составляющие цвета.
RescaleOp – изменяет интенсивность изображения.
Здесь показан пример применения фильтра RescaleOp, изменяющего интенсивность цвета.
В этом примере сначала создается исходный объект BufferedImage на основе изображения, затем создается пустой объект BufferedImage.
Который заполняется отфильтрованными данными исходного изображения, с помощью метода filter созданного объекта RescaleOp.
JavaBeans и POJO
Откроем среду IntelliJ IDEA с созданным проектом Java приложения.
Нажмем правой кнопкой мыши на пакете приложения, и в меню выберем New – GUI Form.
Введем имя формы.
В результате будет создан Java класс и связанное с ним XML описание, которое открывается в редакторе IntelliJ IDEA GUI Designer.
Редактор IntelliJ IDEA GUI Designer позволяет создавать графические пользовательские интерфейсы (GUI) приложения, используя компоненты библиотеки Swing.
Этот инструмент помогает создавать диалоговые окна и группы элементов управления, которые будут использоваться в контейнере верхнего уровня, таком как JFrame.
Когда вы создаете форму с помощью GUI Designer, вы создаете панель, а не фрейм.
Пользуясь палитрой компонентов редактора, вы можете перетаскивать компоненты в форму и редактировать их свойства.
Для того чтобы компонент графического интерфейса пользователя можно было применять в таком визуальном средстве разработки, он должен обладать дополнительными качествами.
У него должен быть ярлык, помещаемый в палитру компонентов.
Среди полей компонента должны быть выделены свойства (properties), которые будут показаны в окне свойств.
У него должны быть определены методы доступа getXxx () /setXxx () /isXxx () к каждому свойству.
Этими методами будет пользоваться IDE среда разработки, чтобы определить свойства компонента.
Компонент, имеющий эти и другие определенные свойства, в технологии Java называется компонентом JavaBean.
В него может входить один или несколько классов.
Как правило, файлы этих классов упаковываются в один jar-файл и отмечаются в его файле MANIFEST. MF как Java-Bean: True.
Все компоненты AWT и Swing являются компонентами JavaBeans.
Спецификация Sun Microsystems определяет JavaBeans как повторно используемые программные компоненты, которыми можно управлять, используя графические конструкторы и средства IDE.
Визуальные средства разработки – это не основное применение JavaBeans.
Главное достоинство компонентов, оформленных как JavaBeans, в том, что они без труда встраиваются в любое приложение.
И приложение можно собрать из готовых JavaBeans как из строительных блоков, остается только настроить их свойства.
Это называется компонентное программирование.
Чтобы класс мог работать как bean, он должен соответствовать определённым соглашениям об именах методов, конструкторе и поведении.
Эти соглашения дают возможность создания инструментов, которые могут использовать, замещать и соединять JavaBeans.
Эти соглашения следующие:
Класс должен иметь конструктор без параметров, с модификатором доступа public.
Такой конструктор позволяет инструментам создать объект без дополнительных сложностей с параметрами.
Свойства класса должны быть доступны через get, set и другие методы (так называемые методы доступа), которые должны подчиняться стандартному соглашению об именах.
Это легко позволяет инструментам автоматически определять и обновлять содержание bean’ов.
Многие инструменты даже имеют специализированные редакторы для различных типов свойств.
Класс должен быть сериализуем.
Это даёт возможность надёжно сохранять, хранить и восстанавливать состояние bean независимым от платформы и виртуальной машины способом.
Класс должен иметь переопределенные методы equals, hashCode и toString.
В среде разработки NetBeans можно легко создать компонент JavaBeans с помощью меню New проекта приложения.
Для создания компонентов JavaBeans Java SE API включает в себя пакет java.beans.
В частности, помимо обычных свойств, представленных отдельными значениями и массивами значений, компонент JavaBeans может иметь связанные свойства, которые уведомляют слушателей, когда изменяется их значение.
Класс компонента JavaBeans содержит методы addPropertyChangeListener и removePropertyChangeListener для управления слушателями, и когда связанное свойство изменяется, компонент отправляет событие PropertyChangeEvent своим зарегистрированным слушателям.
Также класс компонента JavaBeans может запускать любой тип события, включая пользовательские события.
Как и в случае со свойствами, события идентифицируются по определенной схеме имен методов, add <Event> Listener и remove <Event> Listener.
Другое понятие, с которым вы можете столкнуться – это Plain Old Java Object.
Plain Old Java Object – это объект Java, который не расширяет или не реализует специализированные классы и интерфейсы каких-либо фреймворков.
POJO это просто класс Java, который содержит только поля, без логики их обработки и обеспечивает доступ ко все полям только через методы get/set.
Компоненты JavaBeans дополняют Plain Old Java Object наличием конструктора по умолчанию и наличием слушателей listener изменения свойств.
То есть Plain Old Java Object описывает структуру данных для дальнейшего использования в приложении.
Сериализация
Все мы знаем о том, что Java позволяет создавать в памяти объекты для многократного использования.
Однако все эти объекты существуют лишь до тех пор, пока выполняется создавшая их виртуальная машина.
Было бы неплохо, если бы создаваемые нами объекты могли бы существовать и вне пределов жизненного цикла виртуальной машины?
Как уже было сказано, JavaBeans обладает свойством сохранения, когда его свойства, поля и информация о состоянии могут сохраняться и извлекаться из хранилища.
Механизм, который делает возможным такое сохранение, называется сериализацией.
Сериализация объектов означает преобразование объекта в поток данных и запись его в хранилище, при этом объект представлен как последовательность байтов, которая включает в себя данные объекта, а также информацию о типе объекта и типах данных, хранящихся в объекте.
Любое приложение, которое использует такой компонент, может затем «восстановить» его путем десериализации.
Затем объект возвращается в исходное состояние.
Например, приложение Java может сериализовать окно Frame на машине Windows, сериализованный файл можно отправить с помощью электронной почты на машину Solaris, а затем приложение Java сможет восстановить окно Frame в точное состояние, существующее на машине Windows.
Чтобы так сохраняться, компоненты должны поддерживать сериализацию, реализуя либо интерфейс java.io.Serializable, либо интерфейс java.io.Externalizable.
При сериализации объекта в файл, по соглашению создается файл с расширением. ser.
Классы ObjectInputStream и ObjectOutputStream представляют собой потоки, которые содержат методы сериализации и десериализации объекта.
Это два класса, с помощью которых собственно осуществляют сериализацию и десериализацию объекта.
Если какой-либо класс в иерархии наследования класса реализует интерфейс Serializable или Externalizable, тогда этот класс и его подклассы сериализуются.
Примеры сериализуемых классов включают в себя классы Component, String, Date, Vector и Hashtable.
Известные классы, которые не поддерживающие сериализацию, включают в себя классы Image, Thread, Socket и InputStream.
Попытка сериализации объектов этих типов приведет к исключению NotSerializableException.
Java Object Serialization API автоматически сериализует большинство полей объекта Serializable в поток байтов.
Сюда входят примитивные типы, массивы и строки.
API не сериализует или десериализует поля, отмеченные как transient или static.
Transient (нерезидент) – модификатор полей класса.
Отмеченные этим модификатором поля не записываются в поток байт при применении стандартного алгоритма сериализации.
При десериализации объекта такие поля инициализируются значением по умолчанию.
Этот модификатор применяется, например, если поле класса является объектом несериализуемого класса, или некоторые поля могут не сериализоваться из соображений безопасности, например, поле пароля, или значение поля корректно только в рамках текущего контекста.
Вы можете контролировать уровень сериализации объекта.
Есть несколько способов управления серилизацией:
Это автоматическая сериализация, реализуемая интерфейсом Serializable.
Программное обеспечение сериализации Java сериализует весь объект, за исключением полей transient или static.
Интерфейс Serializable не объявляет никаких методов; он действует как маркер, сообщая инструментам сериализации объектов, что ваш класс сериализуется.
Маркировка класса интерфейсом Serializable означает, что вы сообщаете виртуальной машине Java (JVM), что ваш класс будет работать с сериализацией по умолчанию.
Классы, которые реализуют Serializable, должны иметь доступ к конструктору без аргументов супертипа.
Этот конструктор будет вызываться, когда объект будет «восстанавливаться» из файла. ser, в котором хранится сериализованный объект.
Вам не нужно реализовывать Serializable в вашем классе, если он уже реализован в суперклассе.
Все поля, кроме полей transient или static, сериализуются.
Используйте модификатор transient, чтобы указать поля, которые вы не хотите сериализовать, и указать классы, которые не являются сериализуемыми.
Если объект однажды уже записанный в поток, спустя какое-то время записывается в него снова, то по умолчанию, ObjectOutputStream сохраняет ссылки на объекты, которые в него записываются.
Это означает, что, если состояние записываемого объекта, который уже был записан, будет записано снова, новое состояние не сохраняется.
Существует два способа это исправить.
Во-первых, вы можете каждый раз после вызова метода записи закрывать поток, а затем открывать его снова.
Во-вторых, вы можете вызвать метод ObjectOutputStream.reset, который сигнализирует потоку о том, что необходимо освободить кэш от ссылок, которые он хранит, чтобы новые вызовы методов записи действительно записывали данные.
Вы можете дополнительно контролировать, как объекты сериализуются, путем написания собственных реализаций методов writeObject и readObject и включить их в свой сериализуемый класс.
Классы, требующие специальной обработки во время процесса сериализации и десериализации, должны определить эти методы с этими точными сигнатурами.
Метод writeObject класса определяет сериализацию объекта.
А метод readObject восстанавливает поток данных, который вы определили с помощью writeObject.
Если же вы хотите полностью контролировать сериализацию объекта, используйте интерфейс Externalizable, например, при записи и чтении определенного формата файла.
Для использования интерфейса Externalizable вам необходимо реализовать два метода: readExternal и writeExternal.
Классы, которые реализуют Externalizable, должны иметь конструктор без аргументов.
Класс XMLEncoder позволяет сериализовать объект в XML-файл.
Одно из достоинств этой формы сериализации заключается в том, что вы можете легко просмотреть данные объекта в сохраненной форме.
Вы также можете изменить данные в файле в текстовом редакторе.
Соответственно с помощью класса XMLEncoder, можно легко восстановить потом объект из XML-файла.