Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых
[Назад]

Библиотека примеров приложений Java

Оглавление
Кнопки Button

Кнопки с графикой и анимацией
Кнопка в виде аплета
Переключатели класса Checkbox
Переключатели с зависимой фиксацией
Нестандартные переключатели
Списки класса Choice
Списки класса List
Поля класса Label
Поля класса TextField
Поля класса TextArea
Нестандартные текстовые поля
Кнопки и события в JDK 1.1

Линейки Scrollbar
Окно ScrollPane

Назад Вперед

3.2. Кнопки с графикой и анимацией

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

Исходный текст примера

Архив проекта для Java WorkShop 2.0

Демонстрация
(ваш браузер должен уметь работать с аплетами Java)

Немного теории

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

К сожалению, вы не можете каким-либо образом изменить внешний вид кнопок Button.

Что же делать?

Выход есть. Относительно легко создать свою кнопку, пользуясь классом Canvas. Этот класс, произошедший от класса java.awt.Component, как раз и предназначен для создания компонент с нестандартным поведением.

Класс Canvas

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

Установите размеры окна органа управления

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

Метод minimumSize, определенный в классе java.awt.Component, задает минимальные размеры компонента при его размещении в окне. В JDK 1.1 этот метод был заменен на аналогичный с названием getMinimumSize.

Метод preferredSize (и метод getPreferredSize из JDK 1.1) позволяет задать предпочтительные размеры компонента.

В классе java.awt.Component предусмотрен также метод getMaximumSize, возвращающий (в JDK 1.1) максимально допустимый размер окна компонента.

Если вы создаете компонент для кнопки, и в окне компонента будет рисоваться графическое изображение кнопки (в нажатом или отжатом состоянии), размеры окна кнопки лучше всего сделать равным размерам этого изображения.

Предусмотрите метод paint

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

Для того чтобы ваш орган управления стал чувствителен к операциям, выполняемым при помощи мыши, следует предусмотреть такие методы как mouseDown, mouseEnter и так далее.

Как сделать анимацию

Реализовав в классе, образованном на базе класса Canvas, интерфейс Runnable, вы можете создать многопоточный компонент. Отдельный поток в виде метода run может, например, периодически перерисовывать окно кнопки, меняя цвет надписи или что-нибудь еще.

Описание примера

В окне нашего аплета расположены три кнопки с надписями Red, Blue и Yellow (рис. 1).

Рис. 1. Кнопки в исходном состоянии

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

Рис. 2. Изменение цвета названия кнопки

Когда же мы нажимаем на кнопку, она "утапливается" вглубь окна, при этом надпись на кнопке перекрашиваетя в синий цвет (рис. 3).

Рис. 3. Кнопка Red в нажатом состоянии

Для каждой кнопки мы подготовили два графических изображения, показанных ниже:

Кнопка в отжатом состоянии
Кнопка в нажатом состоянии

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

Главный класс аплета

В главном классе аплета, созданном как обычно на базе класса Applet, мы определили три поля для хранения ссылок на наши самодельные кнопки и два поля на объекты класса Image:

cButton btnRed;
cButton btnBlue;
cButton btnYellow;

Image imgButtonUp;
Image imgButtonDn;

Оставим пока в стороне описание разработанного нами класса cButton и займемся исходными текстами главного класса аплета.

Метод init

В процессе инициализации аплета метод init устанавливает начальный цвет фона и изображения, вызывая для этого методы setBackground и tForeground:

setBackground(Color.yellow);
setForeground(Color.black);

Затем метод загружает два изображения кнопки (соответственно, в отжатом и нажатом состоянии):

imgButtonUp = 
  getImage(getCodeBase(), "btnup.gif");
      
imgButtonDn = 
  getImage(getCodeBase(), "btndn.gif");

При загрузке изображения мы указываем ссылку на адрес URL, соответствующий каталогу, в котором расположен CLASS-файл аплета. Такая ссылка возвращается методом getCodeBase.

Далее метод init создает три кнопки как объекты класса cButton:

btnRed =  new cButton(this, 
  imgButtonUp, imgButtonDn, "Red", 80, 20);
    
btnBlue =  new cButton(this, 
  imgButtonUp, imgButtonDn, "Blue", 80, 20);
      
btnYellow =  new cButton(this, 
  imgButtonUp, imgButtonDn, "Yellow", 80, 20);

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

Созданные кнопки добавляются в окно аплета методом add подобно обычным кнопкам класса Button:

add(btnRed);
add(btnBlue);
add(btnYellow);
Метод doButtonAction

Каким образом наш аплет узнает о том, что кнопка была нажата?

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

Исходный текст метода doButtonAction достаточно прост, поэтому мы приводим его здесь полностью:

public void doButtonAction(cButton btn)
{
  if(btn.equals(btnRed))
    setBackground(Color.red);
      
  else if(btn.equals(btnBlue))
    setBackground(Color.blue);
      
  else if(btn.equals(btnYellow))
    setBackground(Color.yellow);
      
  repaint();
}

Метод doButtonAction проверяет ссылку, полученную через параметр btn. Если эта ссылка совпадает со ссылкой на одну из наших кнопок, выполняется изменение цвета фона и перерисовка окна аплета.

Класс cButton

Класс cButton инкапсулирует в себе поведение нашей кнопки. Его структура приведена ниже:

class cButton extends Canvas
  implements Runnable
{
  Image imgButtonUp;
  Image imgButtonDn;
  Container cont;
  int btnWidth;
  int btnHeight;
  Dimension dimMinSize;
  boolean bButtonUp = true;
  
  String btnTitle;
  
  Thread threadAnimate = null;
  Color clrTitleColor = Color.black;
  
  // ----------------------------------
  // Конструктор
  // ----------------------------------

  public cButton(Container parent,
    Image imgUp, Image imgDn,
    String btnTitle, int width, int height)
  {
    . . .
  }  

  // ----------------------------------
  // Методы, возвращающие размеры кнопки
  // ----------------------------------

  public Dimension getPreferredSize()
  {
    . . .
  }
  public Dimension getMinimumSize()
  {
    . . .
  }
  public Dimension preferredSize()
  {
    . . .
  }
  public Dimension minimumSize()
  {
    . . .
  }

  // ----------------------------------
  // Метод paint
  // ----------------------------------

  public void paint(Graphics g)
  {
    . . .
  }

  // ----------------------------------
  // Метод для обработки событий от мыши
  // ----------------------------------

  public boolean mouseDown(
    Event ev, int x, int y)
  {
    . . .
  }
  public boolean mouseUp(
    Event ev, int x, int y)
  {
    . . .
  }
  public boolean mouseEnter(
    Event ev, int x, int y)
  {
    . . .
  }
  public boolean mouseExit(
    Event ev, int x, int y)
  {
    . . .
  }

  // ----------------------------------
  // Метод run для анимации
  // ----------------------------------

  public void run()
  {
    . . .
  }

  // ----------------------------------
  // Метод doButtonAction
  // ----------------------------------

  public void doButtonAction()
  {
    . . .
  }
}

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

Поля класса cButton

Поля imgButtonUp и imgButtonDn предназначены для хранения ссылок на изображения кнопки, соответственно, в отжатом и нажатом состоянии:

Image imgButtonUp;
Image imgButtonDn;

В поле cont хранится ссылка на аплет или другой компонент, внутри которого расположена кнопка:

Container cont;

Поля btnWidth и btnHeight хранят ширину и высоту кнопки, соответственно:

int btnWidth;
int btnHeight;

Для хранения минимальных размеров кнопки мы предусмотрели поле dimMinSize класса Dimension:

Dimension dimMinSize;

Поле bButtonUp хранит текущее состояние кнопки:

boolean bButtonUp = true;

Если в этом поле находится значение true, кнопка отжата, если false - нажата.

Надпись на кнопке записывается в поле btnTitle:

String btnTitle;

В поле threadAnimate класса Thread хранится ссылка на поток анимации, задачей которого является переключение цвета надписи кнопки, на которую указывает курсор мыши:

Thread threadAnimate = null;

И, наконец, в поле clrTitleColor хрантися текущий цвет этой надписи:

Color clrTitleColor = Color.black;
Конструктор класса cButton

Получив управление, конструктор класса cButton проверяет ссылки на изображения кнопок, переданные ему через параметры imgUp и imgDn:

if(imgUp == null || imgDn == null)
{
  System.err.println("Invalid image");
  return;
}

Если эти ссылки равны null (что может быть, например, при неправильном указании имени файлов изображений), конструктор завершает свою работу с сообщением об ошибке.

Далее конструктор сохраняет значения переданных ему параметров в соответствующих полях класса cButton:

imgButtonUp = imgUp;
imgButtonDn = imgDn;
cont = parent;
btnWidth  = width;
btnHeight = height;
this.btnTitle = btnTitle;

Минимальные размеры образуются как объект класса Dimension из ширины и высоты кнопки:

dimMinSize = new Dimension(
  btnWidth, btnHeight);
Методы getPreferredSize, getMinimumSize, preferredSize и minimumSize

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

return dimMinSize;

Для того чтобы обеспечить совместимость с браузерами, не способными работать с методами JDK 1.1, мы переопределили методы preferredSize и minimumSize. Без них кнопки не работают, например, в браузере Netscape Navigator версии 4.

Метод paint

Задача метода paint заключается в рисовании надписи на поверхности кнопки.

Текст надписи хранится в поле btnTitle, а ее цвет определяется содержимым поля clrTitleColor. Заметим, что в процессе анимации метод run периодически меняет цвет надписи с черного на красный и обратно. В нажатом состоянии надпись отображается синим цветом.

Для того чтобы надпись отображалась в окне кнопки по центру, мы определяем ее размеры. Это можно сделать, зная метрики шрифта, полученные методом getFontMetrics:

FontMetrics fm = g.getFontMetrics();
int nTitileWidth = 
  fm.stringWidth(btnTitle);
int nTitleHeight = fm.getAscent() - 
  fm.getLeading() - fm.getDescent();

Ширина строки заголовка (которая зависит не только от шрифта, но и от самого заголовка), определяется методом stringWidth. Что же касается высоты заголовка, то мы вычисляем ее с учетом выступающих элементов символов.

Далее метод paint вычисляет координаты, в которых необходимо нарисовать надпись:

int x0 = (btnWidth - nTitileWidth) / 2;
int y0 = ((btnHeight - nTitleHeight) / 2) +
     nTitleHeight;

При вычислении учитывается ширина и высота окна кнопки.

На следующем этапе метод paint определяет текущее состояние кнопки (нажата или отжата), рисует соответствующее изображение и надпись:

if(bButtonUp)
{
  g.drawImage(imgButtonUp, 0, 0, this);
  g.setColor(clrTitleColor);
  g.drawString(btnTitle, x0, y0);
}
else
{
  g.drawImage(imgButtonDn, 0, 0, this);
  g.setColor(Color.blue);
  g.drawString(btnTitle, 
    x0 - 1, y0 - 1);
}

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

Метод mouseDown

Метод mouseDown получает управление, когда пользователь нажимает кнопку мыши, расположив курсор над поверхностью кнопки.

Прежде всего мы здесь проверяем текущее положение кнопки:

public boolean mouseDown(
  Event ev, int x, int y)
{
  if(bButtonUp)
  {
    bButtonUp = false;
    repaint();
    doButtonAction();
  }  
  return true;
}

Если кнопка отжата, в поле bButtonUp записывается значение false. Таким способом мы переключаем состояние кнопки. Далее метод mouseDown перерисовывает окно кнопки (чтобы там появилось изображение нажатой кнопки) и вызывает метод doButtonAction, определенный в классе cButton. Мы расскажем о нем немного позже.

Метод mouseUp

Метод mouseUp проверяет текущее состояние кнопки и, если она нажата, переключает кнопку в отжатое состоние:

public boolean mouseUp(
  Event ev, int x, int y)
{
  if(!bButtonUp)
  {
    bButtonUp = true;
    repaint();
  }  
  return true;
}

Для этого в поле bButtonUp записывается значение true.

Вслед за этим метод mouseUp перерисовывает окно кнопки.

Метод mouseEnter

Когда пользователь располагает курсор мыши над поверхностью кнопки, получает управление метод mouseEnter. Наша реализация этого метода запускает анимацию как поток класса Thread:

public boolean mouseEnter(
  Event ev, int x, int y)
{
  if(threadAnimate == null)
  {
    threadAnimate = new Thread(this);
    threadAnimate.start();
  }
  return true;
}

Поток запускается методом start.

Метод mouseExit

Метод mouseExit вызывается, когда курсор покидает область окна кнопки. От останавливает анимацию и переключает кнопку в отжатое состояние:

public boolean mouseExit(
  Event ev, int x, int y)
{
  if(threadAnimate != null)
  {
    threadAnimate.stop();
    threadAnimate = null;
    clrTitleColor = Color.black;
  }

  bButtonUp = true;
  repaint();
  
  return true;
}

Сразу после остановки задачи анимации метод mouseExit устанавливает черный цвет надписи, так как поток threadAnimate мог оставить его красным.

Метод run

Метод run выполняет свою работу в бесконечном цикле. Эта работа заключается в периодическом переключении цвета. В качестве флага мы здесь используем переменную bFlipFlop типа boolean. Во время работы цикла она меняет свое значение каждые 300 миллисекунд.

Вот как это происходит:

while(true)
{
  if(bFlipFlop)
  {
    clrTitleColor = Color.red;
    bFlipFlop = false;
  }
  else
  {
    clrTitleColor = Color.black;
    bFlipFlop = true;
  }
  repaint();
    
  try
  {
    Thread.sleep(300);
  }
  catch (InterruptedException ex)
  {
    threadAnimate.stop();
  }
}

После очередного изменения значения цвета в поле clrTitleColor метод run принудительно перерисовывает окно кнопки, вызывая методы repaint.

Перед очередной итерации выполняется задержка на 300 миллисекунд.

Метод doButtonAction

Как мы уже говорили, когда пользователь нажимает кнопку мыши, разместив курсор над окном кнопки, управление передается методу mouseDown, определенному нами в классе cButton.

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

public void doButtonAction()
{
  ((CustomButton)cont).doButtonAction(this);
}

Что делает метод doButtonAction?

Он просто вызывает метод с таким же названием, но определенный в родительском аплете. В качестве параметра мы передаем ему ссылку this, то есть ссылку на объект-кнопку, нажатую пользователем.

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


Назад Вперед

[Назад]