Понятие интерфейса в java. Приемы программирования: пример применения интерфейсов. Файловая структура Java-проекта
Скажу сразу - при работе с графикой, скорее всего, со временем Вам придется воспользоваться всеми предоставленными инструментами без исключения для достижения наилучшего визуального эффекта. Подробное описание о том «что и как» можно найти - это официальный туториал по Graphics2D. Его должно быть более чем достаточно, чтобы ввести Вас в курс дела.
Я уже привел небольшой пример написания своего UI, но есть и другие варианты кастомизации интерфейса. Каждый отдельный J-компонент производит свою Lightweight-отрисовку при помощи метода paint(), который можно легко переопределить и изменить. Напрямую (не всегда, но чаще всего) его лучше не использовать (не буду вдаваться в подробности, так как это целая тема для отдельного топика). Для следующего примера используем метод paintComponent(). Рассмотрим как его можно применить поближе…
Начну с примера - текстовое поле с визуальным фидбэком при отсутствии содержимого:
JTextField field = new JTextField()Наличие содержимого перепроверяется при печати и потере фокуса полем. Переключившись на другой компонент мы увидим как отрисовывается наше дополнение к JTextField"у:
{
private boolean lostFocusOnce = false ;
private boolean incorrect = false ;{
// Слушатели для обновления состояния проверки
addFocusListener (new FocusAdapter()
{
public void focusLost (FocusEvent e)
{
lostFocusOnce = true ;
repaint ();
}
});
addCaretListener (new CaretListener()
{
public void caretUpdate (CaretEvent e)
{
if (lostFocusOnce)
{
incorrect = getText ().trim ().equals ("" );
}
}
});
}protected void paintComponent (Graphics g)
{
super.paintComponent (g);// Расширенная отрисовка при некорректности данных
if (incorrect)
{
Graphics2D g2d = (Graphics2D) g;// Включаем антиалиасинг для гладкой отрисовки
g2d.setRenderingHint (RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);// Получаем отступы внутри поля
Insets insets;
if (getBorder () == null )
{
insets = new Insets (2, 2, 2, 2);
}
else
{
insets = getBorder ().getBorderInsets (this );
}// Создаем фигуру в виде подчеркивания текста
GeneralPath gp = new GeneralPath (GeneralPath.WIND_EVEN_ODD);
gp.moveTo (insets.left, getHeight () - insets.bottom);
for (int i = 0; i < getWidth () - insets.right - insets.left; i += 3)
{
gp.lineTo (insets.left + i,
getHeight () - insets.bottom - ((i / 3) % 2 == 1 ? 2: 0));
}// Отрисовываем её красным цветом
g2d.setPaint (Color.RED);
g2d.draw (gp);
}
}
};
Полный код примера можно взять .
Таким образом можно расширить любой доступный компонент быстро переопределив и дополнив метод отрисовки, не прибегая к написанию отдельных громоздких UI или полноценных компонентов. Плюс данный пример можно достаточно легко вынести в отдельный класс и использовать как готовый элемент для своего интерфейса.
Еще одним плюсом данного способа является то, что Вы получаете независимый от текущего установленного приложению/компоненту LaF/UI - он будет работать всегда. Естественно, для некоторых специфичных UI может понадобиться немного иная отрисовка - поддерживать её или нет - Вам решать.
Хотя зачастую и хочется сделать интерфейс ярче и привлекательней, не всегда требуется для этого изобретать новые компоненты - можно воспользоваться стандартными средствами. Приведу пример кастомизации простейшего чекбокса анимированной сменой состояний и фона. В этот раз я опять (да, можете бить меня палками) воспользуюсь заготовленными изображениями из умелых рук дизайнера.
За основу берутся 8 изображений 16х16 - 4 состояния фона чекбокса и 4 состояния галки (5 на самом деле, но 5ое мы добавим програмно):
У стандартного чекбокса, конечно, нету возможности задать спрайты для анимации состояний, к тому же нам нужно наложить изображения галки на фоновые в разных вариациях. Для этого допишем отдельный метод:
public static ListОстается добавить несколько обработчиков переходов состояний и мы получим анимированный переход между ними:BG_STATES = new ArrayList ();
public static ListCHECK_STATES = new ArrayList (); static
{
// Иконки состояния фона
for (int i = 1; i <= 4; i++)
{
BG_STATES.add (new ImageIcon (
MyCheckBox.class .getResource ("icons/states/" + i + ".png" )));
}// Дополнительное "пустое" состояние выделения
new BufferedImage (16, 16, BufferedImage.TYPE_INT_ARGB)));// Состояния выделения
for (int i = 1; i <= 4; i++)
{
CHECK_STATES.add (new ImageIcon (
MyCheckBox.class .getResource ("icons/states/c" + i + ".png" )));
}
}private Map
iconsCache = new HashMap (); private synchronized void updateIcon ()
{
// Обновляем иконку чекбокса
final String key = bgIcon + "," + checkIcon;
if (iconsCache.containsKey (key))
{
// Необходимая иконка уже была ранее использована
setIcon (iconsCache.get (key));
}
else
{
// Создаем новую иконку совмещающую в себе фон и состояние поверх
BufferedImage b = new BufferedImage (BG_STATES.get (0).getIconWidth (),
BG_STATES.get (0).getIconHeight (), BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = b.createGraphics ();
g2d.drawImage (BG_STATES.get (bgIcon).getImage (), 0, 0,
BG_STATES.get (bgIcon).getImageObserver ());
g2d.drawImage (CHECK_STATES.get (checkIcon).getImage (), 0, 0,
CHECK_STATES.get (checkIcon).getImageObserver ());
g2d.dispose ();ImageIcon icon = new ImageIcon (b);
iconsCache.put (key, icon);
setIcon (icon);
}
}
Получившийся компонент очень легко будет видоизменить к примеру в радиобаттон, или же добавить в него больше состояний перехода для более плавной анимации и т.п.
Собственно, полный рабочий код и изображения выложил отдельно .
Таким образом путей кастомизации элементов есть достаточно много (даже я, наверно, не знаю/не догадываюсь о некоторых из них). Какой способ выбирать - зависит от ситуации - что необходимо получить, какие изменения возможно сделать в существующем компоненте и т.д.
В завершение данной главы, приведу еще один пример полностью видоизмененного UI кнопки с анимацией, возможностью загругления отдельных углов, возможностью настройки стиля и некоторыми другими улучшениями. Вот несколько скринов с итоговым внешним видом (конечно, анимация тут не видна):
Не буду врать, на данный UI кнопки ушло достаточно много времени, но он создавался без помощи и подсказок дизайнера чистыми средствами Graphics2D и Swing. можно скачать и изучить полный сурс и демо данного UI, если Вам стало интересно. В нем использован достаточно большой спектр возможностей Graphics2D и применены некоторые хитрости, которые могут зачастую оказаться полезными.
Итак, думаю достаточно разговоров о графике - о ней более подробно я расскажу в будущих топиках, а сейчас приведу немного интересного материала, который я наработал за достаточно долгое время «общения» со Swing и Graphics2D.
DnD и GlassPane
Думаю первое - Вам всем более чем известно, как и проблемы с ним связанные. Насчет второго - вероятно Вы вскользь слышали о GlassPane или может даже видели это старинное изображение (которое до сих пор актуально, между прочем) об устройстве различных слоев стандартных фреймов. Что же тут такого и зачем я вспомнил об этом? И тем более, как связаны DnD и GlassPane спросите Вы? Вот именно о том, как их связать и что из этого может выйти я и хочу рассказать в этой главе.Чтож, начнем по порядку - что мы знаем о DnD?
У некоторых Swing-компонентов есть готовые реализации для драга (JTree и JList к примеру) - для других можно достаточно легко дописать свою. Чтобы не бросаться словами на ветер - приведу небольшой пример DnD стринга из лэйбла:
JLabel label = new JLabel ("Небольшой текст для DnD" );Теперь возможно перетащить текст данного лэйбла прямо из интерфейса в любое другое место, куда возможно вставить текст через дроп.
label.setTransferHandler (new TransferHandler()
{
public int getSourceActions (JComponent c)
{
return TransferHandler.COPY;
}public boolean canImport (TransferSupport support)
{
return false ;
}protected Transferable createTransferable (JComponent c)
{
return new StringSelection (((JLabel) c).getText ());
}
});
label.addMouseListener (new MouseAdapter()
{
public void mousePressed (MouseEvent e)
{
if (SwingUtilities.isLeftMouseButton (e))
{
JComponent c = (JComponent) e.getSource ();
TransferHandler handler = c.getTransferHandler ();
handler.exportAsDrag (c, e, TransferHandler.COPY);
}
}
});
Фактически - TransferHandler овечает за то, какие данные при драге отдает компонент и как компонент использует входящие данные при драге на него.
Но что делать, если необходимо отследить последовательность действий пользователя при самом перетаскивании?
Для этого есть отдельная возможность повесить слушатель:
DragSourceAdapter dsa = new DragSourceAdapter()Остался последний момент - обозначить роль GlassPane. GlassPane, фактически, позволяет располагать/отрисовывать на себе компоненты, как и любой другой контейнер, но его особенность в том, что он лежит поверх всех Swing-компонентов, когда виден. Т.е. если мы что-либо отрисуем на нем, то оно накроет весь находящийся под ним интерфейс. Это позволяет размещать компоненты независимо от основного контейнера в любом месте, создавать любые визуальные эффекты и делать другие занятные вещи.
{
public void dragEnter (DragSourceDragEvent dsde)
{
// При входе драга в область какого-либо компонента
}public void dragExit (DragSourceEvent dse)
{
// При выходе драга в область какого-либо компонента
}public void dropActionChanged (DragSourceDragEvent dsde)
{
// При смене действия драга
}public void dragOver (DragSourceDragEvent dsde)
{
// При возможности корректного завершения драга
}public void dragMouseMoved (DragSourceDragEvent dsde)
{
// При передвижении драга
}public void dragDropEnd (DragSourceDropEvent dsde)
{
// При завершении или отмене драга
}
};
DragSource.getDefaultDragSource ().addDragSourceListener (dsa);
DragSource.getDefaultDragSource ().addDragSourceMotionListener (dsa);
Приведу для большего понимания небольшой пример подобного «эффекта» - фрейм с несколькими Swing-компонентами на нем. При клике в любой части окна будет появляться эффект «распозающегося» круга, который виден поверх всех элементов. Что самое интересное - подобный эффект не съедает ресурсов и не требует большой груды кода. Не верите? - посмотрите демо и загляните в исходник, вложенный в jar.
Кстати говоря, есть достаточно интересная библиотека на эту тему, заодно предоставляющая дополнительный скролл-функционал и несколько других вкусностей - JXLayer (офф сайт) (описание #1 описание #2 описание #3). К сожалению проекты хостящиеся на сайте java сейчас находтся не в лучшем состоянии, поэтому приходится ссылаться на отдельные ресурсы.
Итак теперь объединим всё что я уже описал в данной главе и сделаем, наконец, что-то полноценное. К примеру - отображение драга панели с компонентами внутри окна:
При драге панели за лэйбл появляется полупрозрачная копия панели показывающая где именно будет размещена панель при завершении драга. Также клавишей ESC можно отменить перемещение.
Рабочий пример и исходный код можно взять .
Конечно, для реализации именно данной функциональности не стоило бы прибегать к использованию DnD - есть более короткие пути. Впрочем, всё зависит от ситуации. Данный вариант позволяет отрисовывать перетаскивание независимо от других компонентов в окне. Также можно на основе него, к примеру, реализовать драг панели между окнами приложения.
AWTUtilities
Так как уже достаточно давно в JDK6 включили некоторые будущие нововведения из 7ки, не могу обойти их стороной, так как с их помощью возможно много чего сделать приложив при этом гораздо меньшие усилия.Итак, нас интересует несколько методов из AWTUtilities:
- AWTUtilities.setWindowShape(Window, Shape) - позволяет установить любому окну определенную форму (будь то круг или хитрый полигон). Для корректной установки формы окно не должно быть декорировано нативным стилем (setUndecorated(true)).
- AWTUtilities.setWindowOpacity (Window, float) – позволяет задать прозрачность окна от 0 (полностью прозрачно) до 1 (непрозрачно). Окно при этом может быть декорировано нативным стилем.
- AWTUtilities.setWindowOpaque (Window, boolean) – позволяет полностью спрятать отображение фона и оформления окна, но при этом любой размещенный на нем компонент будет виден. Для корректной установки данного параметра окно также как и в п.1 не должно быть декорировано нативным стилем.
Что же нам это дает? На самом деле - достаточно широкий спектр возможностей. Окнам своего приложения можно задавать любые хитрые формы какие Вам только потребуются, делать «дырки» посреди приложения, создавать кастомные тени под окно, делать приятные на вид попапы и пр. и пр.
Если переходить к конкретике - setWindowShape на деле я никогда не использую, так как задаваемая окну форма строго обрезается по краю и не очень приятно выглядит. На помощь приходит setWindowOpaque - спрятав оформление и фон окна можно с помощью контейнера с кастомным отрисованным фоном создавать абсолютно любые окна. Приведу небольшой пример использования (в нем также есть также использованы некоторые приемы из предыдущих глав поста):
можно взять работающий jar с исходным кодом. Честно скажу, что потратил на данный пример не более десяти минут (из них - минут пять прикидывал как расположить элементы внутри диалога:). Естественно это лишь один из вариантов примемения этих новых возможностей - на самом деле их куда больше.
Единственная неприятная мелочь в использовании AWTUtilities – нестабильная работа на Linux-системах. Т.е. Не везде и не всегда корректно отрабатывает прозрачность окон. Не уверен, проблемы ли это текущей JDK или же ОС.
Создание своих интерактивных компонентов
Я уже поверхностно рассказал о том, как создавать компоненты, UI и некоторые «навороты» для интерфейса своего приложения, но что же делать, если нам нужно добавить функциональную часть к компоненту или создать свой совершенно новый компонент с некоей функциональностью и стилизацией? Стилизовать стандартные компоненты и делать из них отдельные части нового компонента достаточно долгое и нудное занятие, тем более что при малейшем изменении в одном из компонентов вся схема может поехать. Именно в таких случаях стоит сделать свой компонент «с нуля».Итак, за основу лучше лучше всего взять JComponent и используя paint-методы отрисовать его содержимое. Фактически JСomponent сам по себе - чистый холст с некоторыми зашитыми улучшениями для отрисовками и готовыми стандартными методами setEnabled/setFont/setForeground/setBackground и т.п. Как использовать (и использовать ли их) решать Вам. Все, что Вы будете добавлять в методы отрисовки станет частью компонента и будет отображаться при добавлении его в какой-либо контейнер.
Кстати, небольшое отступление, раз уж зашла речь о контейнерах, - любой наследник и сам JComponent являются контейнерами, т.е. могут содержать в себе другие компоненты, которые будет расположены в зависимости от установленного компоненту лэйаута. Что же творится с отрисовкой чайлд-компонентов, лежащих в данном и как она связана с отрисовкой данного компонента? Ранее я не вдавался подробно в то, как устроены и связаны paint-методы Jcomponent"а, теперь же подробно опишу это…
Фактически, paint() метод содержит в себе вызовы 3ех отдельных методов - paintComponent, paintBorder и paintChildren. Конечно же дополнительно он обрабатывает некоторые «особенные» случаи отрисовки как, например печать или перерисовку отдельной области. Эти три метода всегда вызываются в показанной на изображении выше последовательности. Таким образом сперва идет отрисовка самого компонента, затем поверх рисуется бордер и далее вызывается отрисовка чайлд-компонентов, которые в свою очередь также вызывают свой paint() метод и т.д. Естественно есть еще и различные оптимизации, предотвращающие лишние отрисовки, но об этом подробнее я напишу потом.
Компонент отрисован, но статичен и представляет собой лишь изображение. Нам необходимо обработать возможность управления им мышью и различными хоткеями.
Для этого, во-первых, необходимо добавить соответствующие слушатели (MouseListener/MouseMotionListener/KeyListener) к самому компоненту и обрабатывать отдельные действия.
Чтобы не объяснять все на пальцах, приведу пример компонента, позволяющего визуально ресайзить переданный ему ImageIcon:
можно взять рабочий пример с исходным кодом внутри.
При создании данного компонента я бы выделил несколько важных моментов:
- Определяемся с функционалом и внешним видом компонента - в данном случае это область с размещенным на ней изображением, бордером вокруг изображения и 4мя ресайзерами по углам. Каждый из ресайзеров позволяет менять размер изображения. Также есть возможность передвигать изображение по области, «схватив» его за центр.
- Определяем все необходимые для работы компонента параметры - в данном случае это само изображение и его «опорные» точки (верхний левый и правый нижний углы). Также есть ряд переменных, которые понадобятся при реализации ресайза и драга изображения.
- Накидываем заготовку для компонента (желательно отдельный класс, если Вы собираетесь его использовать более раза) - в данном случае создаем класс ImageResizeComponent, определяем все необходимые для отрисовки параметры, переопределяем метод paintComponent() и отрисовываем содержимое. Также переопределяем метод getPreferredSize(), чтобы компонент сам мог определить свой «желаемый» размер.
- Реализовываем функциональную часть компонента - в данном случае нам будет достаточно своего MouseAdapter"а для реализации ресайза и передвижения. При нажатии мыши в области проверяем координаты и сверяем имх с координатами углов и самого изображения - если нажатие произошло в районе некого угла - запоминаем его и при драге изменяем его координату, ежели нажатие пришлось на изображение - запоминаем начальные его координаты и при перетаскивании меняем их. И наконец, последний штрих - в mouseMoved() меняем курсор в зависимости от контрола под мышью.
Конечно, я просто описал свои шаги при создании компонентов - они не являются чем-то обязательным и могут быть легко расширены и дополнены.
Важно помнить
Есть несколько вещей, которые стоит при любой работе с итерфейсными элементами в Swing – приведу их в этой отдельной небольшой главе.- Любые операции вызова перерисовки/обновления компонентов должны производится внутри Event Dispatch потока для избежания визуальных «подвисаний» интерфейса Вашего приложения. Выполнить любой код в Event Dispatch потоке достаточно легко:
SwingUtilities.invokeLater (new Runnable()
Важно также помнить, что события, вызываемые из различных listener"ов стандартных компонентов (ActionListener/MouseListener и пр.) исходно вызываются из этого потока и не требуют дополнительной обработки.
{
public void run ()
{
// Здесь располагаете исполняемый код
}
}); - Из любых paint-методов ни в коем случае нельзя влиять на интерфейс, так как это может привести к зацикливаниям или дедлокам в приложении.
- Представьте ситуацию - из метода paint Вы изменяете состояние кнопки, скажем, через setEnabled(enabled). Вызов этого метода заставляет компонент перерисоваться и заново вызвать метод paint и мы опять попадаем на этот злополучный метод. Отрисовка кнопки зациклиться и интерфейс приложения будет попросту висеть в ожидании завершения отрисовки (или как минимум съест добрую часть процессора). Это самый простой вариант подобной ошибки.
- Не стоит производить тяжёлые вычисления внутри Event Dispatch потока. Вы можете произвести эти вычисления в отдельном обычном потоке и затем только вызвать обновление через SwingUtilities.invokeLater().
- Не стоит, также, производить тяжёлые вычисления внутри методов отрисовки. По возможности стоит выносить их отдельно и кэшировать/запоминать. Это позволит ускорить отрисовку компонентов, улучшить общую отзывчиввость приложения и снизить нагрузку на Event Dispatch поток.
- Для обвноления внешнего вида компонентов используйте метод repaint() (или же repaint(Rectangle) – если Вам известна точная область для обновления), сам метод repaint необходимо исполнять в Event Dispatch потоке. Для обновления же расположения элементов в лэйауте используйте метод revalidate() (его нет необходимости исполнять в Event Dispatch потоке). Метод updateUI() может помочь в некоторых случаях для полного обновления элемента (например смене данных таблицы), но его нужно использовать аккуратно, так как он также отменит установленный Вами UI и возьмет UI предоставляемый текущим LaF"ом.
- Полная установка LaF всему приложению отменит любые ваши вручную заданные в ходе работы UI компонентов и установит поверх них те UI, которые предлагает устанавливаемый LaF. Поэтому лучше производить установку LaF при загрузке приложения до открытия каких-либо окон и показа визуальных элементов.
Думаю, что этот список можно дополнить еще несколькими пунктами, но они уже будут весьма специфичны/необязательны.
Итоги
Используя, совмещая и варьируя вышеописанные возможности и особенности работы с интерфейсами возможно добиться любого желаемого результата. Необходимо лишь проявить немного фантазии в этом деле. Надеюсь даже базовые примеры данного поста дали Вам достаточно много пищи для размышлений и, возможно, помогли с чем-либо.Еще хотелось бы добавить, что возможно, название статьи может показаться Вам слишком громким и не полностью раскрытым - действительно, я все же не дизайнер и не юзабилити-специалист. Я лишь предлагаю Вам инструменты/способы, которые позволят расширить и улучшить интерфейс приложения. Непосредственно бОльшая (не обязательно вся) часть работы по созданию графики для слайдера/кнопки или определению общего стиля компонентов и самого приложения всегда остается за дизайнером - от этого никуда не уйти.
Все примеры статьи в едином «флаконе». Из начального окна можно выбрать желаемый пример: Добавить метки
Интерфейс – это контракт, в рамках которого части программы, зачастую написанные разными людьми, взаимодействуют между собой и со внешними приложениями. Интерфейсы работают со слоями сервисов, безопасности, DAO и т.д. Это позволяет создавать модульные конструкции, в которых для изменения одного элемента не нужно трогать остальные.
Новички часто спрашивают, чем интерфейс отличается от абстрактного класса . Интерфейсы в Java компенсируют отсутствие множественного наследования классов. У класса-потомка может быть только один абстрактный класс-родитель, а вот интерфейсов класс может применять (имплементировать) сколько угодно.
Интерфейс на Java объявляют примерно так же, как и класс:
public interface MyInterface { void do_something (){ // ... } default void say_goodbye (String userName ) { System . out. println("Пока, " + userName+ "! Заходи ещё." ); } }В имплементирующем интерфейс классе должны быть реализованы все предусмотренные интерфейсом методы, за исключением методов по умолчанию.
Методы по умолчанию впервые появились в Java 8. Их обозначают модификатором default. В нашем примере это метод say_goodbye, реализация которого прописана прямо в интерфейсе. Дефолтные методы изначально готовы к использованию, но при необходимости их можно переопределять в применяющих интерфейс классах.
Функциональный интерфейс Java
Если у интерфейса только один абстрактный метод, перед нами функциональный интерфейс. Его принято помечать аннотацией @FunctionalInterface, которая указывает компилятору, что при обнаружении второго абстрактного метода в этом интерфейсе нужно сообщить об ошибке. Стандартных (default) методов у интерфейса может быть множество – в том числе принадлежащих классу java.lang.Object.
Как выглядит функциональный интерфейс на Java:
@FunctionalInterface public interface GaMechanic { void new_deck(); default int deal_cards(int num_of_players) { // тело метода } default int check_your_cards(int hand) { //... } default int battle(Card player1_Card, Card player2_Card) { //... } }
Функциональные интерфейсы появились в Java 8. Они обеспечили поддержку лямбда-выражений, использование которых делает код лаконичным и понятным:
Button. setOnAction(event - > // если происходит событие System . out. println("Обрабатываем нажатие кнопки." ));
В той же версии появились пакеты встроенных интерфейсов: java.util.function и java.util.stream.
Реализация интерфейсов классами Java
Допустим, есть интерфейс Edible, которым пользуются классы Fruit, Vegetable, Fish. Экземпляры этих классов можно создавать так:
Edible a1 = new Fruit ("Яблоко" , “Антоновка”); Edible a2 = new Fish ("Нерка слабосолёная" , “Тихий океан”, 150 );Хорошим тоном считается давать интерфейсам названия с окончанием -able/-ible - это показывает, что с объектами, имплементирующими интерфейс, можно что-то делать: Edible (можно есть), Moveable (можно двигать), Clickable (реагирует на клик) и т.д.
Обратите внимание на разницу в конструкторах: для фруктов задаём название и сорт, для рыбы – название, район вылова и вес порции в граммах. Но ссылки на оба объекта храним в переменных одного типа – «Съестное».
Интерфейсы и полиморфизм
Пример выше иллюстрирует один из трех основополагающих принципов ООП - полиморфизм. Мы раскрыли одно и то же явление - съедобность - через несколько классов, свойства и методы которых частично отличаются. Представление разных форм одного явления - это и есть полиморфизм. Если нужно, такую систему всегда можно расширить и скорректировать. В нашем случае - добавить новые виды съестного и методы их приготовления.
В Java полиморфизм можно реализовать через:
- наследование - с переопределением параметров и методов базового класса;
- абстрактные классы - шаблоны для раздельной реализации в разных классах;
- интерфейсы - для имплементации классами.
Интерфейс выручает в ситуации, когда при создании переменной мы не знаем, объект какого класса ей будет присвоен.
Чтобы было проще понять интерфейсы в Java, можно их представлять как полностью абстрактные классы, то есть классы все методы которых не имеют реализации, а только лишь объявлены. Но так было до Java 8, в которой появилась возможность создавать в интерфейсе реализацию метода по умолчанию. Интерфейсы так же могут содержать поля, но все они будут объявлены как final и static , то есть по существу являются константами. Кроме того, интерфейсы служат для реализации подобия множественного наследования в Java, которое в чистом виде в Java не поддерживается.
Как уже говорилось, унаследоваться (extends ) в Java можно только от одного класса, каждый класс В или С происходит из неполной семьи, как показано на рисунке а ниже. Все классы происходят только от "Адама", от класса Object. Но часто возникает необходимость породить класс D от двух классов B и С, как показано на рисунке б . Это называется множественным наследованием (multiple inheritance ). В множественном наследовании нет ничего плохого. Трудности возникают, если классы B и C сами порождены от одного класса А, как показано на рисунке в . Это так называемое "ромбовидное" наследование.
В самом деле, пусть в классе А определен метод f(), к которому мы обращаемся из некоторого метода класса D. Можем мы быть уверены, что метод f() выполняет то, что написано в классе А, т. е. это метод A.f()? Может, он переопределен в классах B и С? Если так, то каким вариантом мы пользуемся: В.f() или C.f()? Конечно, допустимо определить экземпляры классов и обращаться к методам этих экземпляров, но это совсем другая ситуация.
В различных языках программирования этот вопрос решается по-разному, главным образом уточнением имени метода f(). Но при этом всегда нарушается принцип KISS. Вокруг множественного наследования всегда много споров, есть его ярые приверженцы и столь же ярые противники. Не будем встревать в эти споры, наше дело — наилучшим образом использовать средства языка для решения своих задач.
Создатели языка Java после долгих споров и размышлений поступили радикально — запретили множественное наследование классов вообще. При расширении класса после слова extends можно написать только одно имя суперкласса. С помощью уточнения super можно обратиться только к членам непосредственного суперкласса.
Но что делать, если все-таки при порождении надо использовать несколько предков? Например, у нас есть общий класс автомобилей Automobile, от которого можно породить класс грузовиков Truck и класс легковых автомобилей Car. Но вот надо описать пикап Pickup. Этот класс должен наследовать свойства и грузовых, и легковых автомобилей.
В таких случаях используется еще одна конструкция языка Java — интерфейс. Внимательно проанализировав ромбовидное наследование, теоретики ООП выяснили, что проблему создает только реализация методов, а не их описание.
Интерфейс (interface ), в отличие от класса, содержит только константы, заголовки методов, и с Java 8 может содержать реализацию методов по умолчанию.
Интерфейсы тоже размещаются в файлах с расширением.java, которые в свою очередь могут находится в пакетах и подпакетах, часто в тех же самых, что и классы, и тоже компилируются в class-файлы.
Описание интерфейса начинается со слова interface , перед которым может стоять модификатор public , означающий, как и для класса, что интерфейс доступен всюду. Если же модификатора public нет, интерфейс будет виден только в своем пакете.
После слова interface записывается имя интерфейса, потом может стоять слово extends и список интерфейсов-предков через запятую. Таким образом, одни интерфейсы могут порождаться от других интерфейсов, образуя свою, независимую от классов, иерархию, причем в ней допускается множественное наследование интерфейсов. В этой иерархии нет корня, общего предка.
Затем в фигурных скобках записываются в любом порядке константы, заголовки методов и реализации методов по умолчанию. Можно сказать, что в интерфейсе все методы абстрактные (те у которых нет реализации по умолчанию), но слово abstract писать не надо. Константы всегда статические, но слова static и final указывать не нужно. Все эти модификаторы принимаются по умолчанию. Все константы и методы в интерфейсах всегда открыты, не обязательно даже указывать модификатор public .
Вот какую схему можно предложить для иерархии автомобилей:
interface
Automobile{ . . . }
interface
Car extends
Automobile{ . . . }
interface
Truck extends
Automobile{ . . . }
interface
Pickup extends
Car, Truck{ . . . }
Таким образом, интерфейс — это только набросок, эскиз. В нем указано, что делать, но не указано, как это делать (если нет реализации по умолчанию).
Поскольку интерфейсы – это как-бы полностью абстрактные классы, то невозможно создать экземпляр интерфейса, но можно создать объектную ссылку интерфейсного типа . Ссылочная переменная интерфейса располагает сведениями только о тех методах, которые объявлены в этом интерфейсе .
Как же тогда использовать интерфейсы, если невозможно создать объекты на их основе?
Использовать нужно не интерфейс, а его реализацию (implementation ). Реализация интерфейса — это класс, в котором расписываются методы одного или нескольких интерфейсов. В заголовке класса после его имени или после имени его суперкласса, если он есть, записывается слово implements и, через запятую, перечисляются имена интерфейсов.
Вот как можно реализовать иерархию автомобилей:
interface
Automobile{ . . . }
interface
Car extends
Automobile{ . . . }
class
Truck implements
Automobile{ . . . }
class
Pickup extends
Truck implements
Car{ . . . }
interface
Automobile{ . . . }
interface
Car extends
Automobile{ . . . }
interface
Truck extends
Automobile{ . . . }
class
Pickup implements
Car, Truck{ . . . }
Реализация интерфейса может быть неполной, некоторые методы интерфейса могут быть реализованы, а другие — нет. Такая реализация — абстрактный класс, его обязательно надо пометить модификатором abstract .
Если класс реализует более одного интерфейса, то он должен предоставить реализацию каждого метода каждого интерфейса либо он должен быть объявлен как abstract .
Методы, которые реализуют интерфейс, должны быть объявлены как public .
Теперь немного попрактикуемся, чтобы понять все получше на примере со звуками животных из прошлого поста. Переделаем ту программу чтобы она использовала интерфейс.
Слева представлены основной класс с методом main() и интерфейс в котором мы опеделили метод getSound() с реализацией по умолчанию, которая будет срабатывать если в классе имплементирующем этот интерфейс не будет описана реализация данного метода.
Стоит обратить внимание что в классе AnimalSound мы создали массив объектных ссылок типа Soud (интерфейсный тип) и поместили туда ссылки на объекты, реализующие данный интерфейс.
Программа генерирует следующий вывод:
И теперь посмотрим на классы Cow, Cat и Dog:
Как видно наши классы Cow, Cat и Dog которые имплементируют интерфейс Sound имеют каждый свою реализацию метода getSound().
И приведу еще один простой пример, того что класс может реализовывать несколько интерфейсов.\
Как видно из примера класс MaxMin реализует два интрефейса.
Скажу сразу - при работе с графикой, скорее всего, со временем Вам придется воспользоваться всеми предоставленными инструментами без исключения для достижения наилучшего визуального эффекта. Подробное описание о том «что и как» можно найти - это официальный туториал по Graphics2D. Его должно быть более чем достаточно, чтобы ввести Вас в курс дела.
Я уже привел небольшой пример написания своего UI, но есть и другие варианты кастомизации интерфейса. Каждый отдельный J-компонент производит свою Lightweight-отрисовку при помощи метода paint(), который можно легко переопределить и изменить. Напрямую (не всегда, но чаще всего) его лучше не использовать (не буду вдаваться в подробности, так как это целая тема для отдельного топика). Для следующего примера используем метод paintComponent(). Рассмотрим как его можно применить поближе…
Начну с примера - текстовое поле с визуальным фидбэком при отсутствии содержимого:
JTextField field = new JTextField()Наличие содержимого перепроверяется при печати и потере фокуса полем. Переключившись на другой компонент мы увидим как отрисовывается наше дополнение к JTextField"у:
{
private boolean lostFocusOnce = false ;
private boolean incorrect = false ;{
// Слушатели для обновления состояния проверки
addFocusListener (new FocusAdapter()
{
public void focusLost (FocusEvent e)
{
lostFocusOnce = true ;
repaint ();
}
});
addCaretListener (new CaretListener()
{
public void caretUpdate (CaretEvent e)
{
if (lostFocusOnce)
{
incorrect = getText ().trim ().equals ("" );
}
}
});
}protected void paintComponent (Graphics g)
{
super.paintComponent (g);// Расширенная отрисовка при некорректности данных
if (incorrect)
{
Graphics2D g2d = (Graphics2D) g;// Включаем антиалиасинг для гладкой отрисовки
g2d.setRenderingHint (RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);// Получаем отступы внутри поля
Insets insets;
if (getBorder () == null )
{
insets = new Insets (2, 2, 2, 2);
}
else
{
insets = getBorder ().getBorderInsets (this );
}// Создаем фигуру в виде подчеркивания текста
GeneralPath gp = new GeneralPath (GeneralPath.WIND_EVEN_ODD);
gp.moveTo (insets.left, getHeight () - insets.bottom);
for (int i = 0; i < getWidth () - insets.right - insets.left; i += 3)
{
gp.lineTo (insets.left + i,
getHeight () - insets.bottom - ((i / 3) % 2 == 1 ? 2: 0));
}// Отрисовываем её красным цветом
g2d.setPaint (Color.RED);
g2d.draw (gp);
}
}
};
Полный код примера можно взять .
Таким образом можно расширить любой доступный компонент быстро переопределив и дополнив метод отрисовки, не прибегая к написанию отдельных громоздких UI или полноценных компонентов. Плюс данный пример можно достаточно легко вынести в отдельный класс и использовать как готовый элемент для своего интерфейса.
Еще одним плюсом данного способа является то, что Вы получаете независимый от текущего установленного приложению/компоненту LaF/UI - он будет работать всегда. Естественно, для некоторых специфичных UI может понадобиться немного иная отрисовка - поддерживать её или нет - Вам решать.
Хотя зачастую и хочется сделать интерфейс ярче и привлекательней, не всегда требуется для этого изобретать новые компоненты - можно воспользоваться стандартными средствами. Приведу пример кастомизации простейшего чекбокса анимированной сменой состояний и фона. В этот раз я опять (да, можете бить меня палками) воспользуюсь заготовленными изображениями из умелых рук дизайнера.
За основу берутся 8 изображений 16х16 - 4 состояния фона чекбокса и 4 состояния галки (5 на самом деле, но 5ое мы добавим програмно):
У стандартного чекбокса, конечно, нету возможности задать спрайты для анимации состояний, к тому же нам нужно наложить изображения галки на фоновые в разных вариациях. Для этого допишем отдельный метод:
public static ListОстается добавить несколько обработчиков переходов состояний и мы получим анимированный переход между ними:BG_STATES = new ArrayList ();
public static ListCHECK_STATES = new ArrayList (); static
{
// Иконки состояния фона
for (int i = 1; i <= 4; i++)
{
BG_STATES.add (new ImageIcon (
MyCheckBox.class .getResource ("icons/states/" + i + ".png" )));
}// Дополнительное "пустое" состояние выделения
new BufferedImage (16, 16, BufferedImage.TYPE_INT_ARGB)));// Состояния выделения
for (int i = 1; i <= 4; i++)
{
CHECK_STATES.add (new ImageIcon (
MyCheckBox.class .getResource ("icons/states/c" + i + ".png" )));
}
}private Map
iconsCache = new HashMap (); private synchronized void updateIcon ()
{
// Обновляем иконку чекбокса
final String key = bgIcon + "," + checkIcon;
if (iconsCache.containsKey (key))
{
// Необходимая иконка уже была ранее использована
setIcon (iconsCache.get (key));
}
else
{
// Создаем новую иконку совмещающую в себе фон и состояние поверх
BufferedImage b = new BufferedImage (BG_STATES.get (0).getIconWidth (),
BG_STATES.get (0).getIconHeight (), BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = b.createGraphics ();
g2d.drawImage (BG_STATES.get (bgIcon).getImage (), 0, 0,
BG_STATES.get (bgIcon).getImageObserver ());
g2d.drawImage (CHECK_STATES.get (checkIcon).getImage (), 0, 0,
CHECK_STATES.get (checkIcon).getImageObserver ());
g2d.dispose ();ImageIcon icon = new ImageIcon (b);
iconsCache.put (key, icon);
setIcon (icon);
}
}
Получившийся компонент очень легко будет видоизменить к примеру в радиобаттон, или же добавить в него больше состояний перехода для более плавной анимации и т.п.
Собственно, полный рабочий код и изображения выложил отдельно .
Таким образом путей кастомизации элементов есть достаточно много (даже я, наверно, не знаю/не догадываюсь о некоторых из них). Какой способ выбирать - зависит от ситуации - что необходимо получить, какие изменения возможно сделать в существующем компоненте и т.д.
В завершение данной главы, приведу еще один пример полностью видоизмененного UI кнопки с анимацией, возможностью загругления отдельных углов, возможностью настройки стиля и некоторыми другими улучшениями. Вот несколько скринов с итоговым внешним видом (конечно, анимация тут не видна):
Не буду врать, на данный UI кнопки ушло достаточно много времени, но он создавался без помощи и подсказок дизайнера чистыми средствами Graphics2D и Swing. можно скачать и изучить полный сурс и демо данного UI, если Вам стало интересно. В нем использован достаточно большой спектр возможностей Graphics2D и применены некоторые хитрости, которые могут зачастую оказаться полезными.
Итак, думаю достаточно разговоров о графике - о ней более подробно я расскажу в будущих топиках, а сейчас приведу немного интересного материала, который я наработал за достаточно долгое время «общения» со Swing и Graphics2D.
DnD и GlassPane
Думаю первое - Вам всем более чем известно, как и проблемы с ним связанные. Насчет второго - вероятно Вы вскользь слышали о GlassPane или может даже видели это старинное изображение (которое до сих пор актуально, между прочем) об устройстве различных слоев стандартных фреймов. Что же тут такого и зачем я вспомнил об этом? И тем более, как связаны DnD и GlassPane спросите Вы? Вот именно о том, как их связать и что из этого может выйти я и хочу рассказать в этой главе.Чтож, начнем по порядку - что мы знаем о DnD?
У некоторых Swing-компонентов есть готовые реализации для драга (JTree и JList к примеру) - для других можно достаточно легко дописать свою. Чтобы не бросаться словами на ветер - приведу небольшой пример DnD стринга из лэйбла:
JLabel label = new JLabel ("Небольшой текст для DnD" );Теперь возможно перетащить текст данного лэйбла прямо из интерфейса в любое другое место, куда возможно вставить текст через дроп.
label.setTransferHandler (new TransferHandler()
{
public int getSourceActions (JComponent c)
{
return TransferHandler.COPY;
}public boolean canImport (TransferSupport support)
{
return false ;
}protected Transferable createTransferable (JComponent c)
{
return new StringSelection (((JLabel) c).getText ());
}
});
label.addMouseListener (new MouseAdapter()
{
public void mousePressed (MouseEvent e)
{
if (SwingUtilities.isLeftMouseButton (e))
{
JComponent c = (JComponent) e.getSource ();
TransferHandler handler = c.getTransferHandler ();
handler.exportAsDrag (c, e, TransferHandler.COPY);
}
}
});
Фактически - TransferHandler овечает за то, какие данные при драге отдает компонент и как компонент использует входящие данные при драге на него.
Но что делать, если необходимо отследить последовательность действий пользователя при самом перетаскивании?
Для этого есть отдельная возможность повесить слушатель:
DragSourceAdapter dsa = new DragSourceAdapter()Остался последний момент - обозначить роль GlassPane. GlassPane, фактически, позволяет располагать/отрисовывать на себе компоненты, как и любой другой контейнер, но его особенность в том, что он лежит поверх всех Swing-компонентов, когда виден. Т.е. если мы что-либо отрисуем на нем, то оно накроет весь находящийся под ним интерфейс. Это позволяет размещать компоненты независимо от основного контейнера в любом месте, создавать любые визуальные эффекты и делать другие занятные вещи.
{
public void dragEnter (DragSourceDragEvent dsde)
{
// При входе драга в область какого-либо компонента
}public void dragExit (DragSourceEvent dse)
{
// При выходе драга в область какого-либо компонента
}public void dropActionChanged (DragSourceDragEvent dsde)
{
// При смене действия драга
}public void dragOver (DragSourceDragEvent dsde)
{
// При возможности корректного завершения драга
}public void dragMouseMoved (DragSourceDragEvent dsde)
{
// При передвижении драга
}public void dragDropEnd (DragSourceDropEvent dsde)
{
// При завершении или отмене драга
}
};
DragSource.getDefaultDragSource ().addDragSourceListener (dsa);
DragSource.getDefaultDragSource ().addDragSourceMotionListener (dsa);
Приведу для большего понимания небольшой пример подобного «эффекта» - фрейм с несколькими Swing-компонентами на нем. При клике в любой части окна будет появляться эффект «распозающегося» круга, который виден поверх всех элементов. Что самое интересное - подобный эффект не съедает ресурсов и не требует большой груды кода. Не верите? - посмотрите демо и загляните в исходник, вложенный в jar.
Кстати говоря, есть достаточно интересная библиотека на эту тему, заодно предоставляющая дополнительный скролл-функционал и несколько других вкусностей - JXLayer (офф сайт) (описание #1 описание #2 описание #3). К сожалению проекты хостящиеся на сайте java сейчас находтся не в лучшем состоянии, поэтому приходится ссылаться на отдельные ресурсы.
Итак теперь объединим всё что я уже описал в данной главе и сделаем, наконец, что-то полноценное. К примеру - отображение драга панели с компонентами внутри окна:
При драге панели за лэйбл появляется полупрозрачная копия панели показывающая где именно будет размещена панель при завершении драга. Также клавишей ESC можно отменить перемещение.
Рабочий пример и исходный код можно взять .
Конечно, для реализации именно данной функциональности не стоило бы прибегать к использованию DnD - есть более короткие пути. Впрочем, всё зависит от ситуации. Данный вариант позволяет отрисовывать перетаскивание независимо от других компонентов в окне. Также можно на основе него, к примеру, реализовать драг панели между окнами приложения.
AWTUtilities
Так как уже достаточно давно в JDK6 включили некоторые будущие нововведения из 7ки, не могу обойти их стороной, так как с их помощью возможно много чего сделать приложив при этом гораздо меньшие усилия.Итак, нас интересует несколько методов из AWTUtilities:
- AWTUtilities.setWindowShape(Window, Shape) - позволяет установить любому окну определенную форму (будь то круг или хитрый полигон). Для корректной установки формы окно не должно быть декорировано нативным стилем (setUndecorated(true)).
- AWTUtilities.setWindowOpacity (Window, float) – позволяет задать прозрачность окна от 0 (полностью прозрачно) до 1 (непрозрачно). Окно при этом может быть декорировано нативным стилем.
- AWTUtilities.setWindowOpaque (Window, boolean) – позволяет полностью спрятать отображение фона и оформления окна, но при этом любой размещенный на нем компонент будет виден. Для корректной установки данного параметра окно также как и в п.1 не должно быть декорировано нативным стилем.
Что же нам это дает? На самом деле - достаточно широкий спектр возможностей. Окнам своего приложения можно задавать любые хитрые формы какие Вам только потребуются, делать «дырки» посреди приложения, создавать кастомные тени под окно, делать приятные на вид попапы и пр. и пр.
Если переходить к конкретике - setWindowShape на деле я никогда не использую, так как задаваемая окну форма строго обрезается по краю и не очень приятно выглядит. На помощь приходит setWindowOpaque - спрятав оформление и фон окна можно с помощью контейнера с кастомным отрисованным фоном создавать абсолютно любые окна. Приведу небольшой пример использования (в нем также есть также использованы некоторые приемы из предыдущих глав поста):
можно взять работающий jar с исходным кодом. Честно скажу, что потратил на данный пример не более десяти минут (из них - минут пять прикидывал как расположить элементы внутри диалога:). Естественно это лишь один из вариантов примемения этих новых возможностей - на самом деле их куда больше.
Единственная неприятная мелочь в использовании AWTUtilities – нестабильная работа на Linux-системах. Т.е. Не везде и не всегда корректно отрабатывает прозрачность окон. Не уверен, проблемы ли это текущей JDK или же ОС.
Создание своих интерактивных компонентов
Я уже поверхностно рассказал о том, как создавать компоненты, UI и некоторые «навороты» для интерфейса своего приложения, но что же делать, если нам нужно добавить функциональную часть к компоненту или создать свой совершенно новый компонент с некоей функциональностью и стилизацией? Стилизовать стандартные компоненты и делать из них отдельные части нового компонента достаточно долгое и нудное занятие, тем более что при малейшем изменении в одном из компонентов вся схема может поехать. Именно в таких случаях стоит сделать свой компонент «с нуля».Итак, за основу лучше лучше всего взять JComponent и используя paint-методы отрисовать его содержимое. Фактически JСomponent сам по себе - чистый холст с некоторыми зашитыми улучшениями для отрисовками и готовыми стандартными методами setEnabled/setFont/setForeground/setBackground и т.п. Как использовать (и использовать ли их) решать Вам. Все, что Вы будете добавлять в методы отрисовки станет частью компонента и будет отображаться при добавлении его в какой-либо контейнер.
Кстати, небольшое отступление, раз уж зашла речь о контейнерах, - любой наследник и сам JComponent являются контейнерами, т.е. могут содержать в себе другие компоненты, которые будет расположены в зависимости от установленного компоненту лэйаута. Что же творится с отрисовкой чайлд-компонентов, лежащих в данном и как она связана с отрисовкой данного компонента? Ранее я не вдавался подробно в то, как устроены и связаны paint-методы Jcomponent"а, теперь же подробно опишу это…
Фактически, paint() метод содержит в себе вызовы 3ех отдельных методов - paintComponent, paintBorder и paintChildren. Конечно же дополнительно он обрабатывает некоторые «особенные» случаи отрисовки как, например печать или перерисовку отдельной области. Эти три метода всегда вызываются в показанной на изображении выше последовательности. Таким образом сперва идет отрисовка самого компонента, затем поверх рисуется бордер и далее вызывается отрисовка чайлд-компонентов, которые в свою очередь также вызывают свой paint() метод и т.д. Естественно есть еще и различные оптимизации, предотвращающие лишние отрисовки, но об этом подробнее я напишу потом.
Компонент отрисован, но статичен и представляет собой лишь изображение. Нам необходимо обработать возможность управления им мышью и различными хоткеями.
Для этого, во-первых, необходимо добавить соответствующие слушатели (MouseListener/MouseMotionListener/KeyListener) к самому компоненту и обрабатывать отдельные действия.
Чтобы не объяснять все на пальцах, приведу пример компонента, позволяющего визуально ресайзить переданный ему ImageIcon:
можно взять рабочий пример с исходным кодом внутри.
При создании данного компонента я бы выделил несколько важных моментов:
- Определяемся с функционалом и внешним видом компонента - в данном случае это область с размещенным на ней изображением, бордером вокруг изображения и 4мя ресайзерами по углам. Каждый из ресайзеров позволяет менять размер изображения. Также есть возможность передвигать изображение по области, «схватив» его за центр.
- Определяем все необходимые для работы компонента параметры - в данном случае это само изображение и его «опорные» точки (верхний левый и правый нижний углы). Также есть ряд переменных, которые понадобятся при реализации ресайза и драга изображения.
- Накидываем заготовку для компонента (желательно отдельный класс, если Вы собираетесь его использовать более раза) - в данном случае создаем класс ImageResizeComponent, определяем все необходимые для отрисовки параметры, переопределяем метод paintComponent() и отрисовываем содержимое. Также переопределяем метод getPreferredSize(), чтобы компонент сам мог определить свой «желаемый» размер.
- Реализовываем функциональную часть компонента - в данном случае нам будет достаточно своего MouseAdapter"а для реализации ресайза и передвижения. При нажатии мыши в области проверяем координаты и сверяем имх с координатами углов и самого изображения - если нажатие произошло в районе некого угла - запоминаем его и при драге изменяем его координату, ежели нажатие пришлось на изображение - запоминаем начальные его координаты и при перетаскивании меняем их. И наконец, последний штрих - в mouseMoved() меняем курсор в зависимости от контрола под мышью.
Конечно, я просто описал свои шаги при создании компонентов - они не являются чем-то обязательным и могут быть легко расширены и дополнены.
Важно помнить
Есть несколько вещей, которые стоит при любой работе с итерфейсными элементами в Swing – приведу их в этой отдельной небольшой главе.- Любые операции вызова перерисовки/обновления компонентов должны производится внутри Event Dispatch потока для избежания визуальных «подвисаний» интерфейса Вашего приложения. Выполнить любой код в Event Dispatch потоке достаточно легко:
SwingUtilities.invokeLater (new Runnable()
Важно также помнить, что события, вызываемые из различных listener"ов стандартных компонентов (ActionListener/MouseListener и пр.) исходно вызываются из этого потока и не требуют дополнительной обработки.
{
public void run ()
{
// Здесь располагаете исполняемый код
}
}); - Из любых paint-методов ни в коем случае нельзя влиять на интерфейс, так как это может привести к зацикливаниям или дедлокам в приложении.
- Представьте ситуацию - из метода paint Вы изменяете состояние кнопки, скажем, через setEnabled(enabled). Вызов этого метода заставляет компонент перерисоваться и заново вызвать метод paint и мы опять попадаем на этот злополучный метод. Отрисовка кнопки зациклиться и интерфейс приложения будет попросту висеть в ожидании завершения отрисовки (или как минимум съест добрую часть процессора). Это самый простой вариант подобной ошибки.
- Не стоит производить тяжёлые вычисления внутри Event Dispatch потока. Вы можете произвести эти вычисления в отдельном обычном потоке и затем только вызвать обновление через SwingUtilities.invokeLater().
- Не стоит, также, производить тяжёлые вычисления внутри методов отрисовки. По возможности стоит выносить их отдельно и кэшировать/запоминать. Это позволит ускорить отрисовку компонентов, улучшить общую отзывчиввость приложения и снизить нагрузку на Event Dispatch поток.
- Для обвноления внешнего вида компонентов используйте метод repaint() (или же repaint(Rectangle) – если Вам известна точная область для обновления), сам метод repaint необходимо исполнять в Event Dispatch потоке. Для обновления же расположения элементов в лэйауте используйте метод revalidate() (его нет необходимости исполнять в Event Dispatch потоке). Метод updateUI() может помочь в некоторых случаях для полного обновления элемента (например смене данных таблицы), но его нужно использовать аккуратно, так как он также отменит установленный Вами UI и возьмет UI предоставляемый текущим LaF"ом.
- Полная установка LaF всему приложению отменит любые ваши вручную заданные в ходе работы UI компонентов и установит поверх них те UI, которые предлагает устанавливаемый LaF. Поэтому лучше производить установку LaF при загрузке приложения до открытия каких-либо окон и показа визуальных элементов.
Думаю, что этот список можно дополнить еще несколькими пунктами, но они уже будут весьма специфичны/необязательны.
Итоги
Используя, совмещая и варьируя вышеописанные возможности и особенности работы с интерфейсами возможно добиться любого желаемого результата. Необходимо лишь проявить немного фантазии в этом деле. Надеюсь даже базовые примеры данного поста дали Вам достаточно много пищи для размышлений и, возможно, помогли с чем-либо.Еще хотелось бы добавить, что возможно, название статьи может показаться Вам слишком громким и не полностью раскрытым - действительно, я все же не дизайнер и не юзабилити-специалист. Я лишь предлагаю Вам инструменты/способы, которые позволят расширить и улучшить интерфейс приложения. Непосредственно бОльшая (не обязательно вся) часть работы по созданию графики для слайдера/кнопки или определению общего стиля компонентов и самого приложения всегда остается за дизайнером - от этого никуда не уйти.
Все примеры статьи в едином «флаконе». Из начального окна можно выбрать желаемый пример: