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));
		setSelectionForeground(Color.black);
		setOpaque(false);
		setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		setLayoutOrientation(JList.HORIZONTAL_WRAP);
		setVisibleRowCount(-1);

		addListSelectionListener(this);
		addComponentListener(this);

		initAnimation();
	}

Next we need to setup the Timeline …
Every time timeline.play() 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.setDuration(animationSpeed);
 	timeline.addPropertyToInterpolate("selectionBounds", currentSelectionBounds, newSelectionBounds);
 	timeline.addPropertyToInterpolateTo("selectionAlpha", newSelectionAlpha);
 	timeline.setEase(new Spline(0.8f));
 	timeline.addCallback(new Repaint(this));
 	timeline.addCallback(new TimelineCallbackAdapter() {
 		@Override
 		public void onTimelineStateChanged(TimelineState oldState, TimelineState newState, float durationFraction, float timelinePosition) {
 			if (newState.equals(TimelineState.DONE))
 				currentSelectionBounds.setBounds(newSelectionBounds);
 		}
 	});
 }

Then we implement ListSelectionListener and ComponentListener.


 @Override
 public void valueChanged(ListSelectionEvent e) {
 	if (!e.getValueIsAdjusting())
 		updateSelectionBounds();
 }

 @Override
 public void componentHidden(ComponentEvent e) {}

 @Override
 public void componentMoved(ComponentEvent e) {
 	updateSelectionBounds();
 }

 @Override
 public void componentResized(ComponentEvent e) {
 	updateSelectionBounds();
 }

 @Override
 public void componentShown(ComponentEvent e) {
 	updateSelectionBounds();
 }

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())
 		timeline.cancel();
 	timeline.play();
 }

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


@Override
 protected void paintComponent(Graphics g) {
 	super.paintComponent(g);

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

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 {

 // http://java.sun.com/javase/6/docs/api/java/lang/ref/SoftReference.html
 private HashMap<String, SoftReference<ImageIcon>> thumbnails;

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

 	setOpaque(false);
 	setHorizontalAlignment(JLabel.CENTER);
 	setHorizontalTextPosition(JLabel.CENTER);
 	setVerticalTextPosition(JLabel.BOTTOM);
 }

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

 	setText(movie.getTitle());

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

 	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;
 }
}

About these ads

9 comments on “How It’s Made: Cover Gallery

  1. [...] lot of effort into it, and is now sharing with you some of the tricks he learnt. He has two posts, one about the cover gallery he created , and the other about creating a Java-based YouTube video player [...]

  2. Matthias says:

    Hello,
    I think this is a great example but
    I tried to compile the example and the 2 classes:
    ImageUtils and Movie are not found.
    Can you help me with this issue ?

    Thanks very much in advance!

    Regards,
    Matthias Schneider

    • Željko says:

      Movie is a simple java bean with properties like title, genre, year etc. And ImageUtils has getCoverThumb(File) method that will read the image from the file and resize it (make a thumbnail).

  3. Matthias says:

    Thanks for your answer. I got it running.
    It’s really cool. But what I think is even cooler is the slide show selection of the movies in your program. Do you also publish peaces of that code? That would be very interesting!:-) Is there a good tutorial for using the trident library ?

    Regards
    Matthias Schneider

  4. javanut says:

    Thanks for sharing the knowledge, i really like this app

  5. zanga says:

    hi,
    your program is wonderful,but i can not compil it…the type Timeline is not accepted..

  6. MethoD says:

    Where can I download your application?

    Matthias said he can’t compile.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s