Creating a Java Swing range slider

I recently worked on an open-source Java Swing application that included filtering features to help the user refine a list of results.  These features displayed controls to filter on several attributes, such as category, extension, and size.  In particular, the size control used a dual-thumb slider to allow the user to select a range of sizes to display.  A demo screenshot illustrates this.

In order to create the range slider, we needed a new Swing component because the existing JSlider component only displays a single thumb to adjust the slider value.  Interestingly, even though JSlider only allows you to update a single value, its data model actually provides support for two values; these define an internal range within the minimum and maximum bounds.

The internal range is defined by the slider value and an “extent,” which is the length of the inner range that begins at the slider value.  In DefaultBoundedRangeModel, the default “extent” is zero and not normally used for anything.  We can add it to the slider value to define an upper value of a selected range.

public void setUpperValue(int value) {
    // Compute new extent.
    int lowerValue = getValue();
    // Set extent to set upper value.int
    newExtent = Math.min(Math.max(0, value - lowerValue),
        getMaximum() - lowerValue);
    setExtent(newExtent);
}

The new dual-thumb slider component takes advantage of the built-in definition for DefaultBoundedRangeModel, and is implemented with just two classes: RangeSlider and RangeSliderUI.  These are included in the demo source code.

RangeSlider extends JSlider to install a UI delegate and provide value change methods. The methods set the lower and upper values in the selected range.  We define the existing slider value as the lower value, and the upper value as the lower value plus the extent.  To access these values, we override some methods, and add a couple of our own.

  • We add setUpperValue(int) to calculate the extent based on the current lower value.  We apply limits to ensure that the upper value is bounded by the lower value and the maximum.
  • We override setValue(int) to calculate the extent to ensure that the upper value does not change.  We apply limits to ensure that the lower value stays bounded by the minimum and the upper value.
  • We override updateUI() to install a custom UI delegate to render the two thumbs.

RangeSliderUI extends BasicSliderUI to paint the two thumbs and handle user events.  This provides a customized rendering of the two thumbs and handles user events to update their values.  This is somewhat involved so we will only highlight the primary aspects of this code.

The main tasks are: (1) calculating the thumb sizes and locations, (2) painting the thumbs, and (3) handling mouse and keyboard input events on the thumbs.

Step 1: Thumb sizes and locations

To calculate the thumb sizes and locations, we need to override several UI delegate methods.

  • We override installUI(JComponent) to initialize a new upperThumbRect attribute, which we use to store the size and position of the upper thumb.
  • We override calculateThumbSize() to update the size of the upper thumb, which is defined to be the same as the lower thumb.
  • We override calculateThumbLocation() to update the location of the upper thumb, which is based on the upper value stored in the slider component.  The thumb is always positioned to be centered over its value on the track.  The logic is essentially copied from the superclass method and applied to upperThumbRect.
  • We override getThumbSize() to return the dimensions of the thumb.  It is possible to use custom images for the thumbs—any shape will do.

Step 2: Paint

To paint the slider component, we need to override the default implementations of various paint methods in the UI delegate, and add a few of our own.

  • We override paintThumb(Graphics) to do nothing.  This is called by the superclass paint() method to draw the single thumb, and we want to replace this with our own logic for two thumbs.
  • We override paint(Graphics, JComponent) to draw the two thumbs as needed.  We define an upperThumbSelected indicator to keep track of which thumb was last selected, so we can draw the unselected thumb first, and draw the selected one on top of it.
  • We add the paintLowerThumb(Graphics) and paintLowerThumb(Graphics) methods.  The two methods are similar – they draw a thumb in the appropriate rectangular region.  (The sample code uses a colored shape for the thumb.)
  • We override paintTrack(Graphics) to draw the slider track, and highlight the selected range.

Step 3: Handle input

To handle mouse drag events on the thumbs, we define a RangeTrackListener inner class that extends the TrackListener class in BasicSliderUI.  We also override createTrackListener(JSlider) to return a new instance of the class.  In RangeTrackListener, we implement several methods.

  • We override mousePressed(MouseEvent) to detect which thumb is being pressed.
  • We override mouseDragged(MouseEvent) to adjust the appropriate slider value as the thumb is dragged.
  • We override mouseReleased(MouseEvent) to reset indicators.
  • We add moveLowerThumb() and moveUpperThumb() to update the slider values and repaint the thumbs.

Finally, to handle keyboard events on the thumbs, such as the arrow keys, we override a pair of methods used to move the slider by increments.

  • We override scrollByBlock(int) to move the selected thumb by a block increment.  This is called when the Page Up or Page Down key is pressed.
  • We override scrollByUnit(int) to move the selected thumb by a unit increment.  This is called when one of the arrow keys is pressed.

Summary

The dual-thumb slider component allows the user to select a range by adjusting its lower and upper bounds.  And by implementing our own UI delegate, we can adjust the appearance of the component to suit our needs.  This can be very useful when working with visual designers who want to create a unique vision.

The Range Slider Demo source code is available at http://github.com/ernieyu/Swing-range-slider.

Posted in Java, Swing | 17 Comments

Using Java Swing animation

Short animations can enhance the usability of an application by providing visual cues to its behavior.  Modern user interfaces are required to manage large amounts of information in a relatively small screen area.  To do this, they often organize features by collapsing or hiding elements onto the edges of the screen.  By adding a time dimension, an animation can be used to shift visual controls as they appear or disappear, which helps users recall their location for later access.

A recent Java Swing project provided me with an opportunity to apply this idea.  Our application organized user results into a series of tabs displayed horizontally across the top of the window.  As the number of tabs grew, we collapsed the extra tabs into a “More” button that displayed a drop-down list of tabs to choose from.  Screen animations were added as visual cues to indicate the accessibility of the tabs—new  tabs were shifted in from the left, while existing tabs were moved out to the right.

I created a demo application to illustrate how this is done.  A screenshot is shown below.

Strategy

To implement the screen animation, I made use of a pair of Swing animation libraries published by Chet Haase and Romain Guy with their excellent book, Filthy Rich Clients.  The TimingFramework library provides an API useful for controlling an animation.  The AnimatedTransitions library provides classes useful for animating layout changes in Swing containers.  Both libraries can be found at Java.net, or on the book website.

The two libraries make it easy to set up screen animations where UI components are added, deleted, or moved within a container.  The general idea is to define the end state of the UI layout, along with a set of special effects, and let the libraries do the hard work of redrawing the transition from the beginning state to the end state on the screen.

The demo application code contains just three basic components.  The tab container is implemented by an AnimatedTabList object, which defines the component layout.  Each tab is represented by a Tab object, which displays a text label and a close button.  And finally, the screen effects are defined in the EffectsUtilities class.

Layout Animation

AnimatedTabList is the UI container for the tabs.  In addition to displaying the tabs, the class controls the animation by implementing the TransitionTarget interface and creating the objects needed to run the screen transition.  We use a screen transition that runs in 0.25 seconds.

// Create animator and screen transition for this container.
animator = new Animator(250);
transition = new ScreenTransition(this, this, animator);

The methods in AnimatedTabList are organized to provide an API to add and remove tabs, define the horizontal layout for its UI components, and run the animation.

  • addTab(String) and removeTab(Tab) are called to add and remove tabs.  These methods update an internal list of Tab objects that may be displayed by the container.  The actual layout is deferred until the animation is started.
  • layoutTabs() sets up the layout of the tabs.  This method performs calculations to determine which tabs should appear; if there are any hidden tabs, then the “More” button is displayed.  The method also sets the screen effects that take place for tabs that are added or removed.
  • setupNextScreen() is called by the ScreenTransition object when the animation is started.  This method simply calls layoutTabs() to set up the end state of the container.

The animated screen transition is triggered whenever a tab is added or removed.  When the animation begins, the ScreenTransition object determines the end state of the container, and automatically applies the screen effects as it redraws the container.

Transition Effects

We can define a variety of screen effects to apply when UI components are added or removed from the container.  The EffectsUtilities class includes static utility methods to create some simple effects.  Each screen effect extends the Effect base class.  Four effects are defined.

  • MoveIn shifts a component from a specified start point to its end location.
  • MoveOut shifts a component from its start location to a specified end point.
  • Grow causes a component to grow into its current size.
  • Shrink causes a component to shrink from its current size.

In the demo application, these effects are combined into composite effects and applied to the tabs as they are added or removed.

Summary

Screen animations can provide helpful hints to the user as visual elements move around the screen.  They can also make an application feel lively and fun, which has value in any product.  The TimingFramework and AnimatedTransition libraries make it very easy to animate a Java Swing application.

The Animation Demo source code is available at http://github.com/ernieyu/Swing-animation-demo.
The Timing Framework library is available at http://timingframework.dev.java.net/.
The Animated Transitions library is available at http://animatedtransitions.dev.java.net/.

Posted in Java, Swing | Leave a comment

Hello world!

Welcome to my online catalog of thoughts.  Having been recently laid off from a job, it seems like a good time to do some writing.

Posted in Uncategorized | Leave a comment