The org.jvnet.lafwidget.utils.FadeTracker
class provides
a utility for fade animations on component state changes. Its API
allows tracking component model changes such as rollover effects
(color fade-in / color fade-out), selection animations (animate
checkbox selection), focus animations (focus ring fade-in / fade-out)
and more.
The FadeKind
inner class contains the following static
instances for different cases of state changes:
public static class FadeKind {
/**
* Arming a component.
*/
public static final FadeKind ARM = new FadeKind("lafwidgets.core.arm");
/**
* Pressing a component.
*/
public static final FadeKind PRESS = new FadeKind(
"lafwidgets.core.press");
/**
* Focusing a component.
*/
public static final FadeKind FOCUS = new FadeKind(
"lafwidgets.core.focus");
/**
* Enabling a component.
*/
public static final FadeKind ENABLE = new FadeKind(
"lafwidgets.core.enable");
/**
* Rollover a component.
*/
public static final FadeKind ROLLOVER = new FadeKind(
"lafwidgets.core.rollover");
/**
* Selecting a component.
*/
public static final FadeKind SELECTION = new FadeKind(
"lafwidgets.core.selection");
}
In addition, you can define your own instances of this class for custom animation kinds. Note - be sure to specify unique string in the constructor
The LafWidget.ANIMATION_KIND
client property specified the
animation kind. The value should be one of instances of
org.jvnet.lafwidget.utils.LafConstants.AnimationKind
class (DEBUG
, NONE
, SLOW
, default NORMAL
or FAST
). This property can be set on a component,
container (valid for all contained components unless some
component has this property set) or UIManager
(globally).
The value affects the animation cycle speed (one step for NONE
,
four steps for FAST
, five steps for NORMAL
,
ten steps for SLOW
and hundred steps for DEBUG
).
The API of FadeTracker
class consists of the following sections:
|
|
Getting fade tracker instance |
/**
|
|
|
Commencing fade in / fade out |
/**
|
|
|
Checking if a component is in fade state |
/**
|
|
|
Getting fade state of a component |
/**
|
|
The FadeTrackerCallback
interface is:
/**
* Callback for the fade tracker. Is used when the application (some UI
* delegate) wishes to execute some code on the fade.
*
* @author Kirill Grouchnikov
*/
public static interface FadeTrackerCallback {
/**
* Indicates that a single cycle of fade has been performed. Can be used
* in order to repaint specific portion of the component.
*/
public void fadePerformed(FadeKind fadeKind);
}
It allows attaching a component-specific logic to be executed at fade steps. Here is an example
from Substance tabbed pane UI delegate that illustrates the above API. We add a mouse
motion listener on the tabbed pane. At each mouseMoved
event, we check to see if the current
"rollover" tab (tab under mouse) has changed. If it has, we need to fade-out the previous rollover tab
and fade-in the current rollover tab. However, we do not wish to repaint the whole tabbed pane
on each fade step - only these two tabs need to be repainted.
First, we implement the FadeTrackerCallback
:
protected class TabRepaintCallback implements FadeTrackerCallback {
protected int tabIndex;
protected JTabbedPane tabbedPane;
public TabRepaintCallback(JTabbedPane tabComponent, int tabIndex) {
this.tabbedPane = tabComponent;
this.tabIndex = tabIndex;
}
public void fadePerformed(FadeKind fadeKind) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
ensureCurrentLayout();
int tabCount = tabPane.getTabCount();
if ((tabCount > 0) && (tabIndex < tabCount)
&& (tabIndex < rects.length)) {
// need to retrieve the tab rectangle since the tabs
// can be moved while animating (especially when the
// current layout is SCROLL_LAYOUT)
Rectangle rect = getTabBounds(tabPane, tabIndex);
// System.out.println("Repainting " + tabIndex);
tabPane.repaint(rect);
}
}
});
}
}
Some bookkeeping is here in order to ensure that we will be animating the correct tab screen rectangle (since the tab rectangle can move during the animation cycle). Now we need to attach the relevant mouse listener to the tabbed pane itself (code not relevant to the animation fade omitted):
protected class MouseRolloverHandler implements MouseListener,
MouseMotionListener {
int prevRolledOver = -1;
public void mouseMoved(MouseEvent e) {
mouseLocation = e.getPoint();
int currRolledOver = getRolloverTab();
if (currRolledOver == prevRolledOver) {
} else {
FadeTracker fadeTracker = FadeTracker.getInstance();
if ((prevRolledOver >= 0)
&& (prevRolledOver < tabPane.getTabCount())
&& tabPane.isEnabledAt(prevRolledOver)) {
fadeTracker.trackFadeOut(FadeKind.ROLLOVER, tabPane,
prevRolledOver, true, new TabRepaintCallback(
tabPane, prevRolledOver));
}
if ((currRolledOver >= 0)
&& (currRolledOver < tabPane.getTabCount())
&& tabPane.isEnabledAt(currRolledOver)) {
fadeTracker.trackFadeIn(FadeKind.ROLLOVER, tabPane,
currRolledOver, true, new TabRepaintCallback(
tabPane, currRolledOver));
}
}
prevRolledOver = currRolledOver;
}
public void mouseExited(MouseEvent e) {
if ((prevRolledOver >= 0)
&& (prevRolledOver < tabPane.getTabCount())
&& tabPane.isEnabledAt(prevRolledOver)) {
// only the previously rolled-over tab needs to be
// repainted (fade-out) instead of repainting the
// whole tab as before.
FadeTracker fadeTracker = FadeTracker.getInstance();
fadeTracker.trackFadeOut(FadeKind.ROLLOVER, tabPane,
prevRolledOver, true, new TabRepaintCallback(tabPane,
prevRolledOver));
}
prevRolledOver = -1;
}
}
As can be seen, the prevRolledOver
contains the index of the previous rollover
tab. If the rollover tab changes, we ask the fade tracker to fade out the previous rollover
tab and fade in the new rollover tab. The mouseExited
implementation requests the
fade-out of the previous (in this case it's actually current) rollover tab.
Now that we post the requests on the fade tracker, we need to change the painting code
accordingly. In the overriding implementation of paintTabBackground
method,
we call the fade tracker API to see if the specific tab is tracked, and if so, what is
its current fade cycle (see code below). The fade cycle is a value from 0.0 to 10.0 and is
used as an interpolation coefficient between metal and colored backgrounds. This way
we simulate the animated transition from either unselected (metal) to selected (color)
or from selected (color) to unselected (metal) state. The relevant code snippet is:
if (fadeTracker.isTracked(this.tabPane, tabIndex, null)) {
ColorScheme metallicScheme = SubstanceLookAndFeel
.getMetallicColorScheme();
if (!isRollover) {
// Came from rollover state
colorScheme2 = SubstanceLookAndFeel.getColorScheme();
colorScheme = isSelected ? colorScheme : metallicScheme;
cyclePos = fadeTracker
.getFade10(this.tabPane, tabIndex, null);
} else {
// Came from default state
colorScheme2 = colorScheme;
colorScheme = isSelected ? colorScheme : metallicScheme;
cyclePos = fadeTracker
.getFade10(this.tabPane, tabIndex, null);
}
}
The colorScheme
and colorScheme2
are used later to create the actual
background (for animated tabs one of them points to metal scheme and the
other points to a color scheme). The important variable here is cyclePos
that returns the fade cycle position for the specific tab. This value is later used
to interpolate between the metal and color schemes. At each fade cycle, the custom
fade callback shown earlier (which calls repaint on the specific tab rectangle) is called.
Eventually, we get to the paintTabBackground
which interpolates the background
according to the current fade cycle. At the beginning of the fade-in, the tab is
metal, at the end the tab is colored, and in the middle it is a blend of two schemes.
For more common cases (when the entire component should be repainted during the fading
cycle), you can use the org.jvnet.lafwidget.utils.FadeStateListener
helper
class. This class provides tracking of all changes to button-deriven components
(buttons, toggle buttons, check boxes, radio buttons), including the model
itself (selected, pressed, armed, rollover, enabled) and the focus gain / loss. The
relevant API is (see below for code samples):
/**
* Creates a new listener on model changes that can cause fade animation
* transitions.
*
* @param comp
* Component.
* @param buttonModel
* Model for the component.
* @param callback
* Optional application callback.
*/
public FadeStateListener(Component comp, ButtonModel buttonModel,
FadeTrackerCallback callback)
/**
* Registers listeners on all model changes.
*/
public void registerListeners()
/**
* Unregisters all listeners on model changes.
*/
public void unregisterListeners()
If you decide to use this class in one of your UI delegates, you will need to perform the following steps:
|
|
Add new field to the UI delegate |
protected FadeStateListener fadeStateListener;
|
|
|
Wire it in the installListeners() |
@Override
|
|
|
Unwire it in the uninstallListeners() |
@Override
|
|
|
Use the fade tracker in the painting code |
// check if fading
|
|
In the painting code above, the visibility
is later used to set the opacity
of the radio button mark. This code allows fade animations on selecting /
deselecting the radio button (the radio mark appears / disappears smoothly). The
same principle is easily extended to rollover, enabled and focus animations.