How It’s Made: Cover Gallery

Cover gallery or List View in Revolucion Library is a JList which dispays cover images. You can see it at the beginning of the Revolucion Library screencast.

First of all, we need to extend JList to achieve a selection change animation. In Revolucion Library I used TimingFramework, but here is the example of how to do the same with a great Trident animation library by Kirill.

ListSelectionListener is used to listen for changes in lists selection value, so we can start the animation when a cover is selected.
selectionBounds represent the current bounds of a selection rectangle, and selectionAlpha is used to control the visibility of the selection rectangle – when there is no items in the list, value is 0.0f.
timeline is the main timeline for animations. animationSpeed is actually a duration of the animation in milliseconds.

public class AnimatedList extends JList implements ListSelectionListener, ComponentListener {

	private float selectionAlpha = 0.0f;
	private float newSelectionAlpha = 1.0f;

	private Rectangle selectionBounds = new Rectangle(0, 0, 0, 0);
	private Rectangle currentSelectionBounds = new Rectangle(0, 0, 0, 0);
	private Rectangle newSelectionBounds = new Rectangle(0, 0, 0, 0);

	private Timeline timeline;
	private int animationSpeed = 400;

	private int selectionBorderWidth = 1;
	private int selectionBorderRoundness = 10;
	private Color selectionBorderColor = Color.gray;

	public AnimatedList() {
		setSelectionBackground(new Color(255, 255, 255, 60));



Next we need to setup the Timeline …
Every time is called, selectionBounds will change from currentSelectionBounds to newSelectionBounds, and selectionAlpha from it’s current value to a newSelectionAlpha value. And we add Repaint callback so JList is repainted when these properties change.

private void initAnimation() {
 	timeline = new Timeline(this);
 	timeline.addPropertyToInterpolate("selectionBounds", currentSelectionBounds, newSelectionBounds);
 	timeline.addPropertyToInterpolateTo("selectionAlpha", newSelectionAlpha);
 	timeline.setEase(new Spline(0.8f));
 	timeline.addCallback(new Repaint(this));
 	timeline.addCallback(new TimelineCallbackAdapter() {
 		public void onTimelineStateChanged(TimelineState oldState, TimelineState newState, float durationFraction, float timelinePosition) {
 			if (newState.equals(TimelineState.DONE))

Then we implement ListSelectionListener and ComponentListener.

 public void valueChanged(ListSelectionEvent e) {
 	if (!e.getValueIsAdjusting())

 public void componentHidden(ComponentEvent e) {}

 public void componentMoved(ComponentEvent e) {

 public void componentResized(ComponentEvent e) {

 public void componentShown(ComponentEvent e) {

And here is what updateSelectionBounds() method does:

 public void updateSelectionBounds() {
 	int index = getSelectedIndex();
 	// If there is something selected we set the selection bounds to the bounds of the selected cell ...
 	if (index > -1) {
 		newSelectionBounds.setBounds(getCellBounds(index, index));
 		newSelectionAlpha = 1.0f;
 	// ... or if there is no selection, we do this to simulate rectangle dissapearing ...
 	else {
 		newSelectionBounds.setBounds(getX() + getWidth() / 2, getY() + getHeight() / 2, 0, 0);
 		newSelectionAlpha = 0.0f;

 	// ... and than start the animation
 	if (!timeline.isDone())

And finally, override the paintComponent() to do the selection rectangle painting.

 protected void paintComponent(Graphics g) {

 	Graphics2D g2 = (Graphics2D) g.create();
 	g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, selectionAlpha));
 	g2.fillRoundRect(selectionBounds.x, selectionBounds.y, selectionBounds.width, selectionBounds.height, selectionBorderRoundness, selectionBorderRoundness);
 	g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
 	g2.setStroke(new BasicStroke(selectionBorderWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
 	g2.drawRoundRect(selectionBounds.x, selectionBounds.y, selectionBounds.width, selectionBounds.height, selectionBorderRoundness, selectionBorderRoundness);

Add getters and setters for selectionBounds and selectionAlpha, and that’s it with animations.

To speed up image painting in the JList, I wrote a ListCellRenderer that will cache cover images.

public class CoverListCellRendererTest extends DefaultListCellRenderer {

 private HashMap<String, SoftReference<ImageIcon>> thumbnails;

 public CoverListCellRendererTest() {
 	thumbnails = new HashMap<String, SoftReference<ImageIcon>>();


 public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
 	Movie movie = (Movie) value;


 	String coverFileName = movie.getCover();
 	if (coverFileName != null)

 	return this;

 private ImageIcon getThumbnail(String coverFileName) {
 	ImageIcon icon = null;

 	SoftReference<ImageIcon> reference = thumbnails.get(coverFileName);
 	if (reference != null)
 		icon = reference.get();

 	if (icon == null) {
 		try {
 			BufferedImage image = ImageUtils.getCoverThumb(coverFileName);
 			icon = new ImageIcon(image);
 			thumbnails.put(coverFileName, new SoftReference<ImageIcon>(icon));
 		catch (IOException e) {
 			icon = new ImageIcon(ImageUtils.getEmptyCover());

 	return icon;